目录
在在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
- int array1[] = { 1, 2, 3, 4, 5 };
- int array2[5] = { 0 };
- struct List
- {
- int val;
- char arr[10];
- };
-
- List p = { 1,"hello" };
- class Date
- {
- public:
- Date(int year, int month, int day)
- :_year(year)
- , _month(month)
- , _day(day)
- {
- cout << "Date(int year, int month, int day)" << endl;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
-
- int main()
- {
- int x1 = 1;
- int x2{ 2 };
- int array1[]{ 1, 2, 3, 4, 5 };
- int array2[5]{ 0 };
- List p{ 1, 2 };
-
- int* p1 = new int(99);
- cout << *p1 << endl;
- int* p2 = new int[3]{ 1,3,4 };
- cout << p2[0] << p2[1] << p2[2]<
-
-
- int* pa = new int[4]{ 0 };// C++11中列表初始化也可以适用于new表达式中
- Date d1(2022, 1, 1); // old style
- Date d2{ 2022, 1, 2 }; // C++11支持的列表初始化,这里会调用构造函数初始化
- Date d3 = { 2022, 1, 3 };//隐式类型的转换 + 优化
-
- Date* d4 = new Date[2]{ {2022,9,21},{2022,9,11} };
-
- return 0;
- }
结果:

1. 2 std::initializer_list
std::initializer_list
一般是作为构造函数的参数,
C++11
对
STL
中的不少容器就增加
std::initializer_list
作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为
operator=
的参数,这样就可以用大括号赋值。
模拟实现的
vector
也支持
{}
初始化和赋值
- template<class T>
- class Vector {
- public:
- typedef T* iterator;
- Vector(initializer_list<T> p)
- {
- _start = new T[p.size()];
- _finish = _start + p.size();
- _endofstorage = _start + p.size();
- iterator vit = _start;
- typename initializer_list<T>::iterator lit = p.begin();
- while (lit != p.end())
- {
- *vit++ = *lit++;
- }
-
- }
- Vector<T>& operator=(initializer_list<T> p)
- {
- Vector<T> tmp(p);
- std::swap(_start, tmp._start);
- std::swap(_finish, tmp._finish);
- std::swap(_endofstorage, tmp._endofstorage);
- return *this;
- }
- private:
- iterator _start;
- iterator _finish;
- iterator _endofstorage;
- };
二.声明
2.1decltype
关键字
decltype
将变量的类型声明为表达式指定的类型。
例如:
2.2nullptr
由于
C++
中
NULL
被定义成字面量
0
,这样就可能回带来一些问题,因为
0
既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑,
C++11
中新增了
nullptr
,用于表示空指针。
- #ifndef NULL
- #ifdef __cplusplus
- #define NULL 0
- #else
- #define NULL ((void *)0)
- #endif
- #endif
三.STL中的变化
array:越界一定可以检查出来,访问更安全。
forward_list:

....................
四.右值引用和移动语义
4.1 左值引用和右值引用
C++之前存在左值引用,而C++11新增了右值引用,二者都是给对象取别名。
左值引用介绍:
左值表示一个数据的表达式,可以获取它的地址,赋值,左值只可以出现在=的左边。
举例:
- int main()
- {
- // 以下的p、b、c、*p都是左
- int* p = new int(999);
- int b = 1;
- const int c = 2;
-
- // 以下几个是对上面左值的左值引用
- int*& rp = p;
- int& rb = b;
- const int& rc = c;
- int& pvalue = *p;
- return 0;
- }
右值的介绍:
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值
(
这个不能是左值引
用返回
)
等等,
右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址
。
例如:
- int main()
- {
- 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;
- }
补充:
右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址。
例如上面的10,不能取字面量
10
的地址,但是
rr1
引用后,可以对
rr1
取地
址,也可以修改
rr1
。如果不想
rr1
被修改,可以用
const int&& rr1
去引用。
4.2左值引用与右值引用比较
左值引用总结:
1.
左值引用只能引用左值,不能引用右值。
2.
但是
const
左值引用既可引用左值,也可引用右值。
举例:
- int main()
- {
- int p = 99;
- int& k1 = p;
- /* int& k2 = 99;*///报错
-
- const int& k1 = p;
- const int& k2 = 99;
- return 0;
- }
右值引用总结:
1. 右值引用只能引用右值,不能引用左值。
2.
但是右值引用可以引用
move
以后的左值。
举例:
- int main()
- {
- // 右值引用只能右值,不能引用左值。
- int&& r1 = 10;
- int a = 10;
- int&& r2 = a;// error C2440: “初始化”: 无法从“int”转换为“int &&”, message : 无法将左值绑定到右值引用
- int&& r3 = std::move(a);// 右值引用可以引用move以后的左值
- return 0;
- }
4.3左值引用的场景
先来回顾下左值引用的场景,以之前模拟实现的string进行比较。
- class String
- {
- public:
- typedef char* iterator;
-
- String(const String& s)// 拷贝构造
- :_str(nullptr)
- , _size(0)
- , _capacity(0)
- {
- cout << "String(const String& s) -- 深拷贝" << endl;
-
- String tmp(s._str);
- swap(tmp);
- }
-
- String& operator=(const String& s)// 赋值重载
- {
- cout << "String& operator=(String s) -- 深拷贝" << endl;
- string tmp(s);
- swap(tmp);
-
- return *this;
- }
- void swap(String& s)// s1.swap(s2)
- {
- ::swap(_str, s._str);
- ::swap(_size, s._size);
- ::swap(_capacity, s._capacity);
- }
-
- ~String()
- {
- delete[] _str;
- _str = nullptr;
- }
- String& operator+=(char ch)
- {
- push_back(ch);
- return *this;
- }
- ...........
- ...........
- //其余函数的实现
-
- private:
- char* _str;
- size_t _size;
- size_t _capacity;
- };
上面String类中的那些拷贝构造中的参数,赋值重载的返回值等,都用了&,这样可以减少拷贝带来的消耗。但也存在一些函数,是不能用左值引用的。
当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,(C++98中)只能传值返回。传值返回会导致至少1
次拷贝构造
(
如果是一些旧一点的编译器可能是两次拷贝构造
)
。
例如:
-
- String to_String(int value)
- {
- bool flag = true;
- if (value < 0)
- {
- flag = false;
- value = 0 - value;
- }
-
- String str;
- while (value > 0)
- {
- int x = value % 10;
- value /= 10;
-
- str += ('0' + x);
- }
-
- if (flag == false)
- {
- str += '-';
- }
-
- std::reverse(str.begin(), str.end());
- return str;//str是一个临时对象
- }
-
- String s=to_String(1234);//会用str拷贝一个临时对象,再用那个临时对象拷贝构造s,优化后只有一次,
4.4右值引用的场景
右值也可分为纯右值,将亡值。
例如:
- 10;//纯右值
- x+y;
-
- string("hello"),//将亡值,只该变量马上要被销毁
- string s("hello);
- move(s);
上面String的模拟实现中,拷贝构造函数可以增加一个移动构造函数。
- String(String&& s)
- :_str(nullptr)
- , _size(0)
- , _capacity(0)
- {
- cout << "String(String&& s) -- 资源转移" << endl;
- swap(s);
- }
上面所用的是用右值去接受,可以认为s是一个将亡值,马上会被销毁,没必要去做深拷贝,只需将二者的资源进行转移即可(交换指针)。

上面的to_String函数中,返回的str是一个临时对象,出作用域后会被销毁,增加了移动构造函数后,(此处可认为编译器做了优化,会将str识别为右值,因为它马上要被销毁),则会去匹配最适合的拷贝构造函数,这时会去调用移动构造函数,这样便减小了开销,(仅仅交换了指针,没有再去开辟空间)。
移动赋值:
对于一个已经存在的对象时,用一个即将被销毁的对象去赋值给它,也没必要去深拷贝。
- String& operator=(String&& s)
- {
- cout << "String& operator=(String&& s) -- 资源转移" << endl;
- swap(s);
- return *this;
- }
在c++11后,许多的容器中的函数都增加了右值的版本,如果情况适合,调用右值的版本就可以减少开空间的开销。
可以看下一:



也可以用代码测试一下:

常用的swap函数:

五.完美转发
5.1模板中的&& 万能引用
先看一个简单的例子:
- void show(int& x) { cout << "左值引用\n"; }
- void show(int&& x) { cout << "右值引用\n"; }
- void show(const int& x) { cout << "const左值引用\n"; }
- void show(const int&& x) { cout << "const右值引用\n"; }
-
-
- template <class T>
- void Fun(T&& x)
- {
- show(x);
- }
-
- int main()
- {
- int a = 10;
- const int b = 100;
- Fun(a);//左值
- Fun(b);
-
- Fun(999);//右值
- Fun(move(a));
- return 0;
- }
结果:
- 左值引用
- const左值引用
- 左值引用
- 左值引用
解释:
模板中的 && 不代表右值引用,而是万能引用,其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。
例如:int&& rr1 = 10; 10是右值,rr1是开了一块空间把10存储起来,是可以取地址的,则rr1是左值)
5.2完美转发
对上面的代码进行更改后:
- void Fun(T&& x)
- {
- show(forward
(x));//完美转发 -
- }
结果:
- 左值引用
- const左值引用
- 右值引用
- 右值引用
完美转发在传参的过程中保留对象原生类型属性 ,若有多次传参,要多次使用完美转发。
6.新的类功能
默认成员函数
原来
C++
类中,有
6
个默认成员函数:
1.
构造函数
2.
析构函数
3.
拷贝构造函数
4.
拷贝赋值重载
5.
取地址重载
6. const
取地址重载
C++11
新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
意一个
。那么编译器会自动生成一个默认移动构造。
默认生成的移动构造函数,对于内置类
型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。
(
默认移动赋值跟上面移动构造
完全类似
)
如果
你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
强制生成默认函数的关键字
default:
可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原
因这个函数没有默认生成。比如:
我们提供了拷贝构造,就不会生成移动构造了,那么我们可以
使用default关键字显示指定移动构造生成。
禁止生成默认函数的关键字delete:
在
C++98
中,是该函数设置成
private
,并且只声明补丁
已,这样只要其他人想要调用就会报错。在
C++11
中更简单,
只需在该函数声明加上=delete即
可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
7.可变参数模板
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板

上面的参数
args
前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为
“
参数
包
”
,它里面包含了
0
到
N
(
N>=0
)个模版参数。我们无法直接获取参数包
args
中的每个参数的,
只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特
点,也是最大的难点,即如何展开可变模版参数.
先看一个简单的例子:
- template <class ...Args>
- void ShowList1(Args... args)
- {
- // 参数个数
- cout << sizeof...(args) << endl;//计算的是参数包中形参的个数
- }
-
- int main()
- {
- ShowList1(1,2,3,'x', 1.1);
- }
-
- 结果:5

上面的例子中,先将1传给T,参数包中还剩下4个参数。最后会匹配无参的那个函数。
若参数相同,也可直接进行初始化:

也可以这样使用:
