当前位置: 首页 > news >正文

初识C++ · C++11(1)

目录

前言:

1 统一列表初始化

2 声明

2.1 auto

2.2 decltype

2.3 nullptr

2.4 stl的部分变化

3 右值引用和移动语义


前言:

在C++11之前,C++98的出现使得C++看起来更像是一门独立的语言,C++委员会成立后,对外宣称的是5年一个版本,但是呢,计划赶不上变化,03年发布了C++03,计划07年发布07版本,变数多了,就一直拖啊拖,拖到了C++11,也就是2011年才发布,搁了这么久,C++11也是憋了一个大的,但是挨骂也挨多了,于是呢,后面就想着,有点新东西了就发布新版本,比如之后的C++14 C++17,C++20等,目前来说,C++98 C++11 C++20都是大版本,其中20还没有那么大,毕竟是3年更新一次,C++11相对于98来说,更正了许多错误,引入了很多新特性,包含了约140个新特性,600个缺陷修正,所以这个大版本需要学习的有很多,目前很多公司都是以11作为基准版本的,学习也是很有必要的。


1 统一列表初始化

初始化列表在我们前面vector的时候就有所涉及了,但是当时我们介绍的不是那么深入,介绍了数组赋值的那个花括号里面的叫做initializer_list,在C++11版本支持这种自定义的赋值,但是呢,组委会可能有点强迫症?于是对于内置类型也加上了Initializer_list的赋值方法:

	int a = 1;int b = { 1 };

要注意的是,这里的是列表初始化,而不是初始化列表。

C++11中委员会扩大了列表初始化的范围,从标准库里面的vector等stl容器到用户自定义类型或者是内置类型都可以这么初始化了,所以一个整型,一个数组可以初始化的方式就有很多种了:

int main()
{int a = 1;int b = { 1 };int c(1);int d{ 1 };int arr1[]{ 1,2,3,4,5 };int arr2[] = { 1,2,3,4,5 };return 0;
}

对于单参数的变量来说,可以圆括号构造初始化,也可以花括号列表初始化,花括号的赋值符号可以删除,这个设计看起来可能有点冗余了,但是呢,毕竟发明出来了,咱们想用就用,毕竟影响不大。

所以在列表初始化就的出来了一个结论:万物皆可列表初始化

但是呢,列表初始化实际上走的也是隐式类型转换,比如单参数的自定义类型,构造函数加上explicit,构造函数变成了显式的,禁止了隐式类型转换,如下:

class A
{
public:explicit A(int a = 0):_a(a){}
private:int _a;
};
int main()
{A a1 = 1;A a2 = { 1 };return 0;
}

此时代码就会报错,即:

不能隐式类型转换了,也就不能隐式的拷贝构造了,但是呢目前来说,我们对于explicit是不怎么用到的,了解一下。

提到了列表初始化,就不得不提到initializer_list了,这里以vector为例子:

在C++11的版本,支持initializer_list的构造,这样对于对象的创建就更加简单了。

http://www.cplusplus.com/reference/initializer_list/initializer_list/

这是关于initializer_list的文档,详细介绍了什么是initializer_list,我们简单来说就是数据的集合。

但是这个点更新的,怎么说呢,实际上来说用处不是那么大,更像是一种强迫症,但是更新了咱们就学,这里并不费脑子。


2 声明

2.1 auto

在C++11中对于auto简单的更新了一下,比如auto可以作为返回值了,这个点为某些场景提供了便利,但是对于这种场景:

auto Func4();auto Func3()
{return Func4();
}auto Func2()
{return Func3();
}auto Func1()
{return Func2();
}

请问Func1的的返回值是什么?这种场景就很绕了,你需要不停的去看每一层的数据类型,到头来甚至还是要你自己去推导,反而不利于代码的可读性了。

auto我们的应用场景可能是用来推导迭代器的数据类型,但是绝不是应用于这么复杂的场景:

int main()
{vector<int> v1;vector<int>::iterator it1 = v1.begin();auto it2 = v1.end();return 0;
}

对于某些变量来说,数据类型实在是太长了的,咱们就用auto是无可非议的,但是一般来说,不用auto我觉得代码可读性更好,至少说我知道这里表达的是什么意思。

2.2 decltype

介绍decltype之前,我们先来讨论一下typeid的用法,typeid是用来展现数据的真实类型的,但是typeid有时候又太真实,反而不利于我们学习,比如:

int main()
{int a = 1;int* pa = &a;int& b = a;cout << typeid(pa).name() << endl;cout << typeid(b).name() << endl;return 0;
}

这里打印出来pa的类型是指针,但是b的类型是int,你说是int呢,他本身是a的别名,说是int没错,但是你说它是int的引用类型呢,也可以,这里可能就有点容易混淆了。

那么typeid是不能用来定义数据类型的,它的作用是展现数据的类型,C++11引入了一个新的关键字,decltype,可以用来声明数据类型,它的英文展开就是declare type,声明类型的意思,用法如下:

int main()
{int a = 1;int* pa = &a;int& b = a;    decltype(a) c = 1;decltype(b) d = a;decltype(*pa) e = 2;//e是引用类型return 0;
}

这里相对于其他的类型来说声明是没有问题的,但是对于某些表达式来说,比如*pa,解引用之后应该是指针类型,但是这里的话呢,e是引用类型的,所以就会报错了。

类似的表达式还有:

int main()
{int a = 1;decltype((a)) pp = a;//pp是引用类型const int m = 1;const int& n = m;decltype(n) k = m;//k是const int的引用类型return 0;
}

decltype对于const的行为这里没介绍,想学习的可以在C++Primer 2.5.3节进行学习。

2.3 nullptr

在C++中,对于NULL定义一直是宏定义为0,这就可能引入部分问题了,因为同时可以表示整型和指针类型,C++11中出于安全考虑加入了nullptr,表示空指针。

2.4 stl的部分变化

在C++11中引入了多个新的容器,比如array,forward_list,unordered_map,unordered_set,以及对于容器的构造函数等插入了新的函数重载,以及引入了其他函数,比如emplace。

但是呢,新添加的部分容器实现没有必要,比如array,array的底层是一个静态数组,相对于vector来说,只是对越界来说检查更为严格了而已,感觉引入的必要实现不是太大,,

对于forward_list来说,单向链表,已经有了list了,双向列表,再引入一个单向链表的意义不太大。

但是其他两个容器就很有学习的意义了,基于哈希封装的unordered_map/set。

还有引入了右值构造的函数,以及增加了cend cbegin函数返回const迭代器。


3 右值引用和移动语义

上面的两个大点,可以说是简单了解,过一下即可,今天的重点是右值部分,相信同学们在学习容器的时候就已经听说过右值的大名了,今天它就来了。
在此之前,我们先了解一下,什么是左值?

左值的定义是可以对该变量取地址的话,那么该变量就是左值,如下:

int main()
{int a = 1;int* pa = &a;vector<int> v1;const int c = 0;v1[0];cout << &a << endl;cout << &pa << endl;cout << &v1 << endl;cout << &c << endl;cout << &v1[0] << endl;return 0;
}

这些都是可以取地址的,所以这些变量统统叫做左值,我们平常的使用的引用都是左值引用,因为引用的都是左值,那么什么是右值呢?

右值与左值相对,即不可以取地址的叫做右值

例如:

int Func()
{int a = 1;return a;
}
int main()
{int a = 1;const int c = 0;cout << &(a + c) << endl;cout << &(Func()) << endl;cout << &1 << endl;cout << &string("aaaaa") << endl;return 0;
}

这些都被叫做,右值,但是呢,右值分为纯右值和将亡值

什么是纯右值呢?

例如,10,这种字面量或者返回的a拷贝临时变量就是纯右值,那么什么是将亡值呢?

将亡值例如tostring返回的是一个字符串,编译器会拷贝一个字符串,然后传到main函数里面去,拷贝的那个临时对象就叫做将亡值,因为拷贝完成,它就会销毁了。

这里可以总结一下,纯右值就是内置类型或字面量值,将亡值就是自定义类型。

提问,左值能给右值取引用吗?

对于一般的左值是不可以的,但是右值的话,比如临时对象,具有常性,所以const的话,左值就可以引用右值了:

int main()
{const string& s = string("aaa");return 0;
}

同样的,右值可以给左值取别名吗?

int main()
{int a = 0;int&& ra = a;//不可以int&& rb = move(a);return 0;
}

 一般是不可以的,但是对左值a使用move方法,就可以实现右值引用,move是什么后面再提,move就有点像强行将左值转到右值。

那么为什么引入右值呢?左值引用的短板是什么?

不可否认的是左值引用减少了一次拷贝构造,提升了效率,但是对于这种情况:

string Func()
{string str = "a";return str;
}
int main()
{string str = Func();return 0;
}

最初,编译器会有三次开辟空间,Func()函数里面开辟一次str的空间,主函数里面开辟一次str的空间,临时对象开辟一次空间,所以操作是Func函数返回的str拷贝了一次给临时对象,临时对象再拷贝构造给str,这样就有两次拷贝构造,对于编译器来说就会优化为直接一次构造,也就是用Func()的str来构造主函数的str。

这是经典的左值构造:

在C++98时经常使用。

那么现在引入右值,可能是组委会觉得,左值拷贝构造效率还可以提升一点:

这里画图解释:

这是经典的一次左值拷贝,右值就很离谱了:

因为Func里面的str是一个将亡值,编译器知道了,然后我们使用的还是右值引用,所以,直接,

”起死回生“!为什么打引号呢?因为主函数的str占用了Func里面的str指向的空间,之后Func::str就销毁了,就像是一种器官移植,将str的空间继续延续下去,这就是右值引用的恐怖之处。

那么,右值引用的本质是什么呢?

int main()
{int a = 0;int&& rb = move(a);cout << &rb << endl;return 0;
}

代码能跑通,所以说右值引用的本质是左值

当我们使用右值引用进行构造的时候,叫做移动构造,右值引用赋值的时候叫做移动赋值,这就是移动语义

因为右值引用的本质是左值,这个就很坑了,比如模拟实现的list,明明用的是右值,push_back的时候,调用了insert,但是传给insert的参数看起来是右值,但是本质的属性还是左值,就又会到左值的函数那里,那么好,cv一份右值insert的函数,里面涉及到了new节点,也就是构造一个节点出来,所以构造函数还不能写一个,还要写一份左值的一份右值的。

所以右值引用的本质是左值这个实现是给人坑住了。

那么引入了两个概念叫做 万能引用和完美转发

刚才因为右值引用的本质是左值引用,我们想让他保持右值的属性,就可以用到完美转发,如:

void insert(T&& val)
{//...
}
void Push_back(T&& val)
{//...insert(forward<T> (val));
}

即forward<T>,就是完美转发,有模板参数就加T,没有就不加,这样可以保持原生属性,那么什么是万能引用呢?

即两个&&,有人就问了,这不是右值引用吗?

是的,实际上来说是万能引用,如:

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<class T>
void PerfectForward(T&& t)
{Fun(forward<T>(t));
}int main()
{int a = 1;const int ca = 1;PerfectForward(a);PerfectForward(ca);PerfectForward(10);PerfectForward(move(ca));return 0;
}

PerfectForward函数的参数注意看,是两个引用,这个时候就说了,它是万能引用,不管传的是左值还是右值都可以引用了,想要保持原生属性只需要完美转发一下就可以,这个函数模板我们提供了,剩下的就是编译器要做的事了,那么也可以这样:

template<class T>
void PerfectForward(T&& t)
{Fun((T&&)(t));
}

这个笔者解释的也不太清楚,但是确实可以这样,了解一下。


感谢阅读!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 代码随想录——判断子序列(Leetcode 392)
  • 立仪科技光谱共焦应用之金属隔膜静态重复性测量
  • 化工材料分析丨结构分析丨配方分析丨元素分析
  • 第一百八十八节 Java XML教程 - Java StAX
  • 前端 package.json 的每一项作用
  • 初始化列表的基本介绍
  • 数学建模~~追逐仿真问题
  • 无人机培训机构推广运营理论技术
  • Python中各类常用内置转换函数
  • uniapp免费申请苹果证书教程每次7天可用于测试
  • Redis,MongoDB,Memcached未授权访问漏洞(及其修复方法)
  • NOI Linux 2.0 的安装说明以及使用指南
  • 使用 podman 推送数据到私有仓库的 3 个问题记录
  • 【知识】PyTorch中的数据类型dtype
  • MAVSDK添加自定义消息与函数实现云台(Gimbal)调整功能
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • Android Studio:GIT提交项目到远程仓库
  • CSS实用技巧干货
  • Git初体验
  • Java超时控制的实现
  • Js基础知识(一) - 变量
  • Python 反序列化安全问题(二)
  • React as a UI Runtime(五、列表)
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • Vue 2.3、2.4 知识点小结
  • Vue组件定义
  • Xmanager 远程桌面 CentOS 7
  • 分享一份非常强势的Android面试题
  • 软件开发学习的5大技巧,你知道吗?
  • 树莓派 - 使用须知
  • 我这样减少了26.5M Java内存!
  • 项目管理碎碎念系列之一:干系人管理
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • 在Mac OS X上安装 Ruby运行环境
  • - 转 Ext2.0 form使用实例
  • 走向全栈之MongoDB的使用
  • # 数论-逆元
  • # 透过事物看本质的能力怎么培养?
  • (14)Hive调优——合并小文件
  • (2)(2.10) LTM telemetry
  • (2)从源码角度聊聊Jetpack Navigator的工作流程
  • (3)STL算法之搜索
  • (4)STL算法之比较
  • (7)STL算法之交换赋值
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (三十)Flask之wtforms库【剖析源码上篇】
  • (转)ORM
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • .JPG图片,各种压缩率下的文件尺寸
  • .net core docker部署教程和细节问题
  • .NET Core 版本不支持的问题
  • .NET MAUI Sqlite数据库操作(二)异步初始化方法