【C++11】常见的c++11新特性(一)
文章目录
- 1. C++11 简介
- 2. 常见的c++11特性
- 3.统一的列表初始化
- 3.1initializer_list
- 4. decltype与auto
- 4.1decltype与auto的区别
- 5.nullptr
- 6.右值引用和移动语义
- 6.1左值和右值
- 6.1.1左值的特点
- 6.1.2右值的特点
- 6.1.3右值的进一步分类
- 6.2左值引用和右值引用以及区别
- 6.2.1左值引用
- 6.2.2左值引用总结
- 6.2.3右值引用
- 6.2.4右值引用总结
- 6.2.5move函数
- 6.3右值引用的使用场景及其作用
- 6.3.1移动语义
- 6.3.2移动构造
- 6.3.3移动赋值函数
- 6.3.4右值本身也是一个左值
- 6.3.5STL容器中的移动构造和移动赋值
- 6.4完美转发
- 6.4.1完美转发的介绍
- 6.4.2forward函数的使用
- 7.新的类功能
- 7.1c++11新增了两个默认成员函数
- 7.2delete和dault
- 7.3final和override关键字
1. C++11 简介
c++11,即2011年发布的新c++标准。相比于c++98和03,c++11带来了很多变化,其中包含了约140多个新特性,以及对c++03标准中大约600多个缺陷的修正。这使得c++11更像是一个从c++98/03孕育出来的一个新版本。对比于之前的版本,c++11能更好的用于系统开发和库开发、语法更加的泛华和简单化、更加稳定和安全,不仅功能更加强大,而且能提升程序员的开发效率。这也是我们学习c++11的重要原因。下面介绍常见的一些c++11特性。
2. 常见的c++11特性
- 列表初始化
- 变量类型推导
- auto
- decltype
- 范围for循环
- final与override
- 智能指针
- 新增加容器—静态数组array、forward_list以及unordered系列
- 默认成员函数控制
- 右值引用
- lambda表达式
- 包装器
- 线程库
3.统一的列表初始化
使用列表对数组或者结构体进行初始化在c++98就已经被支持使用了,例如:
struct Point { int _x; int _y; }; int main() { int array1[] = { 1, 2, 3, 4, 5 }; int array2[5] = { 0 }; Point p = { 1, 2 }; return 0; }
但是c++98的列表初始化局限于对数组和聚合类型的初始化,限制比较多,且不支持类型推导。c++11扩大了用{}(初始化列表)的使用范围,使得所有的内置类型和自定义类型都能以一种统一方式进行初始化,包括SL容器等。使用初始化列表时,可以添加等号也可以不添加,类似于构造声明。
struct Point { int _x; int _y; }; int main() { int x1 = 1; int x2{ 2 }; int array1[]{ 1, 2, 3, 4, 5 }; int array2[5]{ 0 }; Point p{ 1, 2 }; // C++11中列表初始化也可以适用于new表达式中 int* pa = new int[4]{ 0 }; return 0; }
自定义类型能支持列表初始化的原因是因为,在其类体中重载了以初始化列表类模板为参数的构造函数。以至于我们能直接使用{}构造一个对象。
3.1initializer_list
initializer_list是一个C++11提供的一个轻量级容器,专门用来接收{}内的初始化列表。本质上是一个类模板,由于模板的特性,在构造initializer_list类时,会自动推导{}里的类型,从而完成对自定义类型的构造。这个容器其实在我之前的文章里有介绍过: initializer_list的介绍。
下面给出一个伪代码样例:
vector(initializer_list l) { _start = new T[l.size()]; _finish = _start + l.size(); _endofstorage = _start + l.size(); iterator vit = _start; typename initializer_list::iterator lit = l.begin(); while (lit != l.end()) { *vit++ = *lit++; } //for (auto e : l) // *vit++ = e; }
上面自定义的vector类重载了以initializer_list模板为参数的一个构造函数,有了这个构造函数之后,就能使用{}的方式对自定义类型进行构造。分析下面代码构造过程:
vector a = { 1,2,3 };
- 编辑器先是根据{}构造出一个initializer_list对象,然后再调用vector类的构造函数。
4. decltype与auto
decltype关键字是一个类型推导工具,可以将变量的类型声明为表达式指定的类型,比如:
decltype表达式的基本用法就是后面跟上一个括号,编译器会自动推导出括号里面表达式的类型,但不执行该表达式。
4.1decltype与auto的区别
auto也是一个c++11更新的一个特性,也能用来做类型的推导,但是和decltype有很多不一样的地方:
- auto 忽略表达式的引用性质,而是推导出一个干净的类型。
- decltype完全保留表达式的类型,包括const属性和引用属性。
思考以下代码:
我们发现,auto并没有推导出引用属性,所以变量b++之后并不会影响num,本质上b只是一个int类型。而decltype能推导出引用属性,所以变量c实际上是一个int&类型,自增之后会影响num。这样证明了decltype推导类型比auto更为准确。同样,const类型的推导也是如此:
5.nullptr
nullptr是c++11新出的一个专门用来表示空属性的一个关键字。关于nullptr与NULL的区别,我在之前的文章中有介绍过。这里就不过多介绍。
6.右值引用和移动语义
6.1左值和右值
左值(Lvalue)和右值(Rvalue)不单单是指某个变量的属性,而是可以用来描述表达式的属性。
- 左值的定义:左值是具有持久存储位置的表达式。换句话说,左值是指那些在内存中具有明确地址的对象,且这个地址持久不变。左值可以出现在赋值表达式的左侧或者右侧。最直观的理解就是左值能用&取出地址。
- 右值的定义: 右值和左值相反,是指不具有持久存储位置的表达式。 右值通常都是临时的,生命周期非常短,比如匿名对象等临时值。最直观的理解就是右值不能用&取出地址。
给出代码样例观察对左值和右值取地址:
我们可以看到,由于10是一个字面量即右值,不能对其取地址。编译器也提示&只能对左值取地址。
6.1.1左值的特点
- 可以用&取地址
- 通常表示对象的身份如变量名
6.1.2右值的特点
- 不能用&取出地址
- 只能在赋值表达式的右边
- 表示的是数据的值,而不是身份
- 通常是临时值、字面量等
6.1.3右值的进一步分类
右值又可以分为存右值和将亡值:
- 纯右值:表示不对应任何对象的临时值,例如字面量或者表达式“1+2”这种。
- 将亡值:表示即将被摧毁、可以被移动的对象。
6.2左值引用和右值引用以及区别
6.2.1左值引用
左值引用使用一个&表示,通常只能绑定给左值。下面给出常见的左值引用左值的例子:
int main() { // 以下的p、b、c、*p都是左值 int* p = new int(0); int b = 1; const int c = 2; // 以下几个是对上面左值的左值引用 int*& rp = p; int& rb = b; const int& rc = c; int& pvalue = *p; return 0; }
6.2.2左值引用总结
int& getTemporary() { int temp = 10; return temp; // 返回局部变量的引用 } int main() { int& ref = getTemporary(); // 悬空引用 std::cout double x = 1.1, y = 2.2; // 以下几个都是常见的右值 10; x + y; fmin(x, y); // 以下几个都是对右值的右值引用 int&& rr1 = 10; double&& rr2 = x + y; double&& rr3 = fmin(x, y); // 这里编译会报错:error C2106: “=”: 左操作数必须为左值 10 = 1; x + y = 1; fmin(x, y) = 1; return 0; } double x = 1.1, y = 2.2; int&& rr1 = 10; const double&& rr2 = x + y; rr1 = 20; rr2 = 5.5; // 报错 return 0; } public: size_t size; int* data; // 构造函数 Buffer(size_t s=10) : size(s), data(new int[s]) { std::cout std::memcpy(data, other.data, size * sizeof(int)); std::cout delete[] data; std::cout // 对缓冲区进行处理 } int main() { Buffer buf1(100); processBuffer(buf1); // 调用复制构造函数 return 0; } other.size = 0; other.data = nullptr; std::cout std::memcpy(data, other.data, size * sizeof(int)); std::cout if (this != &other) { delete[] data;//释放当前资源 size = other.size; data = other.data; other.data = nullptr; other.size = 0; cout if (this != &other) { Buffer temp(other);//构造一个临时对象 delete[] data;//释放自己的资源 swap(temp.data, data); swap(size, temp.size); } return *this; } cout cout cout cout Fun(t); } int main() { PerfectForward(10); //右值 int a; PerfectForward(a); //左值 PerfectForward(std::move(a)); // 右值 const int b = 8; PerfectForward(b); //const 左值 PerfectForward(std::move(b)); // const 右值 return 0; } Fun(forward public: Person(const char* name = "", int age = 0) :_name(name) , _age(age) {} Person(const Person& p) = delete;//编译器不再自动生成拷贝构造函数 private: bit::string _name; int _age; }; public: Person(const char* name = "", int age = 0) :_name(name) , _age(age) {} Person(const Person& p) = default;//强制生成拷贝构造函数 private: bit::string _name; int _age; };
- 编辑器先是根据{}构造出一个initializer_list对象,然后再调用vector类的构造函数。