C++11详解(上)

devtools/2025/3/5 6:16:32/

目录

C++11发展历史

列表初始化

C++11中的std::initializer_list

右值引用和移动语义

左值和右值

左值引用和右值引用

引用延长生命周期

左值和右值的参数匹配

移动构造和移动赋值

返回问题

优化过程

引用折叠

完美转发

应用

可变参数模板

基本语法和原理

包扩展

empalce系列接口

新的类功能

默认的移动构造和移动赋值

成员变量声明时给缺省值

defult和delete

final与override


在了解C++11之前,我们先了解C++的历史!

C++11发展历史

C++11是C++的第⼆个主要版本,并且是从C++98起的最重要更新。它引⼊了⼤量更改,标准化了既 有实践,并改进了对C++程序员可⽤的抽象。在它最终由ISO在2011年8⽉12⽇采纳前,⼈们曾使 ⽤名称“C++0x”,因为它曾被期待在2010年之前发布。C++03与C++11期间花了8年时间,故⽽这 是迄今为⽌最⻓的版本间隔。从那时起,C++有规律地每3年更新⼀次。

列表初始化

C++98传统的{ }初始化

C++98中⼀般数组和结构体可以⽤{}进⾏初始化。

图:

C++11中的{ }初始化

  • C++11以后想统⼀初始化⽅式,试图实现⼀切对象皆可⽤{}初始化,{}初始化也叫做列表初始化。
  • 内置类型⽀持,⾃定义类型也⽀持,⾃定义类型本质是类型转换,中间会产⽣临时对象,最后优化 了以后变成直接构造。
  • {}初始化的过程中,可以省略掉=
  • C++11列表初始化的本意是想实现⼀个⼤统⼀的初始化⽅式,其次他在有些场景下带来的不少便利,如容器push/inset多参数构造的对象时,{}初始化会很⽅便

如:

C++11中的std::initializer_list

  • 上⾯的初始化已经很⽅便,但是对象容器初始化还是不太⽅便,⽐如⼀个vector对象,我想⽤N个 值去构造初始化,那么我们得实现很多个构造函数才能⽀持, vector v1 = {1,2,3};
  • C++11库中提出了⼀个std::initializer_list的类,这个类的本质是底层开⼀个数组,将数据拷⻉ 过来,std::initializer_list内部有两个指针分别指向数组的开始和结束。std::initializer_list⽀持迭代器遍历。
  • 容器⽀持⼀个std::initializer_list的构造函数,也就⽀持任意多个值构成的 初始化。STL中的容器⽀持任意多个值构成的 {x1,x2,x3...} 进⾏ {x1,x2,x3...} 进⾏初始化,就是通过 std::initializer_list的构造函数⽀持的。

右值引用和移动语义

C++98的C++语法中就有引⽤的语法,⽽C++11中新增了的右值引⽤语法特性,C++11之后我们之前学 习的引⽤就叫做左值引⽤。⽆论左值引⽤还是右值引⽤,都是给对象取别名。

左值和右值

  • 左值是⼀个表⽰数据的表达式(如变量名或解引⽤的指针),⼀般是有持久状态,存储在内存中,我 们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const 修饰符后的左值,不能给他赋值,但是可以取它的地址。
  • 右值也是⼀个表⽰数据的表达式,要么是字⾯值常量、要么是表达式求值过程中创建的临时对象 等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。

左值引用和右值引用

  • Type& r1 = x; Type&& rr1 = y; 第⼀个语句就是左值引⽤,左值引⽤就是给左值取别 名,第⼆个就是右值引⽤,同样的道理,右值引⽤就是给右值取别名。
  • 左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值
  • 右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)。
  • move是库里面的⼀个函数模板,本质内部是进行强制类型转换,当然他还涉及⼀些引用折叠的知识。
  • 语法层⾯看,左值引⽤和右值引⽤都是取别名,不开空间。从汇编底层的⻆度看下⾯代码中r1和rr1 汇编层实现,底层都是⽤指针实现的,没什么区别。

需要注意的是:rrx1,rrx2,rrx3,rrx4,rrx5变量都是左值,不是右值,它们开辟了空间去存储相对应的右值!

引用延长生命周期

右值引⽤可⽤于为临时对象延⻓⽣命周期,const的左值引⽤也能延⻓临时对象⽣存期,但这些对象⽆法被修改

因为本来这些临时对象在运行完所在行后,就没了,找不到了,而引用可将生命周期扩大到相应的作用域!

左值和右值的参数匹配

  • C++98中,我们实现⼀个const左值引⽤作为参数的函数,那么实参传递左值和右值都可以匹配。
  • C++11以后,分别重载左值引⽤、const左值引⽤、右值引⽤作为形参的f函数,那么实参是左值会 匹配f(左值引⽤),实参是const左值会匹配f(const左值引⽤),实参是右值会匹配f(右值引⽤)。

有这么几个函数:

调用情况:

移动构造和移动赋值

  • 移动构造函数是⼀种构造函数,类似拷⻉构造函数,移动构造函数要求第⼀个参数是该类类型的引 ⽤,但是不同的是要求这个参数是右值引⽤,如果还有其他参数,额外的参数必须有缺省值。
  • 移动赋值是⼀个赋值运算符的重载,他跟拷⻉赋值构成函数重载,类似拷⻉赋值函数,移动赋值函 数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤。
  • 对于像string/vector这样的深拷⻉的类或者包含深拷⻉的成员变量的类,移动构造和移动赋值才有 意义,因为移动构造和移动赋值的第⼀个参数都是右值引⽤的类型,他的本质是要“窃取”引⽤的 右值对象的资源,⽽不是像拷⻉构造和拷⻉赋值那样去拷⻉资源,从提⾼效率。

下面我将举几个例子看清构造,拷贝构造,移动构造,移动赋值的深层调用逻辑!

有了移动构造和移动赋值,会高效很多,再加上编译器自己会优化构造过程,来达到高效率!

我们将会在linux(优化前)中和vs2022(优化后)中进行比较!

在linux中,运行一个C++文件步骤(假设有一个test.cpp文件):

      1.编译C++文件,使用g++编译器编译你的C++源文件,并指定你希望使用的C++标准。例如,如果你希望使用C++11标准,可以使用-std=c++11选项。类似地,你可以使用-std=c++14-std=c++17-std=c++20来指定其他标准。

指令:(以C++11为例)(优化了)

g++ -std=c++11 -o test test.cpp

    指令:(没有优化

    g++ -fno-elide-constructors test.cpp -o test -std=c++11

             2.运行编译后的可执行文件

    指令:

    ./test

    下面,我们将使用我们自制的string类进行演示:

    这里只演示主要函数:

    讲解:

    优化前:

    如果既有拷贝构造也有移动构造,那么当然走移动构造(走最优的),如果没有移动构造,走拷贝构造!

    注意:string("11111")和move(左值),虽然都是右值,但是还是不一样!

    优化后:

    当然也有优化不了的,因为已经是最高效了的了!

    返回问题

    这里有一个函数:

    有这样一行代码:

    当没有移动构造,只有拷贝构造时:(优化前)

    验证:

    当有移动构造,也有拷贝构造时:(优化前)调用最合适的函数!

    调用拷贝构造的地方,调用了拷贝构造:

    优化过程

    没有移动构造:

    有移动构造:

    在优化的第二过程,在addString只有一个构造过程,str和ret指向同一块空间,只不过出了addString作用域,str就被销毁了,但是空间没被销毁!

    优化检测:

    验证:

    我们继续看一个示例:

    有这样几行代码:

    没有移动构造和移动赋值:(优化前)

    验证:

    没有移动构造和移动赋值:(优化后)

    有移动构造和赋值构造:(优化前)

    在调用了拷贝构造的地方,调用了移动构造或者移动赋值:

    有移动构造和赋值构造:(优化后)

    引用折叠

    • C++中不能直接定义引⽤的引⽤如 int& && r = i; ,这样写会直接报错,通过模板或typedef 中的类型操作可以构成引⽤的引⽤。
    • 通过模板或typedef中的类型操作可以构成引⽤的引⽤时,这时C++11给出了⼀个引⽤折叠的规 则:右值引⽤的右值引⽤折叠成右值引⽤,所有其他组合均折叠成左值引⽤。
    • 像f2这样的函数模板中,T&&x参数看起来是右值引⽤参数,但是由于引⽤折叠的规则,他传递左 值时就是左值引⽤,传递右值时就是右值引⽤,有些地⽅也把这种函数模板的参数叫做万能引⽤。

    有这么两个函数:

    继续看,有这么个函数:

    看推导:

    完美转发

    • Function(T&&t)函数模板程序中,传左值实例化以后是左值引⽤的Function函数,传右值实例化 以后是右值引⽤的Function函数。
    • 变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定 后,右值引⽤变量表达式的属性是左值,也就是说Function函数中t的属性是左值,那么我们把t传 递给下⼀层函数Fun,那么匹配的都是左值引⽤版本的Fun函数。这⾥我们想要保持t对象的属性, 就需要使⽤完美转发实现。

    下面我们来看看应用场景:

    我们调用Function函数:

    Fun(t)语句时:

    因为不管T被推导成什么,形参 t都是左值!

    Fun(forward<T>(t))语句时:

    它会延续T类型!会将原来的数据类型传递下去!

    应用

    所以我们可以提供右值版本:

    可以继续改善:使用万能引用

    可变参数模板

    基本语法和原理

    • C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称 为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函 数参数。
    • 我们⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class...或 typename...指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟...指出 接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板 ⼀样,每个参数实例化时遵循引⽤折叠规则。
    • 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
    • 这⾥我们可以使⽤sizeof...运算符去计算参数包中参数的个数。

    我们来看以下原理:

    包扩展

    • 对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个 包时,我们还要提供⽤于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元 素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操作。底层 的实现细节如图1所⽰。
    • C++还⽀持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数去处理。

    同样的:

    但是我们可以这样遍历:

     //包扩展(解析出参数包的内容)
    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...);
    }

    也可以这样:

    template <class T>
    int GetArg(const T& x)
    {cout << x << " ";return 0;
    }
    template <class ...Args>
    void Arguments(Args... args)
    {}template <class ...Args>
    void Print(Args... args)
    {// 注意GetArg必须返回或者到的对象,这样才能组成参数包给ArgumentsArguments(GetArg(args)...);
    }

    empalce系列接口

    • C++11以后STL容器新增了empalce系列的接⼝,empalce系列的接⼝均为模板可变参数,功能上 兼容push和insert系列,但是empalce还⽀持新玩法,假设容器为container,empalce还⽀持 直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。
    • emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列

    我们来看看push_back和emplace的区别:

    看看pair类型:

    新的类功能

    默认的移动构造和移动赋值

    • 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷⻉构造函数/拷⻉赋值重载/取地址重 载/const 取地址重载,最后重要的是前4个,后两个⽤处不⼤,默认成员函数就是我们不写编译器 会⽣成⼀个默认的。C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
    • 如果你没有⾃⼰实现移动构造函数,且没有实现析构函数、拷⻉构造、拷⻉赋值重载中的任意⼀ 个。那么编译器会⾃动⽣成⼀个默认移动构造。默认⽣成的移动构造函数,对于内置类型成员会执 ⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调⽤ 移动构造,没有实现就调⽤拷⻉构造。
    • 如果你没有⾃⼰实现移动赋值重载函数,且没有实现析构函数、拷⻉构造、拷⻉赋值重载中的任意 ⼀个,那么编译器会⾃动⽣成⼀个默认移动赋值。默认⽣成的移动构造函数,对于内置类型成员会 执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调 ⽤移动赋值,没有实现就调⽤拷⻉赋值。(默认移动赋值跟上⾯移动构造完全类似)。
    • 如果你提供了移动构造或者移动赋值,编译器不会⾃动提供拷⻉构造和拷⻉赋值。

    成员变量声明时给缺省值

    defult和delete

    • C++11可以让你更好的控制要使⽤的默认函数。假设你要使⽤某个默认的函数,但是因为⼀些原因 这个函数没有默认⽣成。⽐如:我们提供了拷⻉构造,就不会⽣成移动构造了,那么我们可以使⽤ default关键字显⽰指定移动构造⽣成。

    • 如果能想要限制某些默认函数的⽣成,在C++98中,是该函数设置成private,并且只声明补丁已, 这样只要其他⼈想要调⽤就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语 法指⽰编译器不⽣成对应函数的默认版本,称=delete修饰的函数为删除函数。

    final与override

    在继承和多态中应用,注意跳转我其他文章!

    由于C++11篇幅过长,我们下期见!


    http://www.ppmy.cn/devtools/164668.html

    相关文章

    android13打基础: 接收自定义广播并在接收到广播时触发设备震动

    receiver的编写 public class ShockReceiver extends BroadcastReceiver {// 静态注册时候的action、发送广播时的action、接收广播时的action&#xff0c;三者需要保持一致public static final String SHOCK_ACTION "com.example.myapplication.android_tut.shock"…

    某小说网站爬虫

    今晚上一篇小说网站给我干难受了&#xff0c;先是五秒盾&#xff0c;还有页面page参数的不规则 直接请求 首先肯定是直接请求 直接请求的代码 import requestsurl"https://beqege.cc/2/21.html" headers{User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) …

    增删改查 数据下载 一键编辑 删除

    index 首页 <template><div class"box"><el-card :style"{ width: treeButton ? 19.5% : 35px, position: relative, transition: 1s }"><el-tree v-if"treeButton" :data"treeData" :props"defaultPro…

    【Canny 边缘检测详细讲解】

    Canny 边缘检测详细讲解 目录 Canny 边缘检测详细讲解一. Canny 边缘检测的基本原理二. 在 MATLAB 中实现 Canny 边缘检测三. 运行结果展示四. 关键参数解释五. 实验与验证六. 总结 Canny 边缘检测是一种经典的图像处理算法&#xff0c;广泛应用于计算机视觉领域。它通过多步骤…

    Gartner发布安全运营指标构建指南

    如何为安全运营指标构建坚实的基础 安全运营经理需要报告威胁检测、调查和响应计划的有效性&#xff0c;但难以驾驭大量潜在的 SOC 指标。本研究提供了设计针对 SOC 的指标系统的示例和实践。 主要发现 需要清晰、一致的衡量标准来向董事会成员或服务提供商等更广泛的团队传达…

    Stable Diffusion 反向提示词(Negative Prompt)深度解析

    Stable Diffusion 反向提示词深度解析&#xff08;2025最新版&#xff09; 一、核心定义与作用 反向提示词&#xff08;Negative Prompt&#xff09;是用于排除生成图像中特定内容或特征的指令集。通过明确告知模型不应出现的元素&#xff0c;反向提示词可有效解决以下三大问…

    MySQL表连接详解

    MySQL表连接详解 在 MySQL 中&#xff0c;表连接&#xff08;Join&#xff09;用于将多个表中的数据组合在一起&#xff0c;基于它们之间的关系进行查询。常见的表连接类型包括内连接、左连接、右连接和全外连接。以下是这些连接类型的详细说明&#xff1a; 1. 内连接&#x…

    什么是线程安全?并行计算

    当一个库声称自己“不是线程安全的”&#xff08;not thread-safe&#xff09;&#xff0c;意思是它在多线程环境下使用时&#xff0c;可能会出现数据竞争&#xff08;data race&#xff09;、未定义行为&#xff08;undefined behavior&#xff09;或不一致的结果。线程安全&a…