多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

C++11:基本语法与使用方法

C++11:基本语法与使用方法 1、C11的{ }1、c11以后想统一初始化方式试图实现一切对象皆可用{ }初始化所以{ }也叫做初始化列表2、 内置类型支持自定义类型也支持自定义类型本质是类型转换中间会产生临时对象最后优化了以后变成直接构造。3、 { }初始化的过程中可以省略掉 。4、 C11列表初始化的本意是想实现是想实现一个同意的初始化方式其次他在有些场景下带来的不少便利如容器push/inset多参数构造的对象时{}初始化会很方便。代码演示#includeiostream#includevectorusingnamespacestd;classDate{friendostreamoperator(ostreamos,constDated);public:Date(intyear1,intmonth1,intday1):_year(year),_month(month),_day(day){coutDate(int year1,int month1,int day1)endl;}Date(constDatedate):_year(date._year),_month(date._month),_day(date._day){coutDate(const Date date)endl;}private:int_year;int_month;int_day;};ostreamoperator(ostreamos,constDated){osd._year/d._month/d._day;returnos;}intmain(){Date d{2,2,3};constDated2{2026,8,29};constDateda{2026,8,30};vectorDatev;v.push_back(d);v.push_back({2026,8,30});//v.push_back(d2);for(size_t i0;iv.size();i){coutv[i]endl;}return0;}2、C11中的std::initializer_listC11的库中提出了 std::initializer_list的类auto il{ 10 , 20 , 30 }这个类本质是开一个数组将数据拷贝过来底层指针分别指向数组的开始和结束。容器支持⼀个std::initializer_list的构造函数也就支持任意多个值构成的 {x1,x2,x3…} 进行初始化。STL中的容器支持任意多个值构成的 {x1,x2,x3…} 进行初始化就是通过std::initializer_list的构造函数支持的。代码演示#includeiostreamusingnamespacestd;intmain(){std::initializer_listintmylist;mylist{1,2,3};inti0;coutsizeof(mylist)endl;coutmylist.begin()endl;coutmylist.end()endl;coutiendl;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 (Targ);move是库里面的⼀个函数模板本质内部是进行强制类型转换当然他还涉及一些引用折叠的知识这个我们后面会细讲。需要注意的是变量表达式都是左值属性也就意味着⼀个右值被右值引用绑定后右值引用变量变量表达式的属性是左值语法层面看左值引用和右值引用都是取别名不开空间。从汇编底层的角度看下面代码中r1和rr1汇编层实现底层都是用指针实现的没什么区别。代码演示#includeiostream#includestringusingnamespacestd;intmain(){//左值可以取地址int*pnewint(0);intb1;constintcb;*p23;strings(sssss);s[0]x;//左值引用给左值去别名intr1b;int*r2p;intr3*p;//右值引用给右值取别名intrr110;intrr2b1;stringrrsstring(1111);//左值引用引用右值conststringrrwstring(22222);//右值引用引用左值intrr4move(b);return0;}3.2 引用延长生命周期右值引因可用于为临时对象延长生命周期const 的左值引用也能延长临时对象生存期但这些对象无法被修改。代码演示#includeiostream#includestringusingnamespacestd;intmain(){string stest;//ss不能被修改,但是可以通过const延长生命周期conststringssss;stringss1ss;coutssendl;coutss1endl;ss1 c11;coutss1endl;return0;}3.3 右值与左值的参数匹配C98中我们实现一个const左值引用作为参数的函数那么实参传递左值和右值都可以匹配。C11以后分别重载左值引用、const左值引用、右值引用作为形参的f函数那么实参是左值会匹配f(左值引用)实参是const左值会匹配f(const 左值引用)实参是右值会匹配f(右值引用)。右值引用变量在用于表达式时属性是左值。代码演示#includeiostream#includestringusingnamespacestd;voidf(intx){cout左值引用重载f(x)\n;}voidf(constintx){coutconst的左值引用重载f(x)\n;}voidf(intx){cout右值引用重载f(x)\n;}intmain(){inti0;constintc1;intii2;f(i);f(c);f(move(i));f(i1);//ii虽然是右值但是右值引用本身的属性是左值所以打印结果是左值引用f(ii);f(move(ii));return0;}打印结果:3.4 右值引用和引动语义的使用场景3.41、移动构造和移动赋值移动构造函数是⼀种构造函数类似拷贝构造函数移动构造函数要求第⼀个参数是该类类型的引用但是不同的是要求这个参数是右值引用如果还有其他参数额外的参数必须有缺省值。移动赋值是⼀个赋值运算符的重载他跟拷贝赋值构成函数重载类似拷贝赋值函数移动赋值函数要求第一个参数是该类类型的引用但是不同的是要求这个参数是右值引用。对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类移动构造和移动赋值才有意义因为移动构造和移动赋值的第⼀个参数都是右值引用的类型他的本质是要“窃取”引用的右值对象的资源而不是像拷贝构造和拷贝赋值那样去靠欸资源从提高效率。对于那些深拷贝代价很大的我们一般都会选择移动构造和移动赋值从而减少开销代码演示#includeiostream#includeassert.h#includestring.h#includealgorithmusingnamespacestd;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){coutstring 构造endl;_strnewchar[_capacity1];strcpy(_str,str);}voidswap(strings){::swap(_str,s._str);::swap(_size,s._size);::swap(_capacity,s._capacity);}string(conststrings):_str(nullptr),_size(0),_capacity(0){coutstring 拷贝构造endl;_capacitys._capacity;_sizes._size;_strnewchar[_capacity1];strcpy(_str,s._str);}//移动构造string(strings)noexcept{coutstring 移动构造endl;swap(s);}stringoperator(conststrings){coutstring 拷贝赋值endl;if(this!s){delete[]_str;_sizes._size;_capacitys._capacity;_strnewchar[s._capacity1];strcpy(_str,s._str);}return*this;}//移动赋值stringoperator(strings)noexcept{coutstring 移动赋值endl;swap(s);return*this;}~string(){cout析构endl;delete[]_str;_strnullptr;}charoperator[](size_t pos){assert(pos_size);return_str[pos];}voidreserve(size_t n){if(n_capacity){char*tmpnewchar[n1];if(_str){strcpy(tmp,_str);delete[]_str;}_strtmp;_capacityn;}}voidpush_back(charch){if(_size_capacity){size_t newcapacity_capacity0?4:_capacity*2;reserve(newcapacity);}_str[_size]ch;_size;_str[_size]\0;}stringoperator(charch){push_back(ch);return*this;}private:char*_str;size_t _capacity;size_t _size;};}intmain(){Move::strings1(xxxxx);// 拷贝构造Move::string s2s1;// 构造移动构造优化后直接构造Move::string s3Move::string(yyyyy);// 移动构造Move::string s4move(s1);cout******************************endl;return0;}3.42右值引用和移动语义解决传值返回问题代码演示#includeiostream#includealgorithm#includestringusingnamespacestd;namespacealgorithm{stringaddStrings(string num1,string num2){string str;intend1num1.size()-1;intend2num2.size()-1;intnext0;while(end10||end20){intval1end10?num1[end1--]-0:0;intval2end20?num2[end2--]-0:0;intretval1val2next;nextret/10;retret%10;str(0ret);}if(next1){str1;}reverse(str.begin(),str.end());returnstr;}}intmain(){string retalgorithm::addStrings(11111,2222);coutret.c_str()endl;return0;}// 场景2//int main()//{// string ret;// ret algorithm::addStrings(11111, 2222);// cout ret.c_str() endl;// return 0;//}不同编译器的处理方式不同可能是拷贝构造优化之后直接一次构造也可能是移动构造优化之后变成移动赋值3.43 右值引用和移动语义在传参中的提效查看STL文档我们发现C11以后容器的push和insert系列的接口否增加的右值引用版本当实参是一个左值时容器内部继续调用拷贝构造进行拷贝将对象拷贝到容器空间中的对象当实参是一个右值容器内部则调用移动构造右值对象的资源到容器空间的对象上
返回列表