【C++进阶】C++11(中)
🥕个人主页:开敲🍉
🔥所属专栏:C++🥭
🌼文章目录🌼
4. 可变参数模板
4.1 基本语法及原理
4.2 包扩展
4.3 emplace系列接口
5. 新的类功能
5.1 默认的移动构造和移动赋值
5.2 成员变量声明时给缺省值
5.3 default和delete
5.4 final 和 override
6. STL中的一些变化
7. lambda
7.1 lambda表达式语法
7.2 捕获列表
7.3 lambda 的应用
7.4 lambda 的原理
4. 可变参数模板
4.1 基本语法及原理
① C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:模板参数包,表示零或多个模板参数;函数参数包,表示零或多个函数参数。
template <class ...Args>//模板参数包
void func(Args ... args);//函数参数包template <class ...Args>
void func(Args& ... args);template <class ...Args>
void func(Args&& ... args);
② 语法上用省略号来指出一个模板参数或函数参数表示的一个包。在模板参数列表中,class ... 或 typename ... 指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟 ... 指出接下来表示零或多个形参对象列表;函数参数包可以用左值引用或右值引用表示,跟前面的普通模板一样,每个参数实例化时遵循引用折叠规则。
③ 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
④ 这里我们可以使用 sizeof... 运算符去计算参数包中参数的个数。(注意,sizeof ... 和 sizeof 不同,sizeof ... 用来计算参数包中参数的个数)
原理一:
编译器会生成如上四个函数,根据传递参数类型和个数的不同调用不同的函数。
原理二:
从更本质去看,编译器生成多个函数模板,这样使得泛型编程更加灵活。
4.2 包扩展
对于一个参数包,我们能做的除了能够计算它的参数个数,我们还能将它扩展。当扩展一个包时,我们还要提供用于每个扩展元素的模式,扩展一个包就是将它分解为构成的元素,对于每个元素应用模式,获得扩展后的列表。我们通过在模式的右边放一个省略号(...)来触发扩展操作。底层实现代码如下图所示:
4.3 emplace系列接口
① C++11以后新增了 emplace 系列接口,emplace系列的接口均为可变模板参数,功能上兼容 insert 和 push 系列,但是 emplace 还支持新玩法,假设容器为 container<T>,emplace还支持直接插入构造 T对象的参数,这样有些场景会高校一些,可以直接在容器空间上构造 T对象。
② emplace 总体上而言是高效的,推荐以后使用 emplace 系列来代替 inset 和 push 系列。
③ 传参数包的过程中,如果是 Args&& ...args 的参数包,要完美转发参数包,方式如下 std::fowward(args)... ,否则编译时右值引用就会变成左值。
5. 新的类功能
5.1 默认的移动构造和移动赋值
① 原来C++类中,有6个默认成员函数:构造、拷贝构造、拷贝赋值、析构、取地址重载、const 取地址重载,最重要的是前四个默认成员函数,后面两个的用处不大。C++11以后新增了两个默认成员函数,移动构造和移动赋值重载。
② 如果你没有自己实现移动构造,并且拷贝构造、拷贝赋值、析构中的任意一个你都没写,那么编译器就会自动生成一个默认的移动构造。默认生成的移动构造,对于内置类型会完成逐字节地拷贝;对于自定义类型,则要看这个自定义类型是否有移动构造,有就调用它的移动构造,没有就调用拷贝构造。
③ 同样的,如果你没有自己实现移动赋值,并且拷贝构造、拷贝赋值、析构中的任意一个你都没写,那么编译器就会自动生成一个默认的移动赋值。默认生成的移动赋值,对于内置类型会完成逐字节地拷贝;对于自定义类型,则要看这个自定义类型是否有移动赋值,有就调用它的移动赋值,没有就调用拷贝赋值。
④ 如果你实现了移动构造和移动赋值,则编译器就不会再生成它们。
5.2 成员变量声明时给缺省值
在声明部分给缺省值是C++11以后支持的。
5.3 default和delete
① C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是一些原因这个函数没有生成。比如:当我们提供拷贝构造函数时,移动构造函数就不会再生成了,那么我们就可以使用 default 关键字来指定移动构造生成。
② 如果想要限制某些默认函数的生成,在C++98中,是将该函数声明为private,这样其他人想要调用就会报错。在C++11中更加简单,只需要在该函数声明前加上 =delete即可,该语法指示编译器不生成对应函数的默认版本,称 =delete修饰的函数为删除函数。
5.4 final 和 override
final 和 override 关键在在 继承和多态 部分讲过,可以自行前去学习,这里不再赘述。
6. STL中的一些变化
① 下图中圈起来的部分就是C++11后 STL新增的一些容器,但实际最有用的是 unordered_map 和 unordered_set。这两个容器我们在 unordered_map 和 unordered_set 中也详细介绍了,这里也不再赘述。
② STL 中容器的新接口也不少,最重要的就是 右值引用和移动语义相关的 push/insert/emplace 系列接口和移动构造、移动赋值,还有 initializer_list 版本的构造等,这些前面都讲过了。
③ 容器的范围 for遍历。
7. lambda
7.1 lambda表达式语法
① lambda表达式本质上是一个匿名函数对象,跟普通函数不同的是它可以定义在函数内部。lambda表达式语法使用层而言没有类型,所以我们一般是用 auto 或者模板参数定义的对象去接收 lambda 对象。
② lambda表达式的格式:[capture-list] (parameters)-> return type{function body}:
1. [capture-list]:捕获列表,该列表总是出现在 lambda 表达式开始位置,编译器根据 [] 来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,具体细节7.2中我们再细讲。捕捉列表为空也不能省略。
2. (paremeters):参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()一起省略 --> [capture-list] ->return type{function body};
3. ->return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导 --> [capture-list] {function body}。
4. {function body}:函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略。
7.2 捕获列表
① lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用外层作用域的变量就需要进行捕获。
② 第一种捕获方式是在捕获列表中显示的传值捕获和传引用捕获,捕获多个变量用逗号分隔。[x,y,&z]表示 x、y传值捕获,x传引用捕获。
③ 第二种捕获方式是在捕获列表中隐式捕获,我们在捕获列表写一个 = 表示隐式传值捕获,在捕获列表写一个 & 表示隐式传引用捕获,这样我们 lambda 函数体中用到了哪些变量就会自动捕获哪些变量。
④ 第三种捕获方式是在捕获列表中混合使用隐式捕获和显示捕获。[=,&x]表示除了 x 变量传引用捕获以外其它变量传值捕获;[&,x]表示除了 x 变量传值捕获以外,其它变量传引用捕获。当使用混合捕获时,第一个元素必须是 = 或者 &,并且 & 混合捕获时,后面的捕获变量必须是 值捕获,同样的,如果是 = 混合捕获时,后面的捕获变量必须是 引用捕获。
⑤ lambda 表达式如果在函数局部域中,它可以捕获 lambda 位置之前定义的变量,不能捕获静态局部变量和全局变量,静态局部变量和全局变量也不需要捕获,lambda 表达式中可以直接使用。这也意味着 lambda 表达式如果定义在全局位置,捕获列表必须为空。
⑥ 默认情况下,lambda 捕获列表是被 const 修饰的也就是说传值捕捉的过来的对象不能修改,
mutable加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传值捕捉的对象就可以
修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。
7.3 lambda 的应用
① 在学习 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义⼀个类,相对会比较麻烦。使用 lambda 去定义可调用对
象,既简单右方便。
② lambda 在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等, lambda 的应用还是很广泛的。
7.4 lambda 的原理
① lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,而lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会生成⼀个对应的仿函数的类。
② 仿函数的类名是编译按⼀定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是生成
的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕
捉,编译器要看使⽤哪些就传哪些对象。
创作不易,点个赞呗,蟹蟹啦~