马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
目次
前言
1.初始化列表
2.std::initializer_list
3.auto
4.decltype
5.nullptr
6.左值引用和右值引用
6.1右值引用的真面目
6.2左值引用和右值引用比力
6.3右值引用的意义
6.3.1移动构造
6.4万能引用
6.5完善转发——forward
结语
前言
C++,这门在体系开发、游戏开发、嵌入式等浩繁范畴都占据主导职位的编程语言,不停在不绝进化。C++11就像是C++家属中的一颗璀璨明星,闪灼着无数令人赞叹的新特性。如果你还在使用传统的C++编码方式,那你大概正在错过许多当代编程的便捷与高效。想象一下,用更少的代码编写出性能更高的步调,轻松应对复杂的并发场景,还能享受更加轻便和安全的内存管理。C++11就能让这齐备成为现实。在这篇博文中,我会一点点揭开C++11新特性的秘密面纱,让你看到C++在这次巨大更新之后所蕴含的无穷潜力。
1.初始化列表
在C++98标准中,允许使用 花括号{ } 对数组大概布局体元素举行同一的列表初始化
比方:
- struct Point{
- int a;
- int b;
- };
- int main(){
- int arry[] = {1,2,3,4};//数组初始化
- Point p = {1,2};//结构体初始化
- return 0;
- }
复制代码 而C++11扩大了使用 花括号{ } 举行初始化的使用范围,使其可以用于全部的内置范例和用户自界说的范例
- //对数组初始化
- int arry1[]{1,2,3,4,5};
- //对内置类型初始化
- int x{9};
- //对用户自定义类型初始化
- Point p{9,9};
- //对vector容器对象进行初始化
- vector<int> vec = {1,2,3,4,5,6,7,8,9};
复制代码 留意:
使用初始化列表时,可以添加等号 " = ",也可以不添加
2.std::initializer_list
initializer_list是什么?
看名字像一个容器,但我们学过的容器并没有这个,但并非没有见过
但C++11支持对各个容器举行初始化列表初始化,离不开它
std::initializer_list 是一个轻量级的模板类,用于在函数参数中转达初始化列表,从而使编译器可以大概精确剖析和处置惩罚使用花括号 {} 举行初始化的对象。
我们在诸多容器的构造中,都可以瞥见它是身影
在vector构造中
在list构造中
std::initializer_list 是界说在 <initializer_list> 头文件中的一个模板类,用于表现一个初始化列表。
它的根本情势如下:
- std::initializer_list<T> init_list = { /* 初始化元素 */ };
复制代码 std::initializer_list 提供了一种轻量级的、只读的视图来访问初始化列表中的元素。它常用于构造函数、函数参数以及返回范例,以支持同一初始化语法。
并不是全部的范例都会有std::initializer_list的构造函数,也并不是有std::initalizer_list才华使用花括号 { } 举行初始化
但只要你显式写了使用std::initializer_list的构造函数,则举行{ }初始化时,会优先调用该函数举行初始化
关于initializer_list的细节不外多赘述,知道有这个东西,和它的大抵用途即可
3.auto
在C++98中auto是一个存储范例的分析符,表明变量是局部自动存储范例,但是局部域中界说局部的变量默认就是自动存储范例,以是auto没有什么代价
C++11废弃auto的原来用法,将其用于实现自动范例推断,这一功能极大地简化了代码编写,特别是在处置惩罚复杂范例时。
举个例子:
当一个范例很复杂时,可以使用auto举行简写
- std::pair<int, std::string> get_user_info() {
- return {42, "Alice"};
- }
- int main() {
- // 使用 auto 简化返回类型的声明
- auto user = get_user_info();
- reutrn 0;
- }
复制代码 4.decltype
在C++11标准中,decltype 是一个新增的关键字,用于在编译时推导表达式的范例。与 auto 关键字类似,decltype 也用于范例推导,但两者的工作机制和应用场景有所差别。
decltype 的根本语法
- decltype(expression) variable_name;
复制代码 分析:
- expression 是任何有效的C++表达式,decltype 会根据这个表达式的范例来推导出 variable_name 的范例。
示例:
- int a = 5;
- double b = 3.14;
- decltype(a) c = a; // c 的类型为 int
- decltype(b+b) d = b; // b+b表达式的结果类型为double,则b的类型为 double
复制代码 留意:
decltype 不会现实盘算表达式的值,它只在编译时举行范例推导。
5.nullptr
由于C++中的NULL被界说为字面量0,但如许大概会带来一些标题
由于 NULL 既能表现指针常量,又能表现整形常量
出于清楚和安全的思量,C++11新增了nullptr,用于表现空指针
6.左值引用和右值引用
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用的语法特性
现在,引用就分为两种
什么是左值,什么是左值引用?
左值是一个表现数据的表达式,如变量名、指针等
左值一样寻常出现在赋值符号的左边
- //下面的指针p ,变量b、c 均为左值
- int* p = new int(0);
- int b =1;
- const int c =2;
复制代码 左值引用就是给左值的引用,给左值取别名
- int*& rp = p;
- int& rb = b;
- const int& rc = c;
复制代码 留意:
左值引用的范例肯定是 左值范例+& ,必须严酷匹配
什么是右值?什么是右值引用
右值也是一个表现数据的表达式,如字面常量、表达式返回值、函数返回值等等
右值只能出现在赋值符号的右边
- //常见右值
- 10;//常量
- x+y;//表达式
- add(x,y)//函数返回值
复制代码 右值引用就是对右值的引用,给右值取别名
- int&& rr1=10;
- double&& rr2 = x + y;
- double&& rr3 = fmin(x,y);
复制代码 右值是不能取地点的,但给右值取别名后,会导致右值被存储到特定的位置(一样寻常是栈区),且可以取到该位置的地点
也就是说:不能取字面量10的地点,但rr1引用后,可以对rr1取地点,也可以修改rr1
误区:
风俗使用左值引用后
我们对右值引用也会有一个误解:可以通过修改右值引用来修改右值
这是不切现实的,我们修改的从来只是右值引用,而并非右值!
如上面的rr1,令rr1 = 11,并不会让 10 = 11!
巧记
- 区分左值和右值:左值在 赋值操纵符的左边,右值在 赋值操纵符的右边
- 区分左值引用和右值引用:左值引用是 范例+&,右值引用是 范例+&&
6.1右值引用的真面目
右值不能被修改,这是公认的,比方 10 永世不大概酿成 11
但为什么被引用后,就可以被修改?10不是在常量区吗?岂非被移到栈区了?
右值引用一个右值,右值本身并没有被“移动”到某个新的地域,而是右值引用的变量本身存储了右值的内容
具体来说:
- 右值引用的变量:右值引用本身是一个左值,它有本身的存储位置(通常是栈上的某个位置)。这个变量存储了右值的内容
- 右值的原始位置:右值的本身的原始位置(如字符常量存储在常量区)并不会由于右值引用而改变变。右值引用只是提供了一个访问右值内容的途径
右值引用的真面目——左值,左值可以被取地点,非const左值可以被修改
6.2左值引用和右值引用比力
左值引用总结:
- 左值引用不能引用左值,但不能引用右值
- const 左值引用可以引用左值,也可以引用右值
右值引用总结:
- 右值引用可以引用右值,不能引用左值,但可以引用move后的左值
6.3右值引用的意义
const 左值引用既可以引用左值又可以引用右值,那为什么C++11还要提出右值引用呢?
是不是多此一举?
并不是,左值引用在一起场景下存在短板,而右值引用恰恰能办理这个短板
举个例子:
先今,我们自界说了一个string类
该类中有一个拷贝构造函数,并在类外界说了一个to_string函数
两者造型如下:
to_string函数
- bit::string to_string(int value) {
- bit::string str;
- //
- return str;
- }
复制代码 string的拷贝构造函数
- string(const string& s) : _str(nullptr)
- {
- std::cout << "MyString(const MyString& s) -- 深拷贝" << std::endl;
- string tmp(s.str);
- swap(tmp);
- }
复制代码 用to_string的返回值去构造一个string对象
这就表现左值引用的短板了
当函数返回对象是一个局部对象,出了函数作用域就不存在了,就会调用拷贝构造创建一个新的临时对象,再用这个临时对象去拷贝构造
一共会发生两次拷贝构造,此中,创建临时对象的时间,涉及开发新的空间
开发新空间,很浪费资源,太多次受不了
6.3.1移动构造
有没有什么简朴,且不吃操纵的方法规避深拷贝
有的,兄弟,有的
to_string的返回值是一个右值,用这个右值构造ret2,如果没有移动构造,调用就会匹配调用拷贝构造,由于const左值引用是可以引用右值的,这里就是一个深拷贝
此时我们在bit::string中增长一个移动构造,就是用右值引用充当形参
具体造型如下
- //移动构造
- string(string&& s)
- :_str(nullptr)
- ,_size(0)
- ,_capacity(0)
- {
- swap(s);
- }
复制代码 此时再次构造ret2,如果既有拷贝构造,又有移动构造,则会调用更为匹配的移动构造
而移动构造中没有新开空间,拷贝数据,服从进步
移动构造的本质:将参数右值的资源盗取过来,占为己有,就不消创建临时对象,开发空间,而是直接盗取别人的资源来构造本身。
为什么可以如许呢?
右值引用可以延伸右值的生命周期,使其生命周期向右值引用看齐
如许函数中的返回值原来出了函数作用域就会被烧毁,接纳资源,右值引用后,就延伸了生命周期,不会被接纳,
可以直接拿来构造新的对象,不消再创建临时对象,造成额外开销。
6.4万能引用
“万能引用”(Universal Reference)是 C++ 编程中的一个术语,重要用于形貌一种可以大概同时绑定到左值(lvalue)和右值(rvalue)的引用范例。
万能引用通常出现在模板编程中,允许函数模板担当恣意范例的参数,从而实现更机动和通用的代码筹划。
来看以下例子
- // 接受左值引用
- void Fun(int& x) {
- std::cout << "Fun(int&): "<< x << std::endl;
- }
- // 接受右值引用
- void Fun(int&& x) {
- std::cout << "Fun(int&&): "<< x << std::endl;
- }
- // 万能引用的模板函数,调用相应的 Fun 函数
- template<typename T>
- void CallFun(T&& arg) {
-
- Fun(arg);
- }
复制代码 在main中举行调用模版函数
- int a = 10;
- const int b = 20;
- CallFun(a); // 传递左值,调用 Fun(int&)
- CallFun(30); // 传递右值,调用 Fun(int&&)
复制代码 运行结果:
按理说,应该会调用一个 Fun(int&&) 和一个Fun(int&),但究竟并不是如许
为什么?
模版的万能引用只是提供了可以大概同时吸取左值引用和右值引用的本领
但在后续的使用中,同一当成左值来使用
即进入模版函数体中,只有左值,没有右值
以是,会调用两次 Fun(int&)
我们渴望事变按照我们的渴望发展
在模版函数体中,右值和左值也会保持原来的属性,不会厘革
这时间,就必要完善转发
6.5完善转发——forward
完善转发是 C++11 引入的一项告急特性,旨在函数模板中将参数以其原始的值种别(左值或右值)转达给其他函数,确保在转发过程中不改变参数的性子。
根本造型:
分析:
示例:
在万能转发的模版函数中,添加完善转发
- template<typename T>
- void CallFun(T&& arg) {
- Fun(std::forward<T>arg);
- }
复制代码 main照常
- int main()
- {
-
- int a = 10;
- const int b = 20;
- CallFun(a); // 传递左值,调用 Fun(int&)
- CallFun(30); // 传递右值,调用 Fun(int&&)
- return 0;
- }
复制代码 运行一下:
和我们的预期划一
右值引用和左值引用各有各的用法,并不是随意创造的,具体怎么用,必要连合现实环境举行分析选择。
结语
综上所述,C++11引入的初始化列表、右值引用以及nullptr等特性,为C++语言的发展注入了新的活力。初始化列表通过提供同一的初始化语法,增强了代码的可读性和可维护性;右值引用依附其独特的语义,极大地优化了对象的移动语义和资源管理服从;nullptr则为指针范例提供了一个明确而安全的表现。这些特性的引入,不光丰富了C++语言的编程范式,更为当代软件开发中的高效性、可靠性要求提供了有力支持,推动了面向对象编程向更高条理发展。
|