【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.xdnf.cn/news/14451.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Maven 构建项目

Maven 是一个项目管理和构建工具&#xff0c;主要用于 Java 项目。它简化了项目的构建、依赖管理、报告生成、发布等一系列工作。 构建自动化&#xff1a;Maven 提供了一套标准化的构建生命周期&#xff0c;包括编译、测试、打包、部署等步骤&#xff0c;通过简单的命令就可以执…

在jquery里,使用$.each()函数循环数组,对象,dom的用法

介绍 $.each() 能遍历一维数组&#xff0c;多维数组&#xff0c;JSON对象&#xff0c;dom2元素。在开发中可以很高效的处理各种数据结构。前提&#xff0c;需要导入jquery 使用 遍历JSON对象 var objDemo {name: linda,age:12, desc: a girl};$.each(objDemo,function(i,va…

UniApp 应用、页面与组件的生命周期详解

UniApp 应用、页面与组件的生命周期详解 在uni-app中包含了 应用生命周期、页面生命周期、和组件生命周期&#xff08; Vue.js的&#xff09;函数。 应用生命周期 应用生命周期仅可在App.vue中监听&#xff0c;在其它页面监听无效。 <script>export default {onLaunc…

进程的创建/终止/等待/替换

目录 一、进程创建 &#xff08;一&#xff09;fork函数的概念 &#xff08;二&#xff09;fork函数示例 二、进程终止 &#xff08;一&#xff09;退出码的概念 &#xff08;二&#xff09;退出码的含义 &#xff08;三&#xff09;相关函数和指令 三、进程等待 &…

【c++丨STL】list的使用

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;C、STL 目录 前言 list简介 一、list的默认成员函数 构造函数(constructor) 析构函数 赋值重载 二、list的迭代器接口 迭代器的功能分类 三、list的容量…

CANoe导入CAN DataBase(DBC文件)

Canoe是一款用于汽车网络仿真和开发的工具&#xff0c;它支持导入DBC文件&#xff08;CAN Database文件&#xff09;以定义和配置CAN网络中的消息、信号和节点。 将DBC文件拷贝至我们的工程目录的DBC文件夹内&#xff0c;随后在Simulation Setup中右击DataBase&#xff0c;进…

nacos配置管理

1、增加依赖 <!--配置管理的依赖 --> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>2.1.0.RELEASE</version> </dependency><de…

每日OJ题_牛客_奇数位丢弃_找规律/模拟_C++_Java

目录 牛客_奇数位丢弃_找规律/模拟 题目解析 C代码1模拟 C代码2找规律 Java代码找规律 牛客_奇数位丢弃_找规律/模拟 奇数位丢弃_牛客题霸_牛客网 描述&#xff1a; 对于一个由 0..n 的所有数按升序组成的序列&#xff0c;我们要进行一些筛选&#xff0c;每次我们丢弃去…

解决table下tr或td选中不生效的问题

目录 一、问题描述 1.首先我们来看一下代码结构 2.检查代码&#xff08;鼠标右键或按下F12&#xff09; 3.解决方案 一、问题描述 解决table下tr或td选中不生效&#xff0c;页面刷新无效果 1.首先我们来看一下代码结构 这里我们的结构是table标签下的tr&#xff0c;tr当…

学籍拍照助手,中小学新生学籍证件照电脑端拍照教程

新学期过半&#xff0c;许多中小学学籍管理员都忙碌起来&#xff0c;为孩子们准备学籍所需的证件照。传统的照相馆拍摄、向家长收集都存在一些弊端&#xff0c;下面就来介绍如何使用校园学籍拍照助手&#xff0c;更智能的完成学籍证件照的拍摄。 1. 准备工作在开始之前&#xf…

SE30 程序运行时间评估

日常执行报表的时候 可能会遇到报表反应时间太长 用户无法接受的情况&#xff0c;此时 作为IT同事 需要分析程序的运行时间&#xff0c;可以使用SAP标准事务码SE30. 1、选择运行时分析-测量-立即执行&#xff08;有些程序可能没有此按钮 需联系开发增加&#xff09; 2、以发…

T-Rex Label标注

这个是做大量数据集的时候用到的&#xff0c;但我觉得他比labelimg好用。 仙人指路✈trexlabel 基本标注 如果是从新开始的话就是 导入图片然后进行直接标注 如果是后期添加图片继续标注&#xff0c;选择你需要的数据集格式&#xff0c;导入即可。 如此&#xff0c;进去就…

部署zabbix遇到问题: cannot find a valid baseurl for repo:centos-sclo-rh/x86 64 怎么解决 ?

安装 Zabbix 前端包&#xff0c;提示cannot find a valid baseurl for repo&#xff1a;centos-sclo-rh/x86 64 安装zabbix前端包 # yum install zabbix-web-mysql-scl zabbix-apache-conf-scl 解决办法&#xff1a; 原因是&#xff1a;CentOS7的SCL源在2024年6月30日停止维护…

小程序+公众号统一账号unionid,实现pc+公众号+小程序统一身份

一、微信开放平台 注册开发者账号、绑定公众号、小程序 二、小程序端获取unionid 1获取code wx.login({success: res > {console.log("getCode", res.code)this.getOpenId(res.code)}}) 2通过code调用后台方法获取openid,unionid 小程序端 getOpenId: functi…

LeetCode【0037】解数独

本文目录 1 中文题目2 求解方法&#xff1a;递归回溯法2.1 方法思路2.2 Python代码2.3 复杂度分析 3 题目总结 1 中文题目 编写一个程序&#xff0c;通过填充空格来解决数独问题。数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只…

零碎02-接口文档管理

目录 一、背景故事 二、解决方案分析 1. 静态文档方案 2. Swagger Springfox 3. Knife4j增强方案 三、示例 1. 添加依赖 2. 配置Knife4j 3. 创建knife4j配置类 4. 启动Spring Boot项目并访问接口文档 5. 使用示例 6. 测试和使用 四、总结 一、背景故事 酷乐是一名…

指标体系构建:如何设计北极星指标设计?

目录 1 北极星指标的作用 2 北极星指标设计标准 标准1 标准2 标准3 标准4 标准5 标准6 3 小结 1 北极星指标的作用 北极星指标是公司业务成功的关键指标&#xff0c;反映了公司为用户带来的价值&#xff0c;有以下3点作用&#xff1a; ● 像北极星一样&#xff0c…

三菱FX5UPLC以太网Socket通信功能Passive开放的程序示例

Passive开放的通信流程如下所示。 参数设置 示例程序中使用的参数设置如下所示。 [CPU模块】 导航窗口↔[参数]↔[模块型号]↔[模块参数]-[以太网端口]-[基本设置]-[对象设备连接配置设置]↔[详细设置]→[以太网配置(内置以太网端口)]画面 【以太网模块】 [导航]中「参数]→[模…

【MATLAB源码-第292期】基于matlab的4ASK调制解调窄带通信系统仿真,输出各节点波形图以及误码率曲线图。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 窄带通信系统是指带宽较小、频谱利用效率较低的通信系统。与宽带通信系统相比&#xff0c;窄带系统的特点是信号的带宽相对较窄&#xff0c;因此需要更精确的调制技术来实现有效的通信。在窄带通信中&#xff0c;常见的调制方…

【搜索结构】AVL树的学习与实现

目录 什么是AVL树 AVL树的定义 插入函数的实现 左单旋和右单旋 左右双旋与右左双旋 什么是AVL树 AVL树实际上就是二叉搜索树的一种变体&#xff0c;我们都知道二i叉搜索树可以将查找的时间复杂度提升到O(logn)&#xff0c;极大提升搜索效率。但是在极端情况下&#xff0c;当…