C++日新月异的未来代码:C++11(上)

article/2025/8/14 8:40:50

文章目录

  • 1.统一的列表初始化
    • 1.1 普通{ }初始化
    • 1.2 initializer_list
  • 2.声明
    • 2.1 auto、nullptr
    • 2.2 decltype
  • 3.左值右值
    • 3.1 概念
    • 3.2 左值引用与右值引用比较
    • 3.3 左值引用与右值引用的应用
    • 3.4 完美转发
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

C++11 能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习

1.统一的列表初始化

1.1 普通{ }初始化

struct Point
{int _x;int _y;
};
int main()
{int array1[] = { 1, 2, 3, 4, 5 };int array2[5] = { 0 };Point p = { 1, 2 };return 0;
}

C++98 中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定

struct Point
{int _x;int _y;
};
int main()
{int x1 = 1;int x2{ 2 };int array1[]{ 1, 2, 3, 4, 5 };int array2[5]{ 0 };Point p{ 1, 2 };// C++11中列表初始化也可以适用于new表达式中int* pa = new int[4] { 0 };return 0;
}

C++11 扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号 =,也可不添加

简单来说,C++11 一切皆可用 {} 初始化,并且可以不写 =,建议日常定义,不要去掉 =,但是我们要能看懂

🔥值得注意的是:Point p1 = { 1, 1 } ,是一种多参数隐式转化,无论哪一种初始化方式都是要调用对应的构造函数的

1.2 initializer_list

initializer_list 是 C++11 引入的一个轻量级容器,用于支持统一的初始化语法和参数列表初始化。它允许函数或类接收任意数量的同类型参数,并提供简洁的初始化方式

vector<int> v1 = { 1,2,3 }; // 调用 vector 的 initializer_list 构造函数

stl 中的容器基本都是支持 initializer_list 初始化的,这初始化的方式和上面的隐式类型不同,具体方式如下:

在这里插入图片描述

首先要知道 initializer_list 主要有这三个成员

  1. 创建一个临时数组(存储在栈上),包含元素 {1, 2, 3}
  2. 生成一个 initializer_list<int> 对象,该对象引用临时数组(内部保存数组的起始地址和长度)
  3. initializer_list 引用临时数组(不拥有其内存),vector 会将临时数组的内容复制元素到自己的内存空间(堆上),与临时数组无关
  4. 临时数组被销毁

🔥值得注意的是: vector 是复制引用数组的元素,而不是直接接收,是因为该临时数组销毁之后,如果 vector 还接收着的话会造成悬空引用

因此 initializer_list 的存在还是很有必要的:

C++11 之前,若想让函数接收一个类似 {1, 2, 3} 的初始化列表作为参数,需要通过数组或容器(如 vector)传递,不够直观。initializer_list 允许函数直接以初始化列表为参数,使代码更符合直觉

2.声明

2.1 auto、nullptr

C++ 增加的特性在前面的文章进行过详细讲解,不过多叙述

传送门:C++命运石之门代码抉择:C++入门(下)

2.2 decltype

// decltype的一些使用使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{decltype(t1 * t2) ret;cout << typeid(ret).name() << endl;
}
int main()
{const int x = 1;double y = 2.2;decltype(x * y) ret; // ret的类型是doubledecltype(&x) p;      // p的类型是int*cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl;F(1, 'a');return 0;
}

typeid().name() 是一种读取类型的方式,但是无法使用,decltypeauto 类似,可以自动推导类型,auto 只能推导,decltype 可以推导并使用

3.左值右值

3.1 概念

传统的 C++ 语法中就有引用的语法,而 C++11 中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名

🚩什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址,一般可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时 const 修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名

int main()
{// 以下的p、b、c、*p都是左值,字符串也算一种左值int* p = new int(0);int b = 1;const int c = 2;// 以下几个是对上面左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& pvalue = *p;return 0;
}

简单来说就是能够取地址的就是左值,左值大多数能赋值(除 const 左值),为左值取别名的变量就是 左值引用

🚩什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名

int main()
{double x = 1.1, y = 2.2;// 以下几个都是常见的右值10;x + y;fmin(x, y);// 以下几个都是对右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);// 这里编译会报错:error C2106: “=”: 左操作数必须为左值10 = 1;x + y = 1;fmin(x, y) = 1;return 0;
}

简单来说,不能取地址的,就是右值,右值不能做左操作数,为右值取别名的变量就是 右值引用

🔥值得注意的是: 右值引用之后,是可以对引用变量取地址修改的,不想修改的话加 const 即可

3.2 左值引用与右值引用比较

🚩左值引用

int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a;   // ra为a的别名//int& ra2 = 10;   // 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}
  1. 左值引用只能引用左值,不能引用右值
  2. 但是 const 左值引用既可引用左值,也可引用右值

因为两者的生命周期不同,会造成悬空。假设允许 int& ra2 = 1010 是右值,无固定内存地址,生命周期短暂。若 ra2 绑定到 10,当表达式结束后,10 被销毁,ra2 将悬空。而 const int& 通过延长右值生命周期,创建一个临时对象存储右值,并将引用绑定到该临时对象,避免了此问题

🚩右值引用

int main()
{// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值return 0;
}
  1. 右值引用只能右值,不能引用左值
  2. 但是右值引用可以 move 以后的左值

右值引用是个临时存储的对象,如果引用左值的话,原本左值中正在使用的值会因为右值引用的销毁而受影响,但是可以使用 move 转成右值,下面会对 move 进行解析

3.3 左值引用与右值引用的应用

string& func()
{static string a;return a;
}int main()
{string ret = func();return 0;
}

之前我们学习过引用返回,是一种左值引用,将变量的地址返回回去,但是这种方式的局限性巨大,大部分情况下返回的是一个局部变量,那么就不能用左值引用返回了,这里只能使用传值返回,传值返回会导致至少 1 次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)

那么右值引用和移动语义解决上述问题:

首先介绍一个概念

  • 普通类型的右值: 纯右值
  • 自定义类型的右值: 将亡值

在这里插入图片描述

早期编译器还没有优化的时候返回的 str 先传递给临时变量,这个过程是拷贝构造,是一次深拷贝,由于此时的临时变量是右值,所以可以使用移动构造,下面将详细介绍移动构造的实现:

// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}
//移动构造string(string&& s):_str(nullptr){cout << "string(string&& s) -- 移动拷贝" << endl;//......}

如图为移动构造的大概框架

  1. 参数传递: 移动构造函数接收一个右值引用参数 string&& s

  2. 资源转移: 举个例子,省略号处可能执行 data = s.datasize = s.size 的操作,看起来好像是转移了资源,其实不是的,只是把原来对象的指针和属性转移给了新对象,并没有拷贝原来对象的主要数据,简单来说就是资源所有权的转移

  3. 原对象资源清理: 将原对象的data指针置为 nullptrsize 置为 0,这样当原对象在析构时(即出了局部作用域时),由于资源所有权被转移给新对象,所以这部分资源不会给释放

通过这样的方式,移动构造函数实现了资源的高效转移,避免了像拷贝构造函数那样对资源进行复制,提高了对象创建和资源管理的效率


通过不断的编译器优化,如今已经可以只使用一次移动构造就能解决了

在这里插入图片描述

两个步骤直接合二为一,编译器通过特殊处理,将本来是左值的 str 返回值,识别成将亡值,直接使用移动构造转移资源

不仅仅有移动构造,还有移动赋值:

在这里插入图片描述

移动构造: func 函数中创建了 string 对象 str ,当 return str 时,会调用移动构造函数。因为 str 是函数内局部对象,返回时将其资源以移动方式构造一个临时对象用于返回值。这避免了对字符串内容的深拷贝,直接转移资源所有权,提升效率

移动赋值:main 函数中,先创建了 string 对象 ret2 ,然后 ret2 = func(); ,这里func() 返回一个临时对象,此操作会调用 string 类的移动赋值运算符。它将 func 函数返回的临时对象的资源(如内部存储字符串的指针等相关资源)转移给 ret2 ,而不是进行字符串内容的复制,同样是为了提高效率

🔥值得注意的是: 移动构造函数是用于创建一个新对象,并从另一个对象(通常是临时对象)那里获取资源所有权;而移动赋值是用于将一个已存在对象的资源所有权转移给另一个已存在的对象。简单来说,移动构造函数是在对象创建时起作用,而移动赋值是在对象已经存在之后进行资源转移时起作用


int main()
{string s1("hello world");string s2(s1);string s3(move(s1));return 0;
}

了解了右值引用相关的移动构造,那么 move 就好解释多了,move 简单理解就是把资源完全复制到一个右值新对象,对原来的对象并没有修改

s2 这里 s1 是左值,调用的是拷贝构造,这里我们把 s1 move处理以后,会被当成右值,调用移动构造,但是这里要注意,一般是不要这样用的,因为我们会发现 s1 的资源被转移给了 s3s1 被置空了

3.4 完美转发

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}
int main()
{PerfectForward(10);           // 右值int a;PerfectForward(a);            // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b);      // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}

模板中的 && 不代表右值引用,而是万能引用,其既能接收左值又能接收右值,模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力

T&& 的推导规则:

  • 当传入左值(如变量 a)时,T 被推导为左值引用类型(如 int&),此时 T&& 折叠为 int&(即左值引用),也叫引用折叠

  • 当传入右值(如 10move(a))时,T 被推导为非引用类型(如 int),此时 T&& 成为 int&&(即右值引用)

在这里插入图片描述

但运行之后发现全都是左值,并没有按照传入实参的类型调用

关键点: 变量名本身永远是左值,无论这个变量是左值引用还是右值引用类型

为什么 t 是左值?

T&& 无论被推导成什么,都是根据 t 这个实参的传入进行调整的,我们知道右值是不可以被修改的,所以这里理应是个左值,而且 C++ 标准明确规定:变量名(无论其类型是左值引用还是右值引用)都是左值表达式

因此,Fun(t) 中的 t 始终被视为左值,调用 Fun(int&)Fun(const int&)

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{Fun(forward<T>(t));
}
int main()
{PerfectForward(10);           // 右值int a;PerfectForward(a);            // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b);      // const 左值
![请添加图片描述](https://i-blog.csdnimg.cn/direct/5331a7028bb842c8987b625c5cc4c15e.png)PerfectForward(std::move(b)); // const 右值return 0;
}

因此保持参数的原始值类别(左值或右值)就要用到 forward

  • 如果原始参数是左值,T 会被推导为左值引用(如 int&),forward<int&>(t) 返回左值引用
  • 如果原始参数是右值,T 会被推导为非引用类型(如 int),forward<int>(t) 返回右值引用(int&&

这样,t 的原始值类别(左值 / 右值)就被正确传递给 Fun 函数


希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述


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

相关文章

C++从入门到实战(十二)详细讲解C++如何实现内存管理

C从入门到实战&#xff08;十二&#xff09;详细讲解C如何实现内存管理 前言一、C内存管理方式1. new/delete操作内置类型2. 异常与内存管理的联系&#xff08;简单了解&#xff09;3. new和delete操作自定义类型 二、 operator new与operator delete函数&#xff08;重点&…

【2025年最新版】Java JDK安装、环境配置教程 (图文非常详细)

文章目录 【2025年最新版】Java JDK安装、环境配置教程 &#xff08;图文非常详细&#xff09;1. JDK介绍2. 下载 JDK3. 安装 JDK4. 配置环境变量5. 验证安装6. 创建并测试简单的 Java 程序6.1 创建 Java 程序&#xff1a;6.2 编译和运行程序&#xff1a;6.3 在显示或更改文件的…

【Linux系统】从 C 语言文件操作到系统调用的核心原理

文章目录 前言lesson 15_基础IO一、共识原理二、回顾C语言接口2.1 文件的打开操作2.2 文件的读取与写入操作2.3 三个标准输入输出流 三、过渡到系统&#xff0c;认识文件系统调用3.1 open 系统调用1. 比特位标志位示例 3.2 write 系统调用1. 模拟实现 w 选项2. 模拟实现 a 选项…

JavaSwing之--JTextField

JavaSwing之–JTextField JTextField 是一个允许编辑单行文本的轻量级组件&#xff0c;它提供了一系列的构造方法和常用方法用来编写可以存储文本的文本框满足程序功能的需求。 以下在简要介绍常用构造方法、普通方法后详解各种方法的应用及举例。 一、构造方法 方法名称功…

Windows系统之VHD安装

环境准备 工具说明Dism部署系统、提取和转换系统镜像等等&#xff0c;还有很多功能大家可以自行探索。这里只用到Dism的部署系统功能。 Releases Chuyu-Team/Dism-Multi-language GitHubbcdedit.exe自带工具 C:\Windows\System32\bcdedit.exe 创建虚拟磁盘 首先右键点击我…

解决Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field ‘com.sun.tools.javac.tre

问题描述 在更新自建基础项目过程中&#xff0c;compile、install报错。 Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualid 解决方案 问题原因是Lombok &#xff0c;与 JDK 21 兼容的最低 Lombok 版本是…

【C++】二叉搜索树 - 从基础概念到代码实现

&#x1f4cc; 个人主页&#xff1a; 孙同学_ &#x1f527; 文章专栏&#xff1a;C &#x1f4a1; 关注我&#xff0c;分享经验&#xff0c;助你少走弯路 文章目录 1. 二叉搜索树的概念2. 二叉搜索树的性能分析3. 二叉搜索树的插入4. 二叉搜素树的查找5. 二叉搜索树的删除6.二…

C++之类和对象基础

⾯向对象三⼤特性&#xff1a;封装、继承、多态 类和对象 一.类的定义1. 类的定义格式2.类域 二.实例化1.对象2.对象的大小 三.this指针 在 C 的世界里&#xff0c;类和对象构成了面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;的核心框架&…

报错java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not ...解决方法

在运行项目时出现java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualidzz这样的报错 解决方法 1.第一步&#xff1a;在pom文件中将lombok的版本改成最新的 此时1.18.34是新…

2025-03-12 Python深度学习1——安装Anaconda与PyTorch库

文章目录 1 配置 Anaconda1.1 下载1.2 安装1.3 配置环境变量1.4 检查安装 2 安装 PyTorch 库2.1 创建 DL 环境2.2 安装/升级 CUDA2.3 配置环境变量2.4 安装 Pytorch 库方法一&#xff08;不稳定&#xff09;方法二&#xff08;推荐&#xff09; 2.5 检查安装 3 Pycharm Communi…

C++ 关联式容器:map,multimap,set,multiset

目录 引言 一、关联式容器概述 1.1 与序列式容器的区别 1.2 底层结构 二、set容器详解set介绍 2.1 set的特性 2.2 set的模板参数 2.3 set的常用接口 2.4 set使用示例 三、map容器详解map介绍 3.1 map的特性 3.2 map的模板参数 3.3 map的常用接口 3.4 map使用示例 …

从零开始配置Qt+VsCode环境

从零开始配置QtVsCode环境 文章目录 从零开始配置QtVsCode环境写在前面扩展安装及配置Qt Configure配置 VsCode创建Qt工程VsCodeQMakeMinGwVsCodeQMakeMsvcVsCodeCMakeMinGwVsCodeCMakeMsvcQtCreatorQMakeMinGw->VsCodeQtCreatorQMakeMsvc->VsCodeQtCreatorCMakeMinGw-&g…

Matlab/Simulink - BLDC直流无刷电机仿真基础教程(一) - 三相逆变器的搭建

Matlab/Simulink - BLDC直流无刷电机仿真基础教程&#xff08;一&#xff09; - 三相逆变器的搭建 前言一、BLDC电机六步换相简明控制原理二、Simulink中BLDC电机模块的机械连接三、三相逆变电路的搭建四、仿真参数设置与仿真结果验证五、补充内容参考链接 前言 本系列文章分享…

Lapce:一款用 Rust 编写的快速且强大的代码编辑器

Lapce&#xff08;IPA&#xff1a;/lps/&#xff09;是一个使用纯 Rust 编写的开源代码编辑器。通过利用 OpenGL 渲染 GUI&#xff0c;以及 Rust 提供的性能&#xff0c;采用Xi-Editor的Rope Science设计&#xff0c;可实现闪电般的快速计算。 Stars 数35888Forks 数1113 主要…

SpringBoot启动后初始化的几种方式

目录 一、静态代码块 二、构造方法 三、PostConstruct 四、InitializingBean 接口 五、 Bean 注解中的 initMethod 六、 CommandLineRunner 接口 七、ApplicationRunner 接口 八、EventListener事件 九、SmartInitializingSingleton接口 十、ApplicationListener接口…

【MySQL课程学习】:MySQL安装,MySQL如何登录和退出?MySQL的简单配置

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;MySQL课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 MySQL在Centos 7环境下的安装&#xff1a; 卸载…

Node.js下载安装及环境配置教程(保姆级教程)

一、安装程序 &#xff08;安装包放在文章最后需要的友友可自取哦&#xff09; &#xff08;1&#xff09;下载完成后&#xff0c;双击安装包&#xff0c;开始安装Node.js &#xff08;2&#xff09;此位置可修改为自己的安装路径&#xff0c;修改完后点击next &#xff08;3…

com.mysql.cj.jdbc.exceptions.CommunicationsException Communications link failure 问题解决

前言: 一般这个报错大多是网络原因导致的&#xff0c;确保你不是网络问题再往下看 问题 在一个方法上&#xff08;该方法非常复杂执行时间长&#xff09;加了 Transactional(rollbackFor Exception.class)后出现了如下图所示的错误 解决&#xff1a; 经过排查并非网络问…

【解决方案】CloudFront VPC Origins 实践流程深入解析 —— 安全高效架构的实战之道

目录 引言一、VPC Origins 的核心价值&#xff08;一&#xff09;安全性提升&#xff08;二&#xff09;运维效率优化&#xff08;三&#xff09;成本节约&#xff08;四&#xff09;全球分发能力的保留 二、VPC Origins 的架构解析&#xff08;一&#xff09;流量路径设计&…

MySQL性能调优(三):MySQL中的系统库(sys系统库、information_schema)

文章目录 MySQL性能调优数据库设计优化查询优化配置参数调整硬件优化 MySQL中的系统库1.3.sys系统库1.3.1.sys使用须知1.3.2.sys系统库使用1.3.3.查看慢SQL语句慢在哪里1.3.4.小结 1.4.information_schema1.4.1.什么是information_schema1.4.2.information_schema表分类Server层…