资讯动态

C++11的重要特性

发布时间:2026/9/28 5:30:01 来源:尧图企业网站定制
列表初始化C98的{}用法C11以后想统一初始化方式试图实现一切对象皆可用初始化{}初始化也叫做列表初始化类型分为内置类型和自定义类型均可用{}初始化。内置类型比较简单我们给示例时再做解释自定义类型本质上是先隐式类型转换再拷贝构造被编译器优化为直接构造。此外用{}初始化时可以省略接下来登场的是初始化列表上述初始化均为类型但是还没有容器初始化因此有了初始化列表initializer_listC11库中提出了一个std:initializer_list的类。这个类的本质是底层开一个数组将数据拷贝过来std::initializer_list内部有两个指针分别指向数组的开始和结束初始化列表初始化我们之前就使用过本质上是重载了构造函数将std:initializer_list作为形参利用迭代器将std:initializer_list内的数据一个一个push入容器//思考这三句代码的语义 vectorint v1({ 1,2,3,4,5 }); vectorint v2 { 1,2,3,4,5 }; const vectorint v3 { 1,2,3,4,5 };我个人不太喜欢{}初始化感觉乱乱的右值移动和移动语义我们之前使用的引用是左值引用而C11新增的是右值引用他们两个都可以取别名但右值引用仅仅只是取别名那还增加他干什么。我们先了解什么是左值什么是右值左值是可以去地址的值比如int、类对象等右值是无法取地址的值比如匿名对象、临时对象、字符值常量、存储于寄存器中的变量等右值不能赋值所以只能出现在赋值符号右侧左值的英文简写为lvalue右值的英文简写为rvalue。传统认为它们分别是left value、right value的缩写。现代C中lvalue被解释为loactor value的缩写可意为存储在内存中、有明确存储地址可以取地址的对象而rvalue被解释为read value指的是那些可以提供数据值但是不可以寻址。也就是说左值和右值的核心区别就是能否取地址而不是能否修改左值引用不能直接引用右值但是const左值引用可以引用右值右值引用不能直接引用左值但是右值引用可以引用move左值move可以将左值转化为右值本质上内部是强制类型转换变量表达式都是左值属性也就意味着一个右值被右值引用绑定后右值引用变量变量表达式的属性是左值。这个是给后面铺垫的只看理论的话我们看不出来什么用处此外右值引用可以延长生命周期因为临时对象、匿名对象作用域都只在当前那一行右值引用后就变为此引用的生命周期之前我们介绍的函数const引用和引用的重载它的逻辑是变量走引用那个重载常量走const引用的重载版本函数调用走最适配的那么C11以后常量去左值引用const 左值走const 左值引用右值走右值引用。const 右值引用我们基本舍弃没什么使用价值等示例时讲解左值引用用途是在函数中左值引用传参和左值引用传返回值时减少拷贝同时还可以修改实参和修改返回对象的价值。左值引用已经解决大多数场景的拷贝效率问题但是有些场景不能使用传左值引用返回那就是传返回值时返回值是局部变量左值引用的话就会出现“野引用”右值引用能解决吗也不可以因为这个局部变量如果是类对象的话函数结束就直接析构了右值引用也没办法那怎么办移动构造和移动赋值C11以后有了一个新概念八个默认成员函数新增的两个就是移动构造和移动赋值移动构造函数是一种构造函数类似拷贝构造函数移动构造函数要求第一个参数是该类类型的右值引用如果还有其他参数额外的参数必须有缺省值保证是默认可以调用的函数移动赋值是赋值运算符的重载他跟拷贝赋值构成函数重载类似拷贝赋值函数移动赋值函数要求第一个参数是该类类型的右值引用对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类移动构造和移动赋值才能体现价值因为移动构造和移动赋值的第一个参数都是右值引用的类型他的本质是要“窃取”引用的右值对象的资源不需要自己再开辟空间而是将右值开辟过的空间拿过来不让它析构了而不是无法用左值引用后只能用拷贝构造和拷贝赋值那样去拷贝资源提高了效率从移动构造和移动赋值的实现我们就可以知道效率非常高右值引用就是为了这些场景而产生的移动构造和移动赋值在传参的应用场景如C11以后容器的push和insert系列的新增接口当实参是一个左值时容器内部继续调用拷贝构造进行拷贝将对象拷贝到容器空间中的对象当实参是一个右值时容器内部则调用移动构造右值对象的资源到容器空间的对象上下面是我模拟实现的C11将类型分为了两大类一个是右值另一个是泛左值右值分为纯右值和将亡值纯右值和C98的右值概念等价字符值常量或求值结果相当于字符值常量或是一个不具名的临时对象如1、true、nullptr或传值返回函数调用又或者是a、ab等将亡值从字面即可理解即将死亡的值如move(左值)又或者是返回右值引用的函数的调用表达式。泛左值分为将亡值和左值。左值还按我们之前的定义即可。为什么将亡值也是左值因为将亡值是左值转化过来的无语...引用折叠如果我们直接写如int rra a编译器会报错。但是我们使用typedef int T然后用T rr a编译器不会报错。或者用类模板传int给class T在类中使用T也是可以的。这时构成了引用的引用就是引用折叠C11给出了类型引用的规则右值引用的右值引用是右值引用其他如右值引用的左值引用都是左值引用从第二个示例可以看出Function函数既可以实现左值引用也可以实现右值引用因此我们可以将合并为但是如果想实现push_back函数自动识别类型它就不能和类模板同一模板参数不然我们初始化类对象时就确定了类模板参数push_back函数就不能自动识别类型了同理insert函数也可以这样大家可以自己实现一下t永远都是左值无论是左值引用还是右值引用但是上述代码我们统一将参数u转化为右值move(u)会无差别将实参对象释放如果不使用move那么t是左值如果是右值引用也就是右值传过来的就无法做到移动构造那般提高效率。C11引入完美转发可以解决此问题完美转发简单来说完美转发就是使该变量保留原来的属性原来是左值就还是左值原来是右值就还是右值完美转发forward本质是一个函数模板他主要还是通过引用折叠的方式实现上面示例中传递给push_back的实参是右值T被推导为string没有折叠forward内部u被强转为右值引用返回传递给push_back的实参是左值U被推导为string引用折叠为左值引用forward内部u被强转为左值引用返回template class _Ty _Ty forward(remove_reference_t_Ty _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue return static_cast_Ty(_Arg); }可变参数模板即可变数量参数的函数模板和类模板而这些参数被称为参数包分为两种分别是模板参数包——零或多个模板参数和函数参数包——零或多个函数参数图片中...Args就表示一个参数包class...或者typename...均可。参数包中可以有零或多个参数。此外函数参数包支持引用重叠每一个参数都会推导自己的类型跟前面的普通参数模板用法一致可变参数模板的原理跟模板类似本质还是去实例化对应类型和个数的函数参数包可以通过sizeof...计算参数包中的参数个数参数包让之前的类型可变到现在的类型、参数数量均可变对参数包操作我们只能计算它的大小和将它进行包扩展目的是利用所传的实参去实现所要的函数功能具体用法如下这三个省略号的位置很乱但是没办法语法就是这样emplace系列接口这是我们之前STL中容器遇到但没有介绍的它用到了可变参数模板我们在这里才能介绍对push和insert函数来说emplace可以实现它们的功能但可以在有些情况提升效率直接构造。因此emplace系列接口是更高效的以后可以用emplace替代push和insertNode的构造函数也需要重载一个可变参数的版本这幅图有细节一是重载了移动构造二是默认构造函数是移动构造版本三是可变参数没有像之前那样递归拆解参数包这是运行时展开参数包而是可变参数包编译时就全部展开作为_data的构造函数参数编译时展开我们前面介绍了C新增的两个默认成员函数——移动构造函数和移动赋值函数编译器自动生成的移动构造函数的条件是我们没有写移动构造函数以及我们没有写析构函数、拷贝构造、拷贝赋值重载中的任意⼀ 个。前者自然不用说后者是因为这三个若有深拷贝的需求都需要手动实现因此移动构造也需要手动实现。其次若此类的成员变量是自定义成员变量是否实现移动构造取决于它是否实现移动构造移动赋值与移动构造条件一致不再赘述default和delete为了更好的控制成员函数C出了default和delete。我们前面就介绍过default若想一个默认成员函数强制编译器自己生成就可以使用default它本来不会生成我们强制让它生成才有意义。由于满足条件以后编译器会自己生成默认成员函数C98我们是拿个萝卜占坑位函数写成空什么也不执行并设置成privateC11以后我们可以用delete称为删除函数格式与default一致这样编译器就不会自己生成了外面也调用不了了有几条特性是C11以后才有但是我们前面就介绍了如成员变量给缺省值、final和override等。不用那么在意毕竟C11也是很早之前了出了这么多特性后STL也发生了很多变化如unordered_map和unordered_set我们刚才介绍的emplace系列接口移动构造和移动赋值初始化列表构造等。以及范围for遍历lamdalamda表达式本质上是一个匿名函数对象。lamda表达式在语法上其具体类型我们无法知道我们一般是通过auto或模板参数定义的对象去接受它在接受这一语法之前我们潜意识里要认为它是一个特殊的函数形式要当函数来用这样才容易接受格式[capture_list](parameters)-return type{函数体}[capture_list]是捕捉列表用来捕获上下文的变量为己用即使为空[]也不可以省略(参数列表)与普通函数的参数列表类似不过lamda表达式若没有参数可以省略返回类型与普通函数的返回类型一致⼀般返回值类型明确情况下可以省略让编译器自己推导函数体与普通函数也类似但不可省略可以用捕捉列表的变量上面是捕捉列表的简单认识下面我们再细讲捕捉列表可以传值捕捉和传引用捕捉看起来会引起歧义示例见。第一种捕捉方式是捕捉哪个变量就显示写哪个变量第二种捕捉方式是隐式捕捉[]中写一个外界变量全传值捕捉函数体中用到哪些变量就去捕捉哪些[]中写一个外界变量全传引用捕捉第三种是混合使用[]中写一个外界变量全传值捕捉若我们想要某些变量是传引用捕捉[]写“变量名”全传引用捕捉同理也可以这样若lamda表达式写在全局域静态变量和全局变量不需要捕获也不可捕获此时[]什么也不能写了默认情况下传值捕捉是const修饰的不可修改若想修改得用mutable修饰参数列表但此时也是形参改变不影响实参。使用mutable后参数列表就不可省略了除了lamda还有两个函数调用方式——仿函数和函数指针。相比来说lamda表达式简单有好用因此lamda表达式在很多场景都有应用我们以后会不断接触到lamda表达式和范围for一样都是语法糖本质都是借助原有知识实现如范围for底层是迭代器lamda表达式底层是仿函数类捕捉列表是它的成员变量捕获变量是去完成构造函数。隐式捕捉是编译器看用到哪些变量去传哪些变量。lamda表达式的函数体/参数/返回类型就是operator()的函数体/参数/返回类型包装器functionfunction既是一个类模板也是一个包装器。它实例化出的对象可以包装存储其他可调用对象如lamda表达式、仿函数、函数指针以及即将介绍的bind等。它被定义在在functional头文件中也就是使用它我们需要包含头文件functional存储的可调用对象被称为function的目标。这些可调用对象的类型不同但包装器可以统一类型实现包装方便我们调用普通函数/静态成员函数std:::function内部缓冲区保存函数裸地址直接跳转调用不存在this非静态成员函数缓冲区保存成员函数指针不一定是单纯一个地址因为成员函数指针不一定是简单一个地址调用必须提供对象指针或类对象需要this来调用成员函数首个参数若为指针直接使用传入对象做this首个参数若为类对象先构造临时对象拿临时对象做this调用成员函数bind是一个类模板也是一个包装器。bind的一般形式auto newCallable bind(callable,arg_list);对接收的可调用对象进行处理后返回一个可调用对象相当于做一些加工如调整参数个数和参数顺序。newCallable是新的可调用对象callable是我们想包装的可调用对象arg_list是传给函数的参数列表加工就是在这里操作。我们可以传固定的值锁死某些形参用类似_n的参数按位置顺序接收实参然后按数字顺序传给形参

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑