🌈个人主页:@ꪔ小林Y
✨个人专栏:《C++》,《C语言》,《数据结构入门——从原理到实战》,《拾光Linux》,《网络安全》
🍀代码信条:每一行代码都是成长的脚印👣,每一次调试成功都是对坚持的回应
目录
- 【C++11】1
- 1.右值引用和移动语义
- 1.1左值和右值
- 1.2.左值引用和右值引用
- 1.3引用延长生命周期
- 1.4移动构造和移动赋值
- 1.5引用折叠
- 1.6完美转发
- 2.可变参数模板
- 2.1概念理解
- 2.2折叠表达式(c++17)
- 2.3emplace接口
- 2.4模拟实现list的emplace_back
【C++11】1
1.右值引用和移动语义
C++11之前学的引用是左值引用,C++11新增了右值引用语法特性,无论是左值引用还是右值引用,都是给对象取别名。
1.1左值和右值
- 左值(lvalue):一个表示数据的表达式,存储在内存中,可以获取它的地址,左值可以出现在赋值符号的左边,也可以在右边。定义时const修饰符后的左值,不能给它赋值,但可以取它的地址。如存储在内存中,有明确存储地址可以取地址的对象。
intmain(){//左值:可以取地址//以下的p,b,c,*p,s,s[0]就是常见的左值int*p=newint(0);intb=1;constintc=b;*p=10;strings("11111");s[0]='x';}- 右值(rvalue):是一个数据的表达式,要么是字面值常量,要么是表达式求值过程中创建的临时对象等,右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址。如临时变量,字面量常量,存储于寄存器中的变量等。
//右值:不能取地址doublex=1.1,y=2.2;//以下10,x+y,fmin(x,y),string("11111")都是常见的右值10;x+y;fmin(x,y);string("11111");1.2.左值引用和右值引用
- 左值引用就是给左值取别名,例:
int& r1=x;,右值引用同样的道理,例:int&& rr1=y; - 左值引用不能直接引用右值,但是const左值可以引用右值,例:
const int& rx1=10; - 右值引用不能直接引用左值,但是右值引用可以引用move(左值),例:
int && rrx1=move(b); - 注意,变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值。
- 左值引用解决的效率问题:传左值引用传参,减少拷贝;传左值引用返回,减少拷贝(部分场景)
左值和右值的参数匹配:C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会匹配f(左值引用),实参是const左值会匹配f(const 左值引用),实参是右值会匹配f(右值引用)。
1.3引用延长生命周期
右值引用可用于为临时对象延长生命周期,const 的左值引用也能延长临时对象生存期,但这些对象无法被修改。
1.4移动构造和移动赋值
移动构造函数是⼀种构造函数,类似拷贝构造函数,移动构造函数要求第⼀个参数是该类类型的引用,但是不同的是要求这个参数是右值引用,如果还有其他参数,额外的参数必须有缺省值。
移动赋值是⼀个赋值运算符的重载,他跟拷贝赋值构成函数重载,类似拷贝赋值函数,移动赋值函数要求第⼀个参数是该类类型的引用,但是不同的是要求这个参数是右值引用。
对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第一个参数都是右值引用的类,他的本质是要“窃取”引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,从提高效率。
1.5引用折叠
- 通过模板或者typedef中的类型操作可以构成引用的引用。
- 规则:右值引用的右值引用折叠成右值引用,左右其他组合均折叠成左值引用。
intmain(){//int&& &r=i;这样直接引用是有问题的,会报错typedefint&lref;typedefint&&rref;intn=0;//引用折叠lref&r1=n;//r1的类型是int&lref&&r2=n;//r2的类型是int&rref&r3=n;//r3的类型是int&rref&&r4=1;//r4的类型是int&&}//由于引用折叠限定,f1实例化以后总是一个左值引用template<classT>voidf1(T&x){}//例://1.没有折叠->实例化为void f1(int& x)f1<int>(n);f1<int>(0);//传右值会报错//2.折叠->实例化为void f1(int& x)f1<int&>(n);f1<int&>(0);//传右值会报错//3.折叠->实例化为void f1(int& x)f1<int&&>(n);f1<int&&>(0);//传右值会报错//4.折叠->实例化为void f1(const int& x)f1<constint&>(n);f1<constint&>(0);//5.折叠->实例化为void f1(const int& x)f1<constint&&>(n);f1<constint&&>(0);//由于引用折叠限定,f2实例化以后可以是左值引用,也可以是右值引用template<classT>voidf2(T&&x){}//例://1.没有折叠->实例化为void f2(int&& x)f2<int>(n);//报错f2<int>(0);//2.折叠->实例化为void f2(int& x)f2<int&>(n);f2<int&>(0);//报错//3.折叠->实例化为void f2(int&& x)f2<int&&>(n);//报错f2<int&&>(0);- 由此,引出了万能引用:
传左值,实例化出左值引用的Function函数
传右值,实例化右值引用的Function函数
template<classT>voidFunction(T&&t){inta=0;T x=1;cout<<&a<<endl;cout<<&x<<endl<<endl;}1.6完美转发
变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值,我们想要保持对象的属性,就需要使用完美转发实现。
template<class T>T&&forward(typename remove_reference<T>::type&arg);template<class T>T&&forward(typename remove_reference<T>::type&&arg);template<classX>voidpush_back(X&&x){//完美转发是为了://保持x的属性往下传递//实例化push_back(Lin::string&& x),保持右值属性往下传递//实例化push_back(Lin::string& x),保持左值属性往下传递insert(end(),forward<X>(x));}2.可变参数模板
2.1概念理解
C++11支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:模板参数包:template<class… Args>;,表示零或多个模板参数;函数参数包:void Func(Args&&… args)(args为零或多个参数);:表示零或多个函数参数。通过…触发参数包展开的过程叫包扩展
template <class ...Args> void Func(Args... args) {}//传值形参template <class ...Args> void Func(Args&... args) {}//传左值引用形参template <class ...Args> void Func(Args&&... args) {}//传万能引用形参
用省略号指出⼀个模板参数或函数参数的⼀个包。函数参数包可以用左值引用或右值引用表示,每个参数实例化时遵循引用折叠规则。
2.2折叠表达式(c++17)
template<class...Args>voidPrint(Args...args){((cout<<args<<" "),...);cout<<"\n";}intmain(){Print();Print(1);Print(1,string("xxxx"));Print(1,string("xxxxx"),2.2);return0;}2.3emplace接口
empalce系列的接口均为模板可变参数,功能上兼容push和insert系列,但是empalce还支持新玩法,假设容器为container,empalce还支持直接插入构造T对象的参数,这样有些场景会更高效⼀些,可以直接在容器空间上构造T对象。
template<class... Args>void emplace_back(Args&&... args);template<class... Args>iterator emplace(const_iterator position, Args&&... args);empalce_back和push_back对比:
push_back:需先构造临时对象,再通过拷贝 / 移动构造到容器中;
emplace_back:接收构造对象的参数包,直接在容器节点中构造对象,减少一次拷贝 / 移动。
下面场景详细看一下区别
- 相同: 例:
构造pair+拷贝/移动构造pair到list节点中data上- 不同:例:
直接把构造pair参数包往下传,直接用pair参数包构造pair,push_back做不到lt1.emplace_back("苹果",1);//lt1.push_back("苹果",1);错误,要传pair或者{}隐式转换pair的值 lt1.push_back({"苹果",1});
2.4模拟实现list的emplace_back
#pragmaoncenamespaceLin{template<classT>structlist_node{list_node<T>*_next;list_node<T>*_prev;T _data;list_node(T&&data):_next(nullptr),_prev(nullptr),_data(move(data))// 移动构造或拷贝构造{}template<class...Args>list_node(Args&&...args):_next(nullptr),_prev(nullptr),_data(std::forward<Args>(args)...){}};template<classT,classRef,classPtr>structlist_iterator{usingSelf=list_iterator<T,Ref,Ptr>;usingNode=list_node<T>;Node*_node;list_iterator(Node*node):_node(node){}// *it = 1Refoperator*(){// ……}Ptroperator->(){// ……}// ++itSelf&operator++(){// ……}Selfoperator++(int){// ……}// --itSelf&operator--(){// ……}Selfoperator--(int){// ……}booloperator!=(constSelf&s)const{// ……}booloperator==(constSelf&s)const{// ……}};template<classT>classlist{usingNode=list_node<T>;public:usingiterator=list_iterator<T,T&,T*>;usingconst_iterator=list_iterator<T,constT&,constT*>;// using iterator = list_iterator<T>;// using const_iterator = list_const_iterator<T>;iteratorbegin(){// ……}iteratorend(){// ……}const_iteratorbegin()const{// ……}const_iteratorend()const{// ……}voidempty_init(){// ……}list(){// ……}list(initializer_list<T>il){// ……}template<classInputIterator>list(InputIterator first,InputIterator last){// ……}list(size_t n,T val=T()){// ……}list(intn,T val=T()){// ……}~list(){// ……}// 传统写法// lt2(lt1)list(constlist<T><){// ……}// lt1 = lt3;list<T>&operator=(constlist<T><){// ……}voidswap(list<T><){// ……}voidclear(){// ……}voidpush_back(constT&x){// ……}// 不是万能引用// 因为T是list的参数,list<Lotso::string>实例化时,T就确定了。voidpush_back(T&&x){// ……}voidpush_front(constT&x){// ……}voidpop_back(){// ……}voidpop_front(){// ……}voidinsert(iterator pos,constT&x){// ……}voidinsert(iterator pos,T&&x){// ……}iteratorerase(iterator pos){// ……}size_tsize()const{// ……}template<class...Args>voidemplace_back(Args&&...args){insert(end(),std::forward<Args>(args)...);}// 原理:本质编译器根据可变参数模板生成对应参数的函数/*void emplace_back(string& s) { insert(end(), std::forward<string>(s)); } void emplace_back(string&& s) { insert(end(), std::forward<string>(s)); } void emplace_back(const char* s) { insert(end(), std::forward<const char*>(s)); } */template<class...Args>iteratorinsert(iterator pos,Args&&...args){Node*cur=pos._node;Node*newnode=newNode(std::forward<Args>(args)...);Node*prev=cur->_prev;// prev newnode curprev->_next=newnode;newnode->_prev=prev;newnode->_next=cur;cur->_prev=newnode;returniterator(newnode);}private:Node*_head;size_t _size=0;};}🎊到这儿,本期【C++11】初识——右值引用和移动语义,可变参数模板的内容就结束了。如果文中有表述不准的地方,或是你有更清晰的理解思路,强烈欢迎在评论区留言交流——技术路上多碰撞,才能更快进步
觉得内容对你有帮助的话,别忘了点赞❤️➕收藏🌟,方便后续回顾复习;想跟着一起系统学习数据结构的朋友,也可以点击关注,下一期我们会聚焦更进一步的学习。不见不散✌️