一.列表初始化/{}初始化
回顾:C++98中⼀般数组和结构体可以⽤{}进⾏初始化。
1.C++11中,一切对象皆可用{}初始化,内置类型⽀持,⾃定义类型也⽀持(⾃定义类型本质是类型转换,中间会产⽣临时对象,最后优化了以后变成直接构造)
2.在{}初始化的过程中,可以省略掉=
注:{}初始化不便于对容器进行初始化,若要对容器进行多参数的初始化则需要写多个构造函数,为了解决这一问题,C++11引入了initializer_list
二.C++11中的std::initializer_list
1.initializer_list是一个类模板,这个类的本质是在底层开⼀个数组,将数据拷⻉过来,initializer_list内部有两个指针分别指向数组的开始和结尾。
2.initializer_list - C++ Reference (cplusplus.com),std::initializer_list⽀持迭代器遍历。
3.容器⽀持⼀个std::initializer_list的构造函数,即⽀持任意多个值构成的{x1,x2,x3...}进⾏
初始化。
三.右值引用和移动语义
1.左值和右值
左值:左值是⼀个表示数据的表达式,或是变量名,或是解引⽤的指针
<1>⼀般是有持久状态,存储在内存中,可以获取它的地址;
<2>左值可以出现赋值符号的左边,也可以出现在赋值符号右边;
<3>定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。
右值:右值是⼀个表示数据的表达式,或是字⾯值常量,或是表达式求值过程中创建的临时对象,或是存储于寄存器中的变量
<1>右值不能取地址;
<2>右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边。
注:左值和右值的核心区别就是是否能取地址
2.左值引用和右值引用
<1>示例:左值引用:Type& r1 = x; 右值引用:Type&& rr1 = y;
<2>左值引用可以引用左值,也可以使用const 左值来引用右值
右值引用可以引用右值,也可以引用move(左值)
注:1.move是库⾥⾯的⼀个函数模板,本质内部是进⾏强制类型转换;
2.对左值move之后,再次使用该左值需要慎重,因为该左值的资源可能已经被转移走了
<3>变量表达式都是左值属性,即⼀个右值被右值引⽤绑定后,右值引⽤的变量在用于变量表达式时的属性仍是左值
<4>左值引⽤和右值引⽤都是取别名,不开空间
<5>右值引⽤可⽤于为临时对象延长⽣命周期,const的左值引⽤也能延⻓临时对象⽣命周期,但这些对象⽆法被修改。
3.左值和右值的参数匹配
C++11中分别重载了左值引⽤、const左值引⽤、右值引⽤作为形参的f函数。若实参是左值则会匹配f(左值引⽤);若实参是const 左值则会匹配f(const 左值引⽤);若实参是右值则会匹配f(右值引⽤)
4.右值引用和移动语义的使用场景
回顾:左值引用的使用场景:函数中左值引⽤传参和左值引⽤传返回值。它可以减少拷⻉,同时还可以修改实参和修改返回对象的值
<1>移动构造:移动构造函数是⼀种构造函数,类似拷⻉构造函数。移动构造函数要求第⼀个参数是该类类型的右值引⽤,如果还有其他参数,额外的参数必须有缺省值。
<2>移动赋值:移动赋值是⼀个赋值运算符的重载,他跟拷⻉赋值构成函数重载,类似拷⻉赋值函数,移动赋值函数要求第⼀个参数是该类类型的右值引⽤。
注:对于像string/vector这样的深拷⻉的类或者包含深拷⻉的成员变量的类,移动构造和移动赋值的意义更加明显,因为移动构造和移动赋值的第⼀个参数都是右值引⽤的类型,右值引用的本质是要“窃取”引⽤的右值对象的资源,⽽不是像拷⻉构造和拷⻉赋值那样去拷⻉资源,从而提⾼效率。
<3>右值引⽤和移动语义解决传值返回问题
<4>右值引用和移动语义在传参的提效:
当实参是⼀个左值时,容器内部继续调⽤拷贝构造进行拷贝,将对象拷贝到容器空间中的对象
当实参是⼀个右值,容器内部则调⽤移动构造,右值对象的资源被转移到容器空间中的对象上
5.类型分类
四.引用折叠
1.通过模板或typedef中的类型操作可以构成引⽤的引⽤
2.规则:右值引⽤的右值引⽤折叠成右值引⽤,所有其他组合均折叠成左值引⽤。
3.万能模板:当传递左值时就是左值引⽤,传递右值时就是右值引⽤
template < class T>
void Function(T && t)
{
//....
}
五.完美转发forward
1.引入原因:由于右值引⽤变量表达式的属性是左值,即当传入右值引用的表达式时,Function函数中t的属性是左值,此时若把t传递给下⼀层函数Fun,那么匹配的都是左值引⽤版本的Fun函数。若想要保持t对象的属性,就需要使⽤完美转发实现。
2.完美转发forward本质是⼀个函数模板,它主要通过引⽤折叠的⽅式实现,若传递给Function的实参是右值,T被推导为type&&,没有折叠,forward内部t被强转为右值引⽤返回;传递给Function的实参是左值,T被推导为type&,引⽤折叠为左值引⽤,forward内部t被强转为左值引⽤返回。
3.使用示例:Fun(forward<T>(t));
六.可变参数模板
1.基本内容:可变参数模板包括可变数量参数的函数模板和类模板;可变数⽬的参数被称为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函数参数。
2.语法:⽤省略号来指出⼀个模板参数或函数参数来表⽰⼀个包。在模板参数列表中,class...或
typename...指出接下来的参数表⽰零个或多个类型列表;在函数参数列表中,类型名后⾯跟...指出
接下来表⽰零个或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板⼀样,每个参数实例化时遵循引⽤折叠规则。
template <class ...Args>
void Func(Args... args)
{}
template <class ...Args>
void Func(Args&... args)
{}
template <class ...Args>
void Func(Args&&... args)
{}
3.本质:去实例化对应类型和个数的多个函数。
4.可以使⽤sizeof...运算符去计算参数包中参数的个数。
5.包扩展:解析出参数包,通过在模式的右边放⼀个省略号(...)来触发扩展操作
代码实现:
void ShowList()
{
//编译器递归的终⽌条件,参数包是0个时,直接匹配这个函数
cout << endl;
}
template < class T, class ...Args>
void ShowList(T x, Args... args)
{
cout << x << " ";
// args是N个参数的参数包
// 调⽤ShowList,参数包的第⼀个传给x,剩下N-1传给第⼆个参数包
ShowList(args...);
}
// 编译时递归推导解析参数
template < class ...Args>
void Print(Args... args)
{
ShowList(args...);
}
6.emplace系列接口
<1>empalce系列的接⼝均为模板可变参数
<2>emplace功能上兼容push和insert系列,部分场景下,emplace可以直接构造,而push和insert需要使用构造+移动构造,或是构造+拷贝构造,相对而言,emplace更好用,更强大