✨前言✨
📘 博客主页:to Keep博客主页
🙆欢迎关注,👍点赞,📝留言评论
⏳首发时间:2024年5月28日
📨 博主码云地址:博主码云地址
📕参考书籍:《C++ Primer》《C++编程规范》
📢编程练习:牛客网+力扣网
**由于博主目前也是处于一个学习的状态,如有讲的不对的地方,请一定联系我予以改正!!!
C++11
1 列表化
在C++98的标准中,利用{}只可以对数组以及结构体进行初始化,而在C++11中就引入一切皆可列表化。C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
1.1 内置类型
int main()
{
int a = 10;
//内置类型利用初始化列表初始
int b = { 10 };
int c{ 10 };
}
1.2 自定义类型
class Student {
private:
int _age;
string _sex;
string _classid;
public:
Student(int age = 0,string sex = "男",string classid = "2024")
:_age(age),
_sex(sex),
_classid(classid)
{}
};
int main()
{
//自定义类型支持列表化 构造+拷贝构造-->直接构造
Student s1 = { 18,"男","1234" };
Student s2{ 19,"女","1234" };
Student* stu = new Student[4]{ { 19,"女","1234" },{ 18,"男","1234" } };
return 0;
}
对于这一块的特性,还是用一下自定义类型利用{}进行初始化就可以了,内置类型还是和我们之前写法一样即可,这样看上去才不会那么奇怪!
2 initializer_list
在上面的示例中,我们可以发现对于Student这样的类型进行初始化的时候,我们只能固定的给相应的参数,但是对于库中vector或者是list却可以定义不同长度的对象!查看库中对应的构造函数,可以发现在C++11中引入了新的类型initializer_list。
通过查看该类型,可以发现这个类型就是专门接收传过来值的一个列表,然后利用这个列表对相应的类型进行初始化
我们也可以发现该类型中的成员函数有begin与end,也就是说有一个头指针和一个尾指针指向了这个列表,所以通过这个列表就可以接受到不同长度的值,从而就可以生成不同长度的对象!
所以在C++11中,对于许多容器来说,利用这个类型来初始化容器对象就很方便了!
3 右值引用
3.1左值与右值相关概念
首先我们先来搞清楚一个概念,什么是左值,什么是右值!对于左值与右值的概念如下所示:
左值
:可以取到地址的就是左值,对于左值的引用就是左值引用,我们之前所将的引用一般就是指的左值引用,左值引用就是存储左值的地址!
右值
:不能取到地址的就是右值,对于右值的引用就是右值引用,右值引用存储的是右值拷贝到栈上的临时空间,存储的就是这个临时空间的地址!常见的右值就是常量,表达式,函数返回值(传值返回也就是说出了函数作用域,就会销毁,需要拷贝的)
如下图所示:
int main()
{
//以下a b *b c是左值
int a = 3;
int* b = new int(20);
const int c = 10;
//以下是对左值的引用
int& ya = a;
int* yb1 = b;
const int& yc = c;
int& yb2 = *b;
return 0;
double d1 = 1.1;
double d2 = 2.2;
//以下是右值
10;
x+y;
fmax(x,y);
//以下是对右值的引用
int&& r1 = 10;
double&& r2 = x+y;
double&& r3 = fmax(x,y);
}
3.2 左值引用与右值引用的比较
对于左值引用我们可以发现
1️⃣左值引用不可以引用右值,只能引用左值
2️⃣const的左值引用既可以引用左值,又可以引用右值
对于右值引用而言
1️⃣右值引用不可以引用左值,只可以引用右值
2️⃣右值引用可以引用经过move之后的左值
注意
:move不能想怎么样用就怎么样的用,必须根据下面的代码是否有可能会用到当前的左值,如果用不到,那利用move函数将左值变为右值,然后利用移动语义对资源进行移动!
3.3 相关的应用场景
引用的目的就是为了减少拷贝,提高性能,而左值引用就是我们之前一直在使用的引用,传值传参的引用(常见的就是我们之前所学过的拷贝构造函数)以及函数返回值(出了函数作用域,该引用的对象还存在)的引用!同理,右值引用的目的也是为了减少拷贝,它存在的意义就是解决左值引用所解决不了的问题!也就是说右值引用主要作用于哪些出了作用域就已经销毁了的对象,以及匿名对象!目的就是为了减少拷贝,将右值(有些文章中也称为将亡值)中的资源给转移走!如何把资源转移呢?这就要简单的介绍以下移动构造以及移动赋值
移动构造
:移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。(移动赋值的原理也是一样的,都是通过掠夺将亡值的资源来构造自己)
如下图所示就是移动构造与移动赋值
对上述有了一个基本了解之后,我们以下面这个示例为例,从而进一步的了解编译器的优化规则:
在上图中的C++11优化中,如果没有移动构造,那么还是会继续调用拷贝构造的!因为拷贝构造中是const的左值引用,这样既可以引用左值又可以引用右值!
资源移动的原理
:在一开始我们就知道了,右值是不允许被修改的,但是右值一旦经过引用(也就是右值引用),属性就会发生了变化,就变成左值的属性。那么也就可以进行改变,从而实现资源的转移!
3.4 万能引用
万能引用就是如下所示,就是根据T&&可以接收左值引用,也可以接收右值引用,如果是左值引用,那么模版就会推演成T&,如果是右值引用,模版就会推演成T&&(推演会将T转化成具体的类型,这里为了表述,就直接用了T)
我们可以发现,为什么全是左值引用呢?那是因为右值引用是具有左值属性,所以就都去调用对应的左值引用的函数了!为了解决这个问题,就提出了完美转发的解决办法!
完美转发
:就是利用std::forward(t)在传参的过程中保持了t的原生类型属性
所以对代码的改进如下所示:
4 新的类功能
在之前的学习过程中,我们知道类和对象中是有6个默认的构造函数,在C++11中又添加了2个默认的构造,移动构造与移动赋值函数!
1️⃣如果你没有自己实现移动构造函数
2️⃣没有自己实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。
满足了上述两个条件,那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。也就是说一个类只要他需要深拷贝,就必定需要自己实现以上三个函数,也必定需要我们自己去实现资源的移动!移动赋值的原理就和移动构造的原理是一样的(只不过要是没有移动赋值,那么就会调用拷贝赋值)!
在C++11中,还提出了利用default关键字来强制生成默认函数,利用delete关键字强制的禁止生成默认函数!这个在某些特定的场景下还是有用的(比如我们不允许某个类可以进行拷贝)!
如下图所示:
class Date {
private:
int _year;
int _month;
int _day;
public:
Date(Date& date) = delete;//禁止生成默认的拷贝构造
Date() = default; //强制生成默认的构造函数
};
5 总结
在C++11中还引入了以下STL容器,其中unordered_map就是相当于Java中的hashmap,但是对于添加的array以及forward_list这两个容器就很鸡肋了!感觉作用不是特别大!
对于本文中C++11最重要的特性就是引入右值引用,利用移动语义,移动右值中的资源来进行构造,从而减少拷贝,提高程序的性能!