C++11(上)

article/2025/8/4 10:04:02

历史:

在C++98版本后,C++11是一次大版本的更新。在C++11中新增了许多有用的东西。接下来将由小编来带领大家介绍C++11中新增的内容。

列表初始化:

在C++中,列表初始化(也称为统一初始化或花括号初始化)是一种使用花括号 `{}` 来初始化对象的语法。

在C++98时,花括号初始化一般用于数组或者结构体初始化。

而在C++11中,提出了万物皆可”{}”初始化的概念。不仅内置类型支持列表初始化,自定义类型也同样支持。

initializer_list

std::initializer_list 是 C++11 引入的一个轻量级模板类(定义在 <initializer_list> 头文件中),用于表示由相同类型元素组成的编译期常量数组。它是实现列表初始化({} 初始化)的核心机制,也就是说其实initializer_list就是”{}”,再更简单理解的话initializer_list就是一个数组,只是通过类模板编译后的数组,后续再根据需求实例化出相应类型的数组。

为什么要有initializer_list

我们可以看到initializer_list是支持迭代器,支持迭代器就意味着支持迭代器构造。

并且initializer_list支持多个值进行初始化,而我们STL大多数容器都支持多个值初始化,也是通过先创建initializer_list对象,再通过initializer_list对象的迭代器对其他容器进行初始化值操作。

右值引用与移动语义

左值引用:

我们之前常见的引用,基本都可以称之为左值引用,左值引用的基本都是引用一个变量。

其特点为:

  1. 有明确的身份
  2. 生命周期长
  3. 能够被取地址
  4. 可以多次引用

右值引用:

右值引用,引用的都是右值,右值是具有常量属性的值,并且不能被取地址,类似于被const修饰过。而具有右值属性的基本为一个函数表达式,或变量表达式。但是这里有一个特殊的点,右值引用后的变量是一个具有左值属性的值,这点可能现在看起来会有些绕,但是为了后面的准备这个是必要的。

从语法层来说,无论是左值引用还是右值引用,都是给对象取别名,而给对象取别名是不开空间的。并且区分一个值是左值还是右值,就看它能否取地址。

C++规定左值引用是不能直接引用右值(具有const属性),但是const 左值引用是可以引用右值的。而右值也不能直接引用左值,必须通过move函数,将左值强转成右值才能引用。

引用对对象生命周期的延长:

对于临时变量的生命周期,通常来说只存在当前行。Const 左值引用可以延长临时变量的生命周期,但无法做到修改。而右值引用可以做到这一点。

左值与右值的参数匹配

左值引用可以作为函数参数进行传递,同样的右值引用也可以,只不过在左值与右值在传递的过程中存在些许差异。

如上图,函数f完成了函数重载,暂时屏蔽了右值引用作为参数后。可以看到i,调用的是左值引用,ci是const左值引用,3因为是一个常量所以也是const 左值引用,当move了i后,i属性变为右值,但const 左值引用以及可以引用右值,所以程序能正常运行。

而可能会有点疑问的是为什么x是左值引用。不要忘记之前说的,当一个右值引用的变量绑定了一个右值后,该变量的属性依旧为左值,所以编译器这里把x识别为左值,调用的就是左值引用的f函数。

右值引⽤和移动语义的使⽤场景

左值引用主要使用场景主要用于函数中左值引用传参或传返回值,但无论哪种情况,其目的都是为了减少不必要的拷贝,同时还可以修改实参和修改返回对象的价值。可以说左值引用解决了大多场景中拷贝效率的问题。但在有些情况中,左值引用并不能很好的解决。

上图的函数最后都需要传值返回。第一段代码还好,仅仅只是返回一个string,而第二段代码需要返回的是vector的vector,传值返回就需要创建相同的临时变量,通过临时变量在进行拷贝构造,代价是非常大的。而右值引用也不能直接引用上图的str或vv。因为他们本质上还是一个局部对象。当函数结束时,栈帧就销毁此时就会调用他们的析构函数。



移动构造与移动赋值:

移动构造和移动赋值与拷贝构造和拷贝赋值有着本质区别。

拷贝操作的本质: 创建对象的完整副本。对于管理动态资源(如 vectorstring 或自定义包含指针的类)的对象,这意味着需要分配新内存复制所有数据(深拷贝)。性能开销可能很大,尤其当对象很大或资源层次很深时。

移动操作的本质: 转移资源的所有权,而非复制。其核心在于“窃取” 源对象的资源(如内部指针指向的内存),将其转移到目标对象,并使源对象处于有效但未指定的状态(通常是“空”状态)。移动操作通常仅涉及少量指针的交换或赋值,无需分配新内存或复制数据,因此效率极高

正如上图,如果是深拷贝的话,那么拷贝构造将会及其麻烦。而移动构造不会,ret移动构造给临时变量时,因为临时变量什么都没有,把两个指针交换,就变成ret1里没内容,而临时变量会有ret1里的内容,代价是非常小的。

如上图,通过移动构造S4直接掠夺S1的资源,因为S4本身就为NULL,所以掠夺完S1就为空了,而因为S1已经是右值,再掠夺完后,会直接调用析构函数来析构S1。

001

002

003

004

005

006

007

008

009

010

011

012

013

014

015

016

017

018

019

020

021

022

023

024

025

026

027

028

029

030

031

032

033

034

035

036

037

038

039

040

041

042

043

044

045

046

047

048

049

050

051

052

053

054

055

056

057

058

059

060

061

062

063

064

065

066

067

068

069

070

071

072

073

074

075

076

077

078

079

080

081

082

083

084

085

086

087

088

089

090

091

092

093

094

095

096

097

098

099

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>

#include<assert.h>

#include<string.h>

#include<algorithm>

using namespace std;

namespace cat

{

class string

{

public:

    typedef char* iterator;

    typedef const char* const_iterator;

    iterator begin()

    {

        return _str;

    }

 

    iterator end()

    {

        return _str + _size;

    }

 

    const_iterator begin() const

    {

        return _str;

    }

 

    const_iterator end() const

    {

        return _str + _size;

    }

 

    string(const char* str = "")

        :_size(strlen(str))

        , _capacity(_size)

    {

        cout << "string(char* str)-构造" << endl;

        _str = new char[_capacity + 1];

        strcpy(_str, str);

    }

 

    void swap(string& s)

    {

        ::swap(_str, s._str);

        ::swap(_size, s._size);

        ::swap(_capacity, s._capacity);

    }

 

    string(const string& s)

    :_str(nullptr)

    {

        cout << "string(const string& s) -- 拷⻉构造" << endl;

 

        reserve(s._capacity);

        for (auto ch : s)

        {

        push_back(ch);

        }

    }

 

    // 移动构造

    string(string&& s)

    {

        cout << "string(string&& s) -- 移动构造" << endl;

        swap(s);

    }

 

    string& operator=(const string& s)

    {

        cout << "string& operator=(const string& s) -- 拷⻉赋值" <<

        endl;

        if (this != &s)

        {

            _str[0] = '\0';

            _size = 0;

            reserve(s._capacity);

            for (auto ch : s)

            {

                push_back(ch);

            }

        }

        return *this;

    }

 

    // 移动赋值

    string& operator=(string&& s)

    {

        cout << "string& operator=(string&& s) -- 移动赋值" << endl;

        swap(s);

        return *this;

    }

 

    ~string()

    {

        cout << "~string() -- 析构" << endl;

        delete[] _str;

        _str = nullptr;

    }

 

    char& operator[](size_t pos)

    {

        assert(pos < _size);

        return _str[pos];

    }

 

    void reserve(size_t n)

    {

        if (n > _capacity)

        {

            char* tmp = new char[n + 1];

            if (_str)

            {

                strcpy(tmp, _str);

                delete[] _str;

            }

        _str = tmp;

        _capacity = n;

        }

    }

 

    void push_back(char ch)

    {

        if (_size >= _capacity)

        {

            size_t newcapacity = _capacity == 0 ? 4 : _capacity *2;

            reserve(newcapacity);

        }

        _str[_size] = ch;

        ++_size;

        _str[_size] = '\0';

    }

 

    string& operator+=(char ch)

    {

        push_back(ch);

        return *this;

    }

 

    const char* c_str() const

    {

        return _str;

    }

 

    size_t size() const

    {

        return _size;

    }

 

private:

    char* _str = nullptr;

    size_t _size = 0;

    size_t _capacity = 0;

};

 

 

    string addStrings(string num1, string num2)

    {

        string str;

        int end1 = num1.size() - 1, end2 = num2.size() - 1;

        int next = 0;

        while (end1 >= 0 || end2 >= 0)

        {

            int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;

            int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;

            int ret = val1 + val2 + next;

            next = ret / 10;

            ret = ret % 10;

            str += ('0' + ret);

        }

        if (next == 1)

        str += '1';

        reverse(str.begin(), str.end());

        cout << "******************************" << endl;

        return str;

    }

}

 

 

int main()

{

cat::string ret=cat::addStrings("11111", "2222");

cout << ret.c_str() << endl;

return 0;

}

为了体现右值引用的意义,上图代码是一个自我实现的string类,我们先把移动构造和移动赋值给注释后看上图代码。

当程序运行起来后可以看见,本应该是先拷贝构造临时变量,在拷贝构造ret,但在运行后却变成了直接构造。原因是编译器会对这种行为进行优化(具体优化各不相同,根据编译器及版本不同而定,C++委员会没有硬性规定)。编译器会对拷贝构造+拷贝构造,优化为直接构造。

因为编译器在这里觉得,反正最后的值是给ret,那为什么我不直接将str变为ret的别名,不就省去了拷贝构造的过程,反正结果是一样的。为了验证这一点,我们还可以打印他们的地址,他们的地址是相同的来加以验证。

因为编译器的优化行为是自定义的,我们如果使用老版本的编译器,可能就不会优化的这么厉害。在Linux中我们可以在编译时候加入 -fno-elide-constructors 这段命令,就可以让编译器不要进行优化。

使用不优化的编译运行后,结果跟我们预想是一模一样的,先进行拷贝构造给临时对象,再拷贝构造给ret。如果是深拷贝类型,这种行为就会显得极为麻烦。那么如果我们拥有移动构造和移动赋值来看呢

可以看到,当同时拥有移动构造与拷贝构造时,编译器就会自主选择移动构造完成。因为移动构造的代价够小,所以就算走两次移动构造也是非常快的。

引用折叠:

在 C++ 中,引用折叠(Reference Collapsing)是一组处理"引用的引用"的规则。

也就是说我们并不能直接对引用进行引用,但是通过类模板编译后就可以,对引用进行引用了,也就会产生引用折叠。而引用折叠也有自己的处理逻辑

当编译器遇到"引用的引用"时,会按以下规则折叠:  

& + & → &(左值引用)  

& + && → &(左值引用)  

&& + & → &(左值引用)  

&& + && → &&(右值引用)

从上图也可以发现规律,左值引用碰任何引用都会变成左值引用,只有右值引用碰右值引用其属性才是右值引用。

如上图示例,n是一个左值,0则是右值,

f1<int>(n);是没有任何问题。而f1<int>(0); 则会报错,因为0是一个右值,左值引用是不能直接引用一个右值。

f1<int&>(n);也是没有问题,因为左值引用碰左值引用,还是左值引用。f1<int&>(0); 因为碰撞后还是左值引用依旧报错

f1<int&&>(n);右值引用碰左值引用,会折叠成左值引用,没有问题。f1<int&&>(0);折叠还是左值引用,报错。

f1<const int&>(n);const 左值引用碰左值引用,会折叠成const左值引用,这是权限的缩小,没有问题。f1<const int&>(0);;const 左值引用可以引用右值也没有问题。

    f2<int>(n); 会报错,因为n是一个左值,而f2是一个右值引用,右值不能引用左值。    f2<int>(0);没有问题,0是右值,右值引用去引用右值。

f2<int&>(n);也没问题,因为左值引用碰右值引用,会折叠为左值引用。    f2<int&>(0);折叠后依旧是左值引用,所以报错。

f2<int&&>(n); 折叠后为右值引用,而n为左值,所以报错。f2<int&&>(0);折叠为右值引用去引用右值没问题。

完美转发:

当我们看见上图代码时,大家可能会想到通过引用折叠后调用不同发fun函数。但其实实际运行后除了调用左值引用的fun函数就是调用const 左值引用的fun函数。

因为我们要记得一个点,右值引用的属性是一个左值,所以t在Funciton 函数里是一个左值,而左值就会调用左值引用。但我们其实想的是,如果它是右值就调用右值引用,是左值就调用左值引用。

这里就要介绍完美转发。其实完美转发它也是强转,只是通过过滤,它是右值就强转成右值,是左值就强转成左值。

---------------------------------------------------本篇文章就到这里,感谢各位观看


http://www.hkcw.cn/article/EZXYXtRpMy.shtml

相关文章

从TCO角度分析IBM Cognos Analytics

一、总拥有成本&#xff08;TCO&#xff09;分析 像 Cognos Analytics 这样成熟的企业级 BI 平台&#xff0c;在与新兴的敏捷 BI 工具竞争中&#xff0c;依然能够保持其独特价值和竞争力的关键所在&#xff0c;尤其从企业和组织的长远发展、团队协作以及总拥有成本&#xff08…

使用西门子博图V16时遇到了搜索功能报错的问题,提示缺少SIMATIC Visualization Architect组件怎么办,全网首发

先上解决方案&#xff0c;这个太简单了&#xff0c;直接上官网下载&#xff0c;这个安装包40M&#xff0c;很快就下载完了&#xff0c;然后直接安装就可以了。 官网链接SIMATIC Visualization Architect V16 TRIAL Download - ID: 109772966 - Industry Support Siemens 今天我…

STM32G4 电机外设篇(三) TIM1 发波 和 ADC COMP DAC级联

目录 一、STM32G4 电机外设篇&#xff08;三&#xff09; TIM1 发波 和 ADC COMP DAC级联1 TIM1 高级定时器发波1.1 stm32cubemx配置 2 TIM1 ADC COMP DAC级联2.1 stm32cubemx配置 附学习参考网址欢迎大家有问题评论交流 (* ^ ω ^) 一、STM32G4 电机外设篇&#xff08;三&…

12 Java GUI

Java 在图形开发中的占比并不是特别突出&#xff0c;尤其在传统的客户端图形界面开发方面。不是现代 UI 设计的首选 C#的WinForms&#xff08;传统&#xff09;、WPF&#xff08;现代&#xff09;是Windows 桌面开发的王者 跨平台&#xff08;Windows/macOS/Linux&#xff09;&…

当AI遇见千年古韵:解密“古韵智绘”,让传统纹样焕发新生机

目录: 引言:当千年古韵遇上AI,一场跨越时空的对话“古韵智绘”:不止于复刻,更是创新的引擎核心技术揭秘:AI如何“理解”并“创作”传统纹样? 基石:海量纹样数据库与智能特征提取神笔:基于GANs的AI纹样生成器魔术:风格迁移与融合的艺术桥梁:交互式编辑与开放API接口系…

[AD] Reaper NBNS+LLMNR+Logon 4624+Logon ID

QA QAForela-Wkstn001 的 IP 位址是什麼&#xff1f;172.17.79.129Forela-Wkstn002 的 IP 位址是什麼&#xff1f;172.17.79.136被攻擊者竊取雜湊值的帳戶的使用者名稱是什麼&#xff1f;arthur.kyle攻擊者用來攔截憑證的未知設備的 IP 位址是什麼&#xff1f;172.17.79.135受…

RAG入门之数据导入

LangChain 是什么 LangChain 是一个用于构建基于大语言模型&#xff08;LLM&#xff09;应用的开源框架。它提供了一套工具和抽象&#xff0c;让开发者能够轻松构建复杂的AI应用。 LangChain 的核心功能 文档加载和处理&#xff1a;支持多种格式&#xff08;PDF、文本、网页…

科研学习|科研软件——激活后的Origin导出图时突然出现了demo水印

问题&#xff1a;画完图在导出图形时&#xff0c;导出的图有demo水印&#xff0c;如下图。 解决方法1&#xff1a;右击选择以管理员身份运行。 解决方法2&#xff1a;找到该软件的保存路径&#xff0c;双击Origin64.exe

一:UML类图

类之间的关系 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 学习设计模式的第一步是看懂UML类图&#xff0c;类图能直观的表达类、对象之间的关系&#xff0c;这将有助于后续对代码的编写。 常见的类之间的关系包括&#xff1a;继承…

Python数学可视化——环境搭建与基础绘图

Python数学可视化——环境搭建与基础绘图 数学函数可视化入门&#xff08;一次函数/三角函数&#xff09; 本节将建立Python科学计算环境&#xff0c;并创建基础函数绘图工具&#xff0c;可生成一次函数和三角函数的可视化图像&#xff0c;同时结合物理中的匀速直线运动案例。…

mask2former训练自己的语义分割数据集

一、环境配置 1.1下载源码 mask2former: https://github.com/facebookresearch/Mask2Former/tree/maindetectron2: https://github.com/facebookresearch/detectron2下载完后&#xff0c;新建一个文件夹&#xff0c;起个名字&#xff08;我起的Mask2Former-main&#xff09…

如何使用1panel部署linux网站

找到官网&#xff0c;尝试一下在线安装 如果在线不成功&#xff0c;试一下离线安装 按照指令一步步执行即可&#xff0c;注意换成新版本的名称即可 如果成功&#xff0c;你会看到这个页面 1Panel Log]: [1Panel Log]: 感谢您的耐心等待&#xff0c;安装已完成 [1Panel Log]:…

个人用户进行LLMs本地部署前如何自查和筛选

一、个人用户硬件自查清单&#xff08;从核心到次要&#xff09; 1. 显卡&#xff08;GPU&#xff09;——决定性因素 显存容量&#xff08;关键指标&#xff09;&#xff1a; 入门级&#xff08;8~12GB&#xff09;&#xff1a;可运行7B模型&#xff08;4bit量化&#xff09;…

java Map双列集合

单列集合&#xff1a;一次只能添加一个元素 双列集合&#xff1a;一次添加两个元素&#xff0c;左边的叫键&#xff08;唯一的不能重复&#xff09;&#xff0c;右边叫值&#xff08;可以重复&#xff09;&#xff0c;键和值一一对应。这样一对叫&#xff1a;键值对/键值对对象…

在IIS上无法使用PUT等请求

错误来源&#xff1a; chat:1 Access to XMLHttpRequest at http://101.126.139.3:11000/api/receiver/message from origin http://101.126.139.3 has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource. 其实我的后…

FastVLM: Efficient Vision Encoding for Vision Language Models——为视觉语言模型提供高效的视觉编码

这篇文章的核心内容是介绍了一种名为 FastVLM 的新型视觉语言模型&#xff08;VLM&#xff09;&#xff0c;它通过一种高效的视觉编码器 FastViTHD&#xff0c;在高分辨率图像输入下实现了显著的性能提升和延迟降低。以下是文章的主要研究内容总结&#xff1a; 1. 研究背景与动…

关于开发板连接电脑找不到CH340解决方法大全(附ch340驱动下载链接)

一、一般开发板只需要一根支持传输数据的usb线就可以&#xff0c;找不到就是驱动没安装&#xff0c;一般win11系统会自动后台安装&#xff0c;如果没安装需要手动 ch340驱动官网&#xff1a;南京沁恒微电子股份有限公司 安装还失败就用这个&#xff08;安装之后重启电脑就可以了…

Flask文件处理全攻略:安全上传下载与异常处理实战

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…

机器学习有监督学习sklearn实战二:六种算法对鸢尾花(Iris)数据集进行分类和特征可视化

本项目代码在个人github链接&#xff1a;https://github.com/KLWU07/Machine-learning-Project-practice 六种分类算法分别为逻辑回归LR、线性判别分析LDA、K近邻KNN、决策树CART、朴素贝叶斯NB、支持向量机SVM。 一、项目代码描述 1.数据准备和分析可视化 加载鸢尾花数据集&…

Vim 支持多种编程语言编辑器

软件简介 Vim是Vi编辑器的增强版&#xff0c;它提供了更多的功能和快捷键。Vim是一款自由软件&#xff0c;它是由Bram Moolenaar在1991年创建的。Vim支持多种编程语言&#xff0c;包括C、C、Java、Python、Perl等等。它是一款轻量级的编辑器&#xff0c;可以快速打开和编辑大型…