1、C++11的{ }
- 1、c++11以后想统一初始化方式,试图实现一切对象皆可用{ }初始化,所以{ }也叫做初始化列表
- 2、 内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化了以后变成直接构造。
- 3、 { }初始化的过程中,可以省略掉 =。
- 4、 C++11列表初始化的本意是想实现是想实现一个同意的初始化方式,其次他在有些场景下带来的不少便利,如容器push/inset多参数构造的对象时,{}初始化会很方便。
代码演示:
#include<iostream>#include<vector>usingnamespacestd;classDate{friendostream&operator<<(ostream&os,constDate&d);public:Date(intyear=1,intmonth=1,intday=1):_year(year),_month(month),_day(day){cout<<"Date(int year=1,int month=1,int day=1)"<<endl;}Date(constDate&date):_year(date._year),_month(date._month),_day(date._day){cout<<"Date(const Date& date)"<<endl;}private:int_year;int_month;int_day;};ostream&operator<<(ostream&os,constDate&d){os<<d._year<<"/"<<d._month<<"/"<<d._day;returnos;}intmain(){Date d={2,2,3};constDate&d2={2026,8,29};constDate&da{2026,8,30};vector<Date>v;v.push_back(d);v.push_back({2026,8,30});//v.push_back(d2);for(size_t i=0;i<v.size();i++){cout<<v[i]<<endl;}return0;}2、C++11中的:std::initializer_list
- C++11的库中提出了 std::initializer_list的类,auto il={ 10 , 20 , 30 },这个类本质是开一个数组,将数据拷贝过来,底层指针分别指向数组的开始和结束。
- 容器支持⼀个std::initializer_list的构造函数,也就支持任意多个值构成的 {x1,x2,x3…} 进行初始化。STL中的容器支持任意多个值构成的 {x1,x2,x3…} 进行初始化,就是通过std::initializer_list的构造函数支持的。
代码演示:
#include<iostream>usingnamespacestd;intmain(){std::initializer_list<int>mylist;mylist={1,2,3};inti=0;cout<<sizeof(mylist)<<endl;cout<<mylist.begin()<<endl;cout<<mylist.end()<<endl;cout<<&i<<endl;return0;}
这两个地址与i的地址很近,说明数组上存在栈。
3、左值与右值
- 左值是⼀个表示数据的表达式(如变量名或解引用的指针),⼀般是有持久状态,存储在内存中,我们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。
- 右值也是⼀个表示数据的表达式,要么是字面值常量、要么是表达式求值过程中创建的临时对象等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。
- 值得一提的是,左值的英文简写为lvalue,右值的英文简写为rvalue。传统认为它们分别是leftvalue、right value 的缩写。现代C++中,lvalue 被解释为loactor value的缩写,可意为存储在内存中、有明确存储地址可以取地址的对象,而 rvalue 被解释为 read value,指的是那些可以提供
数据值,但是不可以寻址,例如:临时变量,字面量常量,存储于寄存器中的变量等,也就是说左值和右值的核心区别就是能否取地址。
3.1 左值引用与右值引用
- Type& r1 = x; Type&& rr1 = y; 第⼀个语句就是左值引用,左值引用就是给左值取别名,第二个就是右值引用,同样的道理,右值引用就是给右值取别名。
- 左值引用不能直接引用右值,但是const左值引用可以引用右值
- 右值引用不能直接引用左值,但是右值引用可以引用move(左值)
- template typename remove_reference::type&& move (T&&
arg); - move是库里面的⼀个函数模板,本质内部是进行强制类型转换,当然他还涉及一些引用折叠的知识,这个我们后面会细讲。
- 需要注意的是变量表达式都是左值属性,也就意味着⼀个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值
- 语法层面看,左值引用和右值引用都是取别名,不开空间。从汇编底层的角度看下面代码中r1和rr1汇编层实现,底层都是用指针实现的,没什么区别。
代码演示:
#include<iostream>#include<string>usingnamespacestd;intmain(){//左值:可以取地址int*p=newint(0);intb=1;constintc=b;*p=23;strings("sssss");s[0]='x';//左值引用给左值去别名int&r1=b;int*&r2=p;int&r3=*p;//右值引用给右值取别名int&&rr1=10;int&&rr2=b+1;string&&rrs=string("1111");//左值引用引用右值conststring&rrw=string("22222");//右值引用引用左值int&&rr4=move(b);return0;}3.2 引用延长生命周期
右值引因可用于为临时对象延长生命周期,const 的左值引用也能延长临时对象生存期,但这些对象无法被修改。
代码演示:
#include<iostream>#include<string>usingnamespacestd;intmain(){string s="test";//ss不能被修改,但是可以通过const延长生命周期conststring&ss=s+s;string&&ss1=s+s;cout<<ss<<endl;cout<<ss1<<endl;ss1+=" c++11";cout<<ss1<<endl;return0;}3.3 右值与左值的参数匹配
- C++98中,我们实现一个const左值引用作为参数的函数,那么实参传递左值和右值都可以匹配。
- C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会匹配f(左值引用),实参是const左值会匹配f(const 左值引用),实参是右值会匹配f(右值引用)。
- 右值引用变量在用于表达式时属性是左值。
代码演示:
#include<iostream>#include<string>usingnamespacestd;voidf(int&x){cout<<"左值引用重载f("<<x<<")\n";}voidf(constint&x){cout<<"const的左值引用重载f("<<x<<")\n";}voidf(int&&x){cout<<"右值引用重载f("<<x<<")\n";}intmain(){inti=0;constintc=1;int&&ii=2;f(i);f(c);f(move(i));f(i+1);//ii虽然是右值,但是右值引用本身的属性是左值,所以打印结果是左值引用f(ii);f(move(ii));return0;}打印结果::
3.4 右值引用和引动语义的使用场景
3.41、移动构造和移动赋值
- 移动构造函数是⼀种构造函数,类似拷贝构造函数,移动构造函数要求第⼀个参数是该类类型的引用,但是不同的是要求这个参数是右值引用,如果还有其他参数,额外的参数必须有缺省值。
- 移动赋值是⼀个赋值运算符的重载,他跟拷贝赋值构成函数重载,类似拷贝赋值函数,移动赋值函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用。
- 对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第⼀个参数都是右值引用的类型,他的本质是要“窃取”引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去靠欸资源,从提高效率。
对于那些深拷贝代价很大的,我们一般都会选择移动构造和移动赋值,从而减少开销
代码演示:
#include<iostream>#include<assert.h>#include<string.h>#include<algorithm>usingnamespacestd;namespaceMove{classstring{public:typedefchar*iterator;typedefconstchar*const_iterator;iteratorbegin(){return_str;}iteratorend(){return_str+_size;}const_iteratorbegin()const{return_str;}const_iteratorend()const{return_str+_size;}string(constchar*str=" "):_size(strlen(str)),_capacity(_size){cout<<"string 构造"<<endl;_str=newchar[_capacity+1];strcpy(_str,str);}voidswap(string&s){::swap(_str,s._str);::swap(_size,s._size);::swap(_capacity,s._capacity);}string(conststring&s):_str(nullptr),_size(0),_capacity(0){cout<<"string 拷贝构造"<<endl;_capacity=s._capacity;_size=s._size;_str=newchar[_capacity+1];strcpy(_str,s._str);}//移动构造string(string&&s)noexcept{cout<<"string 移动构造"<<endl;swap(s);}string&operator=(conststring&s){cout<<"string 拷贝赋值"<<endl;if(this!=&s){delete[]_str;_size=s._size;_capacity=s._capacity;_str=newchar[s._capacity+1];strcpy(_str,s._str);}return*this;}//移动赋值string&operator=(string&&s)noexcept{cout<<"string 移动赋值"<<endl;swap(s);return*this;}~string(){cout<<"析构"<<endl;delete[]_str;_str=nullptr;}char&operator[](size_t pos){assert(pos<_size);return_str[pos];}voidreserve(size_t n){if(n>_capacity){char*tmp=newchar[n+1];if(_str){strcpy(tmp,_str);delete[]_str;}_str=tmp;_capacity=n;}}voidpush_back(charch){if(_size>=_capacity){size_t newcapacity=_capacity==0?4:_capacity*2;reserve(newcapacity);}_str[_size]=ch;++_size;_str[_size]='\0';}string&operator+=(charch){push_back(ch);return*this;}private:char*_str;size_t _capacity;size_t _size;};}intmain(){Move::strings1("xxxxx");// 拷贝构造Move::string s2=s1;// 构造+移动构造,优化后直接构造Move::string s3=Move::string("yyyyy");// 移动构造Move::string s4=move(s1);cout<<"******************************"<<endl;return0;}3.42右值引用和移动语义解决传值返回问题
代码演示:
#include<iostream>#include<algorithm>#include<string>usingnamespacestd;namespacealgorithm{stringaddStrings(string num1,string num2){string str;intend1=num1.size()-1;intend2=num2.size()-1;intnext=0;while(end1>=0||end2>=0){intval1=end1>=0?num1[end1--]-'0':0;intval2=end2>=0?num2[end2--]-'0':0;intret=val1+val2+next;next=ret/10;ret=ret%10;str+=('0'+ret);}if(next==1){str+='1';}reverse(str.begin(),str.end());returnstr;}}intmain(){string ret=algorithm::addStrings("11111","2222");cout<<ret.c_str()<<endl;return0;}// 场景2//int main()//{// string ret;// ret = algorithm::addStrings("11111", "2222");// cout << ret.c_str() << endl;// return 0;//}
不同编译器的处理方式不同,可能是拷贝构造,优化之后直接一次构造,也可能是移动构造,优化之后变成移动赋值
3.43 右值引用和移动语义在传参中的提效
- 查看STL文档我们发现C++11以后容器的push和insert系列的接口否增加的右值引用版本
- 当实参是一个左值时,容器内部继续调用拷贝构造进行拷贝,将对象拷贝到容器空间中的对象
- 当实参是一个右值,容器内部则调用移动构造,右值对象的资源到容器空间的对象上