【C++】C++11特性(上)

✨✨欢迎大家来到Celia的博客✨✨

🎉🎉创作不易,请点赞关注,多多支持哦🎉🎉

所属专栏:C++

个人主页:Celia's blog~

目录

一、列表初始化

二、std::initializer_list

三、右值引用和移动语义

3.1 左值和右值 

3.2  左值引用和右值引用

 3.3 引用延长生命周期

 3.4 左值和右值的参数匹配

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

3.5.1 左值引用的使用场景

 3.5.2 移动构造和移动赋值

3.5.3 右值引用、移动语义解决传值返回问题

3.6 引用折叠

3.6.1 引用折叠规则

3.6.2 万能引用

3.7 完美转发

四、可变参数模板

4.1 语法和原理

4.2 包的扩展


一、列表初始化

在C++98中,数组和结构体是可以用{}初始化的:

#include<iostream>
using namespace std;struct Point
{int _x;int _y;
};
int main()
{Point p{ 1,2 };int arr[] = { 1,2,3 };int arr[4] = { 5,6,7,8 };int arr[8] = { 7,8,9 };return 0;
}
  • 在C++11中,为了统一初始化的方式,出现了列表初始化的新初始化方式。
  • 所有类型都可以用{}来初始化(包括内置类型、自定义类型),也叫列表初始化。
  • 自定义类型的初始化本质上是类型转换,编译器会将其优化成直接构造
  • 列表初始化可以省略掉=。
  • 在容器的push/insert操作时,利用列表初始化来构造自定义类型会很方便。
#include<iostream>
using namespace std;class Date
{
public:Date(int _year, int _month, int _day):year(_year),month(_month),day(_day){}
private:int year;int month;int day;
};
int main()
{int a{ 1 };double b{ 2.14 };Date d1 = { 2000,1,1 };Date d2{ 2024,10,11 };return 0;
}

二、std::initializer_list

  • 有了列表初始化之后,对于自定义类型的初始化方便了许多。但是对于一些容器来说,初始化仍然不是那么方便。比如vector,如果我们想初始化三个不同元素的容器,就需要手动写带有三个参数的构造。那么如果想初始化五个元素,十个元素呢?由此可见,增加构造函数并不是有效的方法。
  • 为了解决这个问题,C++11中引入了一个std::initializer_list的类。这个类的底层是开辟一个数组,并将数据拷贝过来。这个类中有两个指针,分别指向数组的开头和结尾。故这个类支持迭代器访问。
  • 如果一个容器支持参数为std::initializer_list构造方法,那么就可以把std::initializer_list中的元素一个一个增添到指定的容器中,这样初始化就方便了许多。
  • 需要注意的是,std::initializer_list中的元素必须是同一类型的。
int main()
{//std::initializer_list构造vector<int> v1({1, 4, 5, 6, 7, 8, 9}); //直接构造vector<int> v2 = { 1,2,3,4,5,6,7 }; //构造临时对象 + 拷贝构造 → 优化成直接构造const vector<int>& v3 = { 1,2,3,4,5 }; //std::initializer_list为临时对象,需要用const引用return 0;
}

三、右值引用和移动语义

在C++98中就有了引用的概念,在C++11中新增了右值引用,之前所学的引用叫做左值引用。无论是左值引用还是右值引用,都是给指定对象起别名

3.1 左值和右值 

  • 左值是一个表示数据的表达式,一般是持久状态,储存在内存中,我们可以获取它的地址。例如:变量名、解引用的指针等。被const修饰的左值,不能够二次赋值,但是可以取它的地址。左值可以在等号的左边,也可以在等号的右边。
  • 右值是一个表示数据的表达式,但一般为字面值常量、或者运算过程中产生的临时对象。右值可以在等号的右边,但是不能在等号的左边。右值不可以取地址
  • 左值的英文简写为lvalue,右值的英文简写为rvalue。传统认为,它们是left value和right value的缩写。但是现代认为,lvalue为loactor value的缩写,意为存储在内存中、有明确存储地址可以取地址的对象。rvalue为read value的缩写,指的是那些可以提供数据值,但是不可以寻址的字面常量或临时对象。总的来说,左值和右值的核心区别在于是否可以取地址
int main()
{//左值int x = 0;cout << &x << endl;int y = 1;//右值x + y;10;"111";//cout << &10 << endl;  //errorreturn 0;
}

3.2  左值引用和右值引用

  • 左值是给左值取别名,右值是给右值取别名。
  • 左值引用无法直接引用右值,但被const修饰的左值引用可以引用右值
  • 右值引用无法直接引用左值,但是可以引用被move过后的左值
  • move是库里面的一个函数模板,本质内部是进行强制类型转换。
  • 变量表达式都是左值属性,也就是说,当一个右值被右值引用绑定后,这个右值引用的属性是左值
  • 当一个常量被右值引用引用后,可以通过右值引用来修改常量的值。并可以再次使用右值引用来访问它。
void func1(int&& x)
{cout << "右值引用" << endl;
}
void func1(int& x)
{cout << "左值引用" << endl;
}int main()
{int a = 10;int& x1 = a; //左值int&& x2 = 1 + 2; //右值func1(x2);  //属性是左值func1(100); //属性是右值cout << x1 << " " << x2 << endl;return 0;
}

 3.3 引用延长生命周期

一般来讲,临时对象的生命周期只在当前一行。但若是引用它,临时变量的生命周期就与这个引用的生命周期相同。

int main()
{cout << 1 + 2 << endl; //生命周期只在当前一行int&& x1 = 2 + 3; //生命周期延长const int& x2 = 3 + 4;  //生命周期延长cout << x1 << " " << x2 << endl;return 0;
}

 3.4 左值和右值的参数匹配

在C++98中,拷贝构造函数的参数为const修饰的左值引用,左值可以匹配,右值也可以匹配。引入右值引用参数后,传入左值会匹配左值引用的构造函数,传入右值会匹配右值引用的构造函数。也就是说,左值引用和右值引用作为参数的函数可以构成函数重载

//函数重载场景示例
void func1(int&& x)
{cout << "右值引用" << endl;
}
void func1(int& x)
{cout << "左值引用" << endl;
}

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

3.5.1 左值引用的使用场景

左值引用的使用场景通常是引用传参或引用作为函数返回值来达到减少拷贝的目的。这已经解决了绝大部分的场景问题。但是还有一些场景是左值引用解决不了的。

//场景1
class Solution {
public: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());return str;}
};
  • 场景1:
  • 当前场景的返回值不能够使用引用来返回,原因是str是一个在函数内部的局部变量,当函数执行完成后,函数栈帧被销毁,str也就不存在了。尽管引用也有延长生命周期的作用,但仍然改变不了函数栈帧销毁的事实。 
  • C++98在这种场景下只能选择传值返回。
//场景2
class Solution {
public:vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);for (int i = 0; i < numRows; ++i){vv[i].resize(i + 1, 1);} for (int i = 2; i < numRows; ++i){for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}} return vv;}
};
  •  场景2:
  • 但如果是场景2的情况,传值返回将是一笔巨大的开销,代价会很大很大。

 3.5.2 移动构造和移动赋值

移动构造和拷贝构造很相似,唯一的不同在于:

  • 移动构造的参数是右值引用,拷贝构造的参数是当前类型的左值引用

移动赋值和赋值重载的区别:

  • 移动赋值的参数是右值引用,赋值重载的参数为当前类型的左值引用

对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第⼀个参数都是右值引用的类型,他的本质是要“窃取”引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,从而提高效率。

//string类部分代码举例
string(const string& s):_str(nullptr)
{reserve(s._capacity);for (auto ch : s){push_back(ch);}
} 
//移动构造
string(string && s)
{swap(s);
}
string& operator=(const string& s)
{if (this != &s){_str[0] = '\0';_size = 0;reserve(s._capacity);for (auto ch : s){push_back(ch);}} return* this;
} 
// 移动赋值
string& operator=(string && s)
{swap(s);return *this;
}

3.5.3 右值引用、移动语义解决传值返回问题

移动语义(Move Semantics)是 C++11 引入的一项重要特性,它允许对象的资源(如堆上分配的内存)在不进行深度复制的情况下进行转移。通过移动语义,可以将对象的资源从一个对象转移到另一个对象,从而避免不必要的内存拷贝,提高程序性能和效率。

在之前的代码中不难观察到,移动赋值、移动构造的独特之处在于:直接交换了右值引用对象和当前对象的数据。由于右值引用的对象是右值,比如常量字符串、匿名对象、临时变量等。这些右值原本的生命周期只有一行,在其他代码段也没有机会使用这些右值,故可以直接掠夺它们的资源,也就是把它们的资源交换过来。

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());return str;}

这段代码在返回str时,返回值会被当做右值来处理。故该返回值的接收必然会调用移动赋值/移动构造。这会直接导致资源的交换,对于string类来说,交换只不过是一个指针和一些其他成员变量。相比于之前的值拷贝,需要一个一个的拷贝指针指向的资源(比如遍历数组元素),移动赋值/移动构造的效率要高很多很多。这就很好的解决了拷贝时间开销巨大的问题。

3.6 引用折叠

在C++中,不可以直接定义引用的引用,如:

int& && a;  //右值引用的引用?

但如果是模板参数,则可能出现以下情况:

template<class T>
void func(T& data)
{cout << data << endl;
}
int main()
{int a = 1;func<int&&>(a);return 0;
}

这种情况下,实例化函数的模板参数为int&&,所以T为int&&,函数参数理论上为int&& &data。这种情况下,参数可以被认为是引用的引用吗?答案是不可以,参数会被折叠成左值引用。这里就涉及到引用折叠的问题。

3.6.1 引用折叠规则

  • 右值引用的右值引用折叠成右值引用,其他所有情况均会折叠成左值引用
template<class T>
void func(T&& data)
{cout << data << endl;
}
int main()
{int a = 1;func<int>(a); //未折叠 参数为int&& //errorfunc<int>(0); //未折叠 参数为int&&func<int&>(a); //折叠 参数为int&func<int&>(0); //折叠 参数为int&  //errorfunc<const int&>(a); //折叠 参数为const int&func<const int&>(0); //折叠 参数为const int&func<int&&>(a); //折叠 参数为int&& //errorfunc<int&&>(0); //折叠 参数为int&&return 0;
}

3.6.2 万能引用

在了解引用折叠的规则后,我们会发现:

  • 如果函数模板参数给定T类型的右值引用(T&&),那么传入右值引用时,会折叠成右值引用;传入左值引用时,会折叠成左值引用

这种写法的结果与实际传入的参数结果相同,这种写法也叫做万能引用

3.7 完美转发

  • func(T&& t)函数模板中,传左值实例化以后是左值引用的func函数,传右值实例化以后是右值引用的func函数。
  • 右值引用的属性是左值,也就是说,当一个右值引用绑定之后,右值引用变量表达式的属性是左值。接下来传入该引用的属性都会被识别成左值。
  • 如果想保持其原有的右值属性,就需要用到完美转发
  • template <class T> T&& forward (typename remove_reference<T>::type& arg) noexcept;
  • 完美转发实际上也是通过函数模板的引用折叠来实现的。
void func1(int&& x)
{cout << "右值引用" << endl;
}
void func1(int& x)
{cout << "左值引用" << endl;
}
int main()
{int&& a = 0;     //假设这是折叠后的类型,折叠后类型为int&&func1(a);func1(forward<int>(a));
//template <class T> T&& forward (typename remove_reference<T>::type& arg) noexcept;
//大致原理:
//假设T为int,说明没有折叠/折叠后为int&&,说明期望a是右值,将a强转为右值。
//若T为int&,说明折叠后为int&,说明期望a是左值,将a强转为左值。return 0;             
}

四、可变参数模板

4.1 语法和原理

C++11支持可变参数模板,也就是支持可变参数数量函数模板和类模板。可变数目的参数被称作参数包。共有两种参数包:

  • 函数参数包,表示零或多个函数参数。
  • 模板参数包,表示零或多个模板参数。
  • 我们用省略号来指出一个模板参数或函数参数的一个参数包,在模板参数列表中,class...或typename...指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟...指出接下来表示零或多个形参对象列表;函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,每个参数实例化时遵循引用折叠规则。
  • 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
  • 这⾥我们可以使用sizeof...运算符去计算参数包中参数的个数。
template<class ...Args>
void func2(Args&&... args)
{cout << sizeof...(args) << endl;
}
int main()
{func2(1, 1.1, "123"); //包中有三个参数return 0;
}

4.2 包的扩展

对于参数包,除了能计算参数的个数,更重要的是能使用包中的不同类型的参数。扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。
扩展方式之一如下:

void show()
{//最后一个调用传入空参数,需要准备空参数的函数cout << endl;
}
template<class T, class ...Args>
void show(T x, Args ...args)
{cout << x << " ";//第一个参数传给x,剩下的N - 1个参数传给参数包//递归调用show(args...);
}
template<class ...Args>
void func2(Args&&... args)
{cout << sizeof...(args) << endl; //输出参数个数//args是N个参数的参数包//第一个参数传给x,剩下的N - 1个参数传给参数包show(args...);
}
int main()
{func2(1, 1.1, string("hello"));return 0;
}

实际上,编译器在编译时会自动推导以下函数(同时也是递归的执行过程):

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/470600.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jmeter常用配置元件介绍总结之定时器

系列文章目录 安装jmeter jmeter常用配置元件介绍总结之定时器 5.定时器5.1.固定定时器5.2.统一随机定时器5.3.Precise Throughput Timer5.4.Constant Throughput Timer5.5.Synchronizing Timer5.6.泊松随机定时器5.7.高斯随机定时器 5.定时器 5.1.固定定时器 固定定时器Cons…

【含开题报告+文档+PPT+源码】基于Spring Boot智能综合交通出行管理平台的设计与实现

开题报告 随着城市规模的不断扩大和交通拥堵问题的日益严重&#xff0c;综合交通出行管理平台的研究与实现显得尤为重要。现代城市居民对于出行的需求越来越多样化&#xff0c;对于交通信息的获取和处理能力也提出了更高的要求。传统的交通管理方式已经难以满足这些需求&#…

并发基础:(淘宝笔试题)三个线程分别打印 A,B,C,要求这三个线程一起运行,打印 n 次,输出形如“ABCABCABC....”的字符串【举一反三】

🚀 博主介绍:大家好,我是无休居士!一枚任职于一线Top3互联网大厂的Java开发工程师! 🚀 🌟 在这里,你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人,我不仅热衷于探索一些框架源码和算法技巧奥秘,还乐于分享这些宝贵的知识和经验。 💡 无论你是刚刚踏…

万字长文解读深度学习——ViT、ViLT、DiT

文章目录 &#x1f33a;深度学习面试八股汇总&#x1f33a;ViT1. ViT的基本概念2. ViT的结构与工作流程1. 图像分块&#xff08;Image Patch Tokenization&#xff09;2. 位置编码&#xff08;Positional Encoding&#xff09;3. Transformer 编码器&#xff08;Transformer En…

嵌入式硬件杂谈(一)-推挽 开漏 高阻态 上拉电阻

引言&#xff1a;对于嵌入式硬件这个庞大的知识体系而言&#xff0c;太多离散的知识点很容易疏漏&#xff0c;因此对于这些容易忘记甚至不明白的知识点做成一个梳理&#xff0c;供大家参考以及学习&#xff0c;本文主要针对推挽、开漏、高阻态、上拉电阻这些知识点的学习。 目…

使用jmeter查询项目数据库信息,保存至本地txt或excel文件1108

知识点1&#xff1a;使用jmeter把项目数据库的数据导出&#xff0c;并使用jmeter导出数据库的数据 步骤1&#xff1a;使用jmeter把项目数据库的数据导出 &#xff08;1&#xff09;测试计划-添加- 线程组setUp线程组 setUp线程组&#xff1a;添加-配置元件-JDBC Connection …

Flink_DataStreamAPI_输出算子Sink

Flink_DataStreamAPI_输出算子Sink 1连接到外部系统2输出到文件3输出到Kafka4输出到MySQL&#xff08;JDBC&#xff09;5自定义Sink输出 Flink作为数据处理框架&#xff0c;最终还是要把计算处理的结果写入外部存储&#xff0c;为外部应用提供支持。 1连接到外部系统 Flink的D…

01-Ajax入门与axios使用、URL知识

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

HarmonyOS Next星河版笔记--界面开发(4)

布局 1.1.线性布局 线性布局通过线性容器column和row创建 column容器&#xff1a;子元素垂直方向排列row容器&#xff1a;子元素水平方向排列 1.1.1.排布主方向上的对齐方式&#xff08;主轴&#xff09; 属性&#xff1a;.justifyContent&#xff08;枚举FlexAlign&#…

webpack loader全解析,从入门到精通(10)

webpack 的核心功能是分析出各种模块的依赖关系&#xff0c;然后形成资源列表&#xff0c;最终打包生成到指定的文件中。更多复杂的功能需要借助 webpack loaders 和 plugins 来完成。 1. 什么是 Loader Loader 本质上是一个函数&#xff0c;它的作用是将某个源码字符串转换成…

如何用WordPress和Shopify提升SEO表现?

选择合适的建站程序对于SEO优化非常重要。目前&#xff0c;WordPress和Shopify是两种备受推崇的建站平台&#xff0c;各有优势。 WordPress最大的优点是灵活性。它支持大量SEO插件&#xff0c;帮助你调整元标签、生成站点地图、优化内容结构等。这些功能让你能够轻松地提升网站…

ArcGIS Pro属性表乱码与字段名3个汉字解决方案大总结

01 背景 我们之前在使用ArcGIS出现导出Excel中文乱码及shp添加字段3个字被截断的情况&#xff0c;我们有以下应对策略&#xff1a; 推荐阅读&#xff1a;ArcGIS导出Excel中文乱码及shp添加字段3个字被截断&#xff1f; 那如果我们使用ArGIS Pro出现上述问题&#xff0c;该如何…

24/11/13 算法笔记<强化学习> DQN算法

DQN算法的主要特点包括&#xff1a; 神经网络代替Q表&#xff1a;在传统的Q学习中&#xff0c;需要维护一个Q表来存储每个状态-动作对的Q值。而在DQN中&#xff0c;使用神经网络来近似这些Q值&#xff0c;这使得算法能够处理具有大量状态和动作的问题。 经验回放&#xff08;E…

Blender进阶:图像纹理节点和映射节点

13 图像纹理节点 13.1 图像纹理节点 图像纹理节点&#xff0c;用于加载一张贴图 加载图片后&#xff0c;可以从图片上取得一个像素点。 输入&#xff1a;一个坐标矢量 输出&#xff1a;该坐标的像素颜色 演示&#xff1a;使用合并xyz节点来指定坐标。。 13.2 多种贴图 一…

MYSQL 库,表 基本操作

相关的两个编码集(简单了解即可) 1.数据库编码集 :对将要存储的数据进行编码 2.数据库校验集:对将要执行的操作&#xff08;增删查改&#xff09;数据是对数据编码的校验&#xff0c;本质也是一种读取数据库中数据库采用的一种编码格式。 总结&#xff1a;数据库无论对数据做…

万字长文分析函数式编程

目录 一.认识函数式编程 一、函数式编程的定义 二、函数式编程的思想 三、函数式编程的特点 四、函数式编程的应用 二.Lambda表达式 三.Stream流 3.1 创建流对象 3.2 注意事项 3.3 Stream流的中间操作 filter map distinct sorted limit skip flatMap 3.4 St…

DOM 规范 — MutationObserver 接口

前言 最近在重学 JavaScript 中&#xff0c;再一次接触到了 MutationObserver 内容&#xff0c;接着联想到了 Vue 源码中有使用过这个接口&#xff0c;因此觉得有必要对 MutationObserver 接口进行相关了解和学习。 下面是 vue 源码中关于 MutationObserver 接口使用的代码&a…

灰狼优化算法

一、简介 1.1 灰狼优化算法-Grey Wolf Optimizer 通过模拟灰狼群体捕食行为&#xff0c;基于狼群群体协 作的机制来达到优化的目的。&#xff27;&#xff37;&#xff2f;算法具有结构简单、需 要调节的参数少、容易实现等特点&#xff0c;其中存在能够自适应调整 的收敛因子…

AI 写作(五)核心技术之文本摘要:分类与应用(5/10)

一、文本摘要&#xff1a;AI 写作的关键技术 文本摘要在 AI 写作中扮演着至关重要的角色。在当今信息爆炸的时代&#xff0c;人们每天都被大量的文本信息所包围&#xff0c;如何快速有效地获取关键信息成为了一个迫切的需求。文本摘要技术正是为了解决这个问题而诞生的&#x…

【 ElementUI 组件Steps 步骤条使用新手详细教程】

本文介绍如何使用 ElementUI 组件库中的步骤条组件完成分步表单设计。 效果图&#xff1a; 基础用法​ 简单的步骤条。 设置 active 属性&#xff0c;接受一个 Number&#xff0c;表明步骤的 index&#xff0c;从 0 开始。 需要定宽的步骤条时&#xff0c;设置 space 属性即…