【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.mzph.cn/diannao/60475.shtml

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

相关文章

24/11/7 算法笔记 PCA主成分分析

假如我们的数据集是n维的&#xff0c;共有m个数据(x,x,...,x)。我们希望将这m个数据的维度从n维降到k维&#xff0c;希望这m个k维的数据集尽可能的代表原始数据集。我们知道数据从n维降到k维肯定会有损失&#xff0c;但是我们希望损失尽可能的小。那么如何让这k维的数据尽可能表…

JS 实现SSE通讯和了解SSE通讯

SSE 介绍&#xff1a; Server-Sent Events&#xff08;SSE&#xff09;是一种用于实现服务器向客户端实时推送数据的Web技术。与传统的轮询和长轮询相比&#xff0c;SSE提供了更高效和实时的数据推送机制。 SSE基于HTTP协议&#xff0c;允许服务器将数据以事件流&#xff08;…

C/C++每日一练:查找链表的中间节点

链表&#xff08;Linked List&#xff09; 链表是一种线性数据结构&#xff0c;由一系列节点&#xff08;Node&#xff09;通过指针链接在一起。与数组不同&#xff0c;链表中的元素在内存中不需要连续存储&#xff0c;每个节点包含两部分&#xff1a; 数据部分&#xff1a;存…

对称加密与非对称加密:密码学的基石及 RSA 算法详解

对称加密与非对称加密&#xff1a;密码学的基石及 RSA 算法详解 在当今数字化的时代&#xff0c;信息安全至关重要。对称加密和非对称加密作为密码学中的两种基本加密技术&#xff0c;为我们的数据安全提供了强大的保障。本文将深入探讨对称加密和非对称加密的特点、应用场景&…

PH47代码框架全局函数及功能类

PH47代码框架全局函数及功能类 概述 全局函数及功能类体系是PH47框架当中除了4个逻辑层之外最重要的组成部分之一&#xff0c;它们可以在 整个PH7 代码框架及用户代码中使用。常用全局函数及功能类为 PH7 代码框架提供了最常用和最基础的功能实现。 全局函数主要包含了对时间…

力扣 LeetCode 203. 移除链表元素(Day2:链表)

解题思路&#xff1a; 方法一&#xff1a;头节点和非头节点分开处理 方法二&#xff1a;定义一个dummy虚拟节点&#xff0c;后面的节点就可以采用相同的处理方式 注意&#xff1a; cur需要指向要删除的节点的上一个节点&#xff0c;因为要越过这一个被删除的节点 class Sol…

IEC60870-5-104 协议源码架构详细分析

IEC60870-5-104 协议源码架构 前言一、资源三、目录层级一二、目录层级二config/lib60870_config.hdependencies/READMEexamplesCMakeLists.txtcs101_master_balancedcs104_client_asyncmulti_client_servertls_clienttls_server说明 make这些文件的作用是否需要导入这些文件&a…

turbo c 2.0 画螺栓

代码; #include<graphics.h> void bolt(x0,y0,d,l) int x0,y0,d,l; {int x1,x2,x3,x4,x5,x6,x7,x8;int y1,y2,y3,y4,y5,r1,r2,b,c;if(l>2*d) b2*d;else b1;r11.5*d;r20.38*d;c0.1*d;x1x0-0.7*d;x2x0-0.61*d;x3x0-0.32*d;x4x00.8*d;x5x0l-b;x6x0l-c;x7x0l-0.05*d;x8x0…

网络服务综合项目-博客

一、运行环境&#xff1a; 主机主机名系统服务192.168.31.128Server-WebLinuxWeb192.168.31.129Server-NFS-DNSLinuxNFS 二、基础配置&#xff1a; 配置主机名开启防火墙并配置部分开启selinux并配置服务器之间使用ntp.aliyun.com进行时间同步服务器之间实现ssh免密登录 三…

leetcode86:分隔链表

给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a;head [1,4,3,2,5,2], x 3 输出&am…

Android Mobile Network Settings | APN 菜单加载异常

问题 从log看是有创建APN对应的Controller&#xff08;功能逻辑是ok的&#xff09;&#xff0c;但是Mobile Network Settings无法显示&#xff08;UI异常&#xff09;。 日志分析 看似APN 菜单已经创建了&#xff0c;实际上并没有显示。 11-12 07:01:28.150 8773 8773 D Pr…

上海市计算机学会竞赛平台2020年4月月赛丙组永恒的生命游戏

题目背景 2020年4月11日&#xff0c;英国数学家 约翰霍顿康威&#xff08;John Horton Conway&#xff09;因为新型冠状病毒肺炎不幸逝世。他在群论、数论、代数、几何拓扑、理论物理、组合博弈论和几何等领域&#xff0c;都做出了重大贡献。他的离去是人类文明的损失。他最著…

FS8x 功能安全

fail-safe是电独立的和物理隔离的。fail-safe由自己的参考电压和电流提供,有自己的振荡器,有重复的模拟路径以最小化常见的故障,并有LBIST/ABIST来覆盖潜在故障。fail-safe根据设备部件号提供ASIL B或ASIL D遵从性。除非另有规定,fail-safe定时来自故障安全振荡器,其精度为…

项目模块十七:HttpServer模块

一、项目模块设计思路 目的&#xff1a;实现HTTP服务器搭建 思想&#xff1a;设计请求路由表&#xff0c;记录请求方法与对应业务的处理函数映射关系。用户实现请求方法和处理函数添加到路由表&#xff0c;服务器只接受请求并调用用户的处理函数即可。 处理流程&#xff1a; …

内网域环境、工作组、局域网等探针方案

1. 信息收集 1.1 网络收集 了解当前服务器的计算机基本信息&#xff0c;为后续判断服务器角色&#xff0c;网络环境做准备 systeminfo 详细信息 net start 启动服务 tasklist 进程列表 schtasks 计划任务&#xff08;受权限影响&#xff09; 了解当前服务器的网络接口信息…

什么是量化交易

课程大纲 内容初级初识量化&#xff0c;理解量化 初识量化 传统量化和AI量化的区别 量化思想挖掘 量化思想的挖掘及积累技巧 量化代码基础&#xff1a; python、pandas、SQL基础语法 金融数据分析 常用金融分析方式 常用因子分析方式 数据分析实战练习 回测及交易引擎 交易引擎…

OpenHarmony-1.启动流程

OpenHarmony启动流程 1.kernel的启动 流程图如下所示&#xff1a;   OpenHarmony(简称OH)的标准系统的底层系统是linux&#xff0c;所以调用如下代码&#xff1a; linux-5.10/init/main.c: noinline void __ref rest_init(void) {struct task_struct *tsk;int pid;rcu_sch…

【LeetCode】【算法】64. 最小路径和

LeetCode 64. 最小路径和 题目描述 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 思路 思路&#xff1a;这种题太典了&#xff0c;典…

1.7 JS性能优化

从输入url到页面加载完成都做了些什么 输入 URL - 资源定位符 http://www.zhaowa.com - http 协议 追问&#xff1a;http 与 TCP 1. http - 应用层 < > TCP - 传输层 2. 关联 - http基于TCP实现连接 < > UDP > 握手 & 挥手 &#xff08;传输速率上较…

Spring Task详细讲解

✨Spring Task简介 Spring Task 是 Spring 提供的轻量级定时任务工具&#xff0c;也就意味着不需要再添加第三方依赖了&#xff0c;相比其他第三方类库更加方便易用。可以按照约定的时间自动执行某个代码逻辑。 使用场景&#xff1a; 信用卡每月还款提醒银行贷款每月还款提醒…