C++11新特性讲解
写在前面
博主是一个大一计科生,因为学校教的第一个语言就是C++,但是教的版本太老了,什么auto、智能指针、移动语义听都没听过。所以博主决定自学一下C++。但是博主后续不想深入学习C++,决定暂时先学到C++11,像14、17、20、23暂时先不学,学到C++11够用就行
C++11的成就
C++11在C++98的标准上修正了很多缺陷,并添加了很多新特性。可以说C++11是C++新的开始
C++14、17等等相对上一个标准的区别并没有那么大,C++11是改动最大的
其中auto、decltype、基于范围的for循环、列表初始化和{}初始化、using定义别名、final关键字、右值引用、lambda表达式、智能指针是比较重要的
C++11常见特性
(1)auto类型推导
auto的作用:使用了auto关键字之后,编译器会在编译期间自动推导出变量的类型,这样就不用手动指明变量的数据类型了
auto的使用方法:
auto 变量名 = 要赋值的内容;要赋值的内容可以是任何数据类型,整型、浮点型、指针、迭代器都可以
也可以连续定义多个变量:
auto 变量1 = 要赋值的内容,变量2 = 要赋值的内容;
注意:推导时不能有二义性。即这两个变量的类型要一致,不能前边int,后边又变成了double,这样在编译时就会报错
auto的限制:
1.使用auto的时候必须对变量进行初始化,auto 变量;这样编译时会报错
2.auto不能在函数定义时使用,因为函数定义时实际上没有给形参赋值,只有实参传入时才赋值
3.auto不能定义数组
auto的应用:
可以使用auto定义迭代器:使用STL容器的时候,需要使用迭代器来遍历容器里的元素;不同容器有不同类型,定义迭代器时必须指明,而迭代器的类型又很复杂,所以在定义迭代器对象的时候可以使用auto,至于看到.begin()、.end(),必然是能看懂是迭代器的
(2)decltype类型推导
decltype和auto的功能一样,都用来在编译时期进行自动类型推导
因为auto有局限性:使用auto必须要对auto声明类型初始化,但是有时候可能需要根据表达式运行结果确定它的类型,编译时自然是做不到的
decltype正是为了解决这个问题,它可以根据表达式的实际类型推演出定义变量时所用的类型
1.推演表达式类型作为变量的定义类型
使用方法:decltype(表达式) 变量名;
括号里可写:表达式、单个变量、引用 、类的数据成员、结构体的成员变量
2.推演出函数返回值的类型
使用方法:decltype(函数名(符合函数声明的数据类型)) 变量 = 函数名(实参)
这样的话不需要知道这个函数的返回值是什么类型,直接就可以得到一个返回值
(3)基于范围的for循环
C++11为for循环添加了一种全新的语法格式:
for(declaration : expression)
{//循环体
}
解释一下代码里的两个参数:
declaration:表示此处要定义一个变量,该变量的类型为要遍历序列中存储元素的类型。这里的变量可以用auto关键字表示
expression:表示要遍历的序列,常见的可以为事先定义好的普通数组或容器,还可以是用{}初始化的序列
在使用新语法格式的for循环遍历某个序列时,如果需要遍历的同时修改序列中的元素的值,实现方案是在declaration参数处定义引用形式的变量
代码示例:
vector<int> arr = { 1,2,3,4,5,6,7,8 };
for (auto it : { 1,2,3,4,5,6,7,8 })
{cout << it << " ";
}
cout << endl;
for (auto& it : arr)
{it++;
}
for (auto it : arr)
{cout << it << " ";
}
cout << endl;
(4)列表初始化和{}初始化
列表初始化主要用于类和对象,在构造函数部分使用
C++98标准允许使用{}对数组元素进行统一的列表初始值设定
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 0 };
C++11标准提供了使用{}对标准容器的初始化
int* arr3 = new int[5] {1, 2, 3, 4, 5};
vector<int> v{ 1,2,3,4,5 };
map<int, int> m{ { 1,1 }, { 2,2 }, { 3,3 } };vector<int> v{ 1,2,3,4,5 };
map<int, int> m1 = { { 1,1 }, { 2,2 }, { 3,3 } };
使用{}对自定义类型的列表初始化
(5)using定义别名
在C和C++中可以使用typedef重定义一个类型
C++11提供了更简单的方法:using
typedef和using对比:
typedef long long ll;
using ll = long long;
typedef形式:typedef 原名 别名;
using形式:using 别名 = 原名;
这个用处并不是很大,只是一种形式,用typedef和using都可以
(6)final关键字
作用:用来修饰类表示该类不可被继承;用来修饰虚函数表示该虚函数不能被子类重写
用法:class 类名1 final; 这样的话就不能再用class 类名2 :public 类名1。编译时就会报错
virtual 函数() final; 这样的话它所处的类的派生类中就不能重写这个虚函数
应用:
提升安全性、规定好一个固定的函数不允许派生类修改实现方法
(7)右值引用
什么叫左值?什么叫右值?
左值表示可以获取地址的表达式,它能出现在赋值语句的左边并对该表达式进行赋值。加上const之后它就可以取得地址但不能赋值了
右值表示无法获取地址的对象,包括但不限于常量值、函数返回值、lambda表达式。但是它们实际上可以被改变,定义了右值的右值引用时就可以更改右值
注意:判断一个值是左值还是右值不是由这个值在赋值表达式中的位置来决定的
只要是变量,就一定是左值
使用右值引用的原因:右值往往没有名称,在实际开发中可能需要对右值进行修改,因此使用它只能借助引用的方式
C++11规定"&&"表示右值引用
右值引用的实际应用:
1.移动语义
如果一个类中涉及到资源管理,用户必须显式提供复制构造函数、赋值运算符重载、析构函数,否则编译器会自动生成默认,如果遇到复制对象或对象之间相互赋值,就会出错。比如一个对象先消亡,它的指针所指向区域可能被析构函数释放,然后另一个对象的指针就不知道会指向什么了
将一个对象中资源移动到另一个对象中的方式称为移动语义。在C++11如果要实现移动语义,必须使用右值引用
Point(Point&& p): _str(s._str)
{ s._str = nullptr; }
通过实现一个参数为右值引用的构造函数,在构造函数中没有重新分配空间,这样可以提高程序运行的效率
总结:右值引用与移动语义结合,减少无必要资源的开辟来提高代码的运行效率
核心思路:复制构造函数中使用深拷贝,移动构造函数中使用浅拷贝,将第一个指针置为空指针,然后第二个对象直接使用第一个对象的空间
2.move函数
唯一的功能就是将一个左值强制转换成右值引用,通过右值引用使用该值,实现移动语义
注意:被转化的左值声明周期并没有随着左右值的转换而改变,左值变量不会被销毁
(8)lambda表达式
作用:是一种创建匿名函数对象的简便方式,它能在需要的地方直接定义一个小型的、一次性使用的函数,而无需专门命名
用法:[捕获列表] (参数列表) -> 返回类型(可省略,编译器会自动推导) { 函数体 }
捕获列表:用于lambda表达式访问外部变量。分为值捕获、引用捕获、隐式捕获
值捕获直接在捕获列表里写变量名,只传入值,后续不影响原变量;引用捕获在变量名前加"&",在lambda表达式的函数体里改变这个外部变量的话原变量也会改变;隐式捕获是可以捕获所有外部变量,[=]是值捕获所有外部变量,[&]是引用捕获所有外部变量
参数列表:和普通函数参数列表作用一样
(9)智能指针
作用:通过封装原始指针并在对象生命周期结束是自动释放内存,避免手动管理内存出现内存泄漏
分类:unique_ptr、shared_ptr、weak_ptr
unique_ptr:对其指向的对象拥有独占所有权,同一时间只能有一个unique_ptr指向同一个对象,当这个指针被销毁时指向的对象也会被销毁
shared_ptr:采用引用计数的方式管理对象,多个shared_ptr可以指向同一个对象,每当一个新的shared_ptr指向该对象时引用计数加1,当一个shared_ptr被销毁时,引用计数减1,引用计数为0时对象自动销毁
weak_ptr:不控制对象生命周期,是shared_ptr的辅助类,解决shared_ptr可能出现的循环引用问题,可以从shared_ptr或另一个weak_ptr构造,但它不会增加引用计数
用法:指针类型<数据类型>指针名(new 数据类型(赋值))
还有一种用法是make_unique、make_shared,只不过这是C++14的标准,在这里不讲了
weak_ptr帮助shared_ptr解决循环引用的机制:在不得不使用循环引用的时候用weak_ptr替代shared_ptr,这样的话引用计数不会增加,销毁对象的时候可以减到0
篇末总结
博主在学STL的时候就在AI那里看到过这里的一些内容,当时很纳闷,因为博主上课根本没接触过这些东西,说实话C++11标准也算很老的了,但是学校都教不到。
只能说师傅领进门,修行在个人。学校教的cpp事实上的确把我领进了编程的领域,但以后我恐怕都要找资源自学了