[C++11#46](三) 详解lambda | 可变参数模板 | emplace_back | 默认的移动构造

2024-09-04 16:52

本文主要是介绍[C++11#46](三) 详解lambda | 可变参数模板 | emplace_back | 默认的移动构造,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一.lambda

1. 捕捉列表

2. 底层原理

二. 可变参数模板

1. 递归函数方式展开参数包

2. 数组接收方式展开参数包

3. 运用

4.emplace_back

5.移动构造和拷贝构造

强制生成 default


一.lambda

可调用类的对象

  • 函数指针--少用 void(*ptr) (int x)
  • 仿函数--构造类 重载 operator() 对象可以像函数一样使用--eg.模板参数
struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceGreater());
}
  • lambda--匿名函数对象 函数内部,直接定义使用

相当于一个局部的没有函数名的函数对象

int main()
{auto compare = [](int x, int y) {return x > y; };cout << compare(1, 2) << endl;//可以像函数一样调用return 0;
}

内部可以调用全局函数吗?可以

局部函数呢?不可以

1. 捕捉列表

捕捉的方式

  1. [a,b] 传值捕捉
  2. [&a,&b] 传引用捕捉
  3. [=] 传值捕捉方式父作用域中所有变量(包括this指针)
  4. [&] 传引用捕捉方式父作用域中所有变量(包括this指针)
  5. 混合使用,例如捕捉[&x,y]

测试:

int main()
{int a = 0, b = 1;auto add1 = [](int x, int y) { return x + y; };cout << add1(a, b) << endl;auto add2 = [b](int x) {return x + b; };cout << add2(a) << endl;//引用的方式捕捉auto swap = [&a, &b](){int tmp = a;a = b;b = tmp;};swap();cout << "After swapping: a = " << a << ", b = " << b << endl;return 0;
}

特例:

  1. [=, &a, &b]:使用值传递方式捕获所有变量,除了 ab,这两个变量将以引用方式捕获。
  2. [&,a, this]:使用引用传递方式捕获变量 athis,其他变量将以值传递方式捕获。
  3. [=, a]:这个捕捉列表是错误的,因为 = 已经以值传递方式捕获了所有变量,而 a 也被重复捕获了。
  4. 在块作用域以外的lambda函数:捕捉列表必须为空。这意味着lambda不能捕获任何外部变量。
  5. 在块作用域中的lambda函数:只能捕获父作用域中局部变量。
  6. lambda表达式之间不能相互赋值:即使看起来类型相同,也不能直接将一个lambda表达式赋值给另一个。这是因为lambda表达式有特定的捕获行为和生命周期,它们不能像普通变量那样直接赋值。
int main(){int a = 0;int b = 1;int c = 2;int d = 3;const int e = 1;cout << &e << endl;// 引用的方式捕捉所有对象,除了a// a用传值的方式捕捉auto func = [&, a] {//a++;b++;c++;d++;//e++;cout << &e << endl;//const & 得到的是同一个地址};func();return 0;
}

所以对于局部函数我们也可以捕捉后调用,但是没什么必要

2. 底层原理

UUID:唯一识别码

打印查看仿函数:

#include <iostream>class Add {
public:int operator()(int a, int b) const {return a + b;}
};int main() {Add add;int result = add(10, 20);std::cout << "Result: " << result << std::endl;return 0;
}

底层:

将 lambda 和仿函数的底层对比查看:

int main()
{auto f1 = [](int x, int y) {return x + y; };auto f2 = [](int x, int y) {return x + y; };//f1 = f2;cout << typeid(f1).name() << endl;cout << typeid(f2).name() << endl;f1(1, 2);return 0;
}

底层:

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的

  • lambda_uuid(如上编译器没显示)
  • 即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()

编译器底层只有类和仿函数 operate()


二. 可变参数模板

例如 printf,想要几个参数就传几个

  • 模板参数:类型
  • 函数参数:对象

表示为  ...

//模板可变参数
template <class ...Args>
void CppPrint(Args... args)
{cout << sizeof...(args) << endl;//打印参数个数
}
int main()
{CppPrint();CppPrint(1);CppPrint(1, 2);CppPrint(1, 2, 2.2);CppPrint(1, 2, 2.2, string("xxxx"));// ...return 0;
}

我有一技: 可以对可变参数进行打印吗?不可以

类似实现 CppPrint:

要通过编译时的函数重载,递归推演来打印

1. 递归函数方式展开参数包

  • 给函数模板增加一个模板参数这样就可以从接收到的参数包中分离出一个参数出来
    • 在函数模板中递归调用该函数模板,调用时传入剩下的参数包。
    • 如此递归下去,每次分离出参数包中的一个参数,直到参数包中的所有参数都被取出来
  • 还需要给一个递归终止函数
void _ShowList()
{// 结束条件的函数cout << endl;
}template <class T, class ...Args>
void _ShowList(T val, Args... args)
{cout << val << " ";_ShowList(args...);
}//args代表0-N的参数包
template <class ...Args>
void CppPrint(Args... args)
{_ShowList(args...);
}
//传给_,递归挨个解析出第一个值int main()
{CppPrint(1);CppPrint(1, 2);CppPrint(1, 2, 2.2);CppPrint(1, 2, 2.2, string("xxxx"));// ...return 0;
}

2. 数组接收方式展开参数包

还有一种新方式:利用编译器去推演,数组存储,可变参数包一个一个的取出来

  • 这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数
template <class T>
void PrintArg(T t)
{cout << t << " ";
}// 参数包有几个值,就展开调用几次
template <class... Args>
void ShowList(Args... args)
{int arr[] = {PrintArg(args)...};//利用编译器去推演,数组存储cout << endl;
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

3. 运用

模板的可变参数实现调用的多样化,灵活

class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year),_month(month),_day(day){cout << "Date构造" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date拷贝构造" << endl;}private:int _year;int _month;int _day;
};template <class ...Args>
Date* Create(Args... args)
{Date* ret = new Date(args...);return ret;
}int main()
{Date* p1 = Create();Date* p2 = Create(2023);Date* p3 = Create(2023, 9);Date* p4 = Create(2023, 9, 27);
//拷贝构造Date d(2023, 1, 1);Date* p5 = Create(d);return 0;
}

4.emplace_back

带模板参数的&&是万能引用,结合可变参数

emplace 效果的体现场景:主要体现在浅拷贝的拷贝构造的优化

  • emplace 能一直往下传
  • push_back 是先构造,再拷贝构造/移动构造

emplace 可以理解为一个优化的 push_back

5.移动构造和拷贝构造

新的类功能:默认成员函数

在原来的C++类中,编译器会默认生成六个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值运算符重载
  5. 取地址运算符重载
  6. const 取地址运算符重载

其中,最为常用的是前四个,后两个通常使用较少。这些默认成员函数在我们没有显式定义时,编译器会自动生成。

C++11 新增了两个默认成员函数:

  1. 移动构造函数
  2. 移动赋值运算符重载

移动构造函数和移动赋值运算符重载的注意点

  • 如果没有自己定义移动构造函数,并且没有实现析构函数、拷贝构造函数或拷贝赋值运算符中的任意一个,那么编译器会自动生成一个默认的移动构造函数。
    • 对于内置类型成员,执行逐成员按字节的浅拷贝。
    • 对于自定义类型成员,若该成员实现了移动构造函数,则调用移动构造函数;若没有,则调用拷贝构造函数。
  • 类似地,如果没有定义移动赋值运算符重载函数,如上
  • 一旦提供了移动构造函数或移动赋值运算符,编译器就不会再自动生成拷贝构造函数和拷贝赋值运算符。
class Person
{
public:Person(const char* name = "", int age = 0): _name(name), _age(age) {}private:bit::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;            // 调用默认的拷贝构造函数Person s3 = std::move(s1);  // 调用默认的移动构造函数Person s4;s4 = std::move(s2);         // 调用默认的移动赋值运算符return 0;
}

在上述代码中,由于没有定义析构函数、拷贝构造函数和拷贝赋值运算符,编译器会自动生成默认的移动构造和移动赋值运算符。

为什么编译器会自动生成默认的移动构造和移动赋值?

通常,对于需要深拷贝或需要释放资源的类,开发者会显式定义拷贝构造函数、赋值运算符重载和析构函数。如果没有显式定义这些函数,编译器生成的默认移动构造函数和移动赋值运算符将对内置类型执行值拷贝

强制生成 default

在C++11中,可以使用default关键字来强制生成某些默认成员函数,即使开发者提供了其他构造函数。例如,提供了拷贝构造函数后,编译器不会再生成默认的移动构造函数,但可以通过= default来显式请求生成。

class Person
{
public:Person(const char* name = "", int age = 0): _name(name), _age(age) {}Person(const Person& p): _name(p._name), _age(p._age) {}Person(Person&& p) = default;  // 强制生成移动构造函数private:bit::string _name;int _age;
};

即使手动编写了拷贝构造函数,仍然可以通过= default来让编译器生成移动构造函数。

这篇关于[C++11#46](三) 详解lambda | 可变参数模板 | emplace_back | 默认的移动构造的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1136491

相关文章

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

MyBatis常用XML语法详解

《MyBatis常用XML语法详解》文章介绍了MyBatis常用XML语法,包括结果映射、查询语句、插入语句、更新语句、删除语句、动态SQL标签以及ehcache.xml文件的使用,感兴趣的朋友跟随小... 目录1、定义结果映射2、查询语句3、插入语句4、更新语句5、删除语句6、动态 SQL 标签7、ehc

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

一文详解Python如何开发游戏

《一文详解Python如何开发游戏》Python是一种非常流行的编程语言,也可以用来开发游戏模组,:本文主要介绍Python如何开发游戏的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、python简介二、Python 开发 2D 游戏的优劣势优势缺点三、Python 开发 3D