统一的列表初始化
{ }初始化
C++98中,标准允许使用{ }对数组或者结构体对象进行统一的列表初始值设定
举个栗子
struct point { int _x; int _y; }; int main() { int arr[] = { 1,2,3,4,5 }; point p = { 1,2 }; return 0; }
C++11扩大 { }符号的使用范围,使其可用于所有的内置类型和自定义类型;使用初始化列表时,可添加赋值符号(=),也可不添加
举个栗子
int main() { vector<int> v = { 1,2,3,4,5 }; int x = { 1 }; return 0; }
std::initializer_list
上面之所以可以使用{ }对vector进行列表初始化,其实是因为在其构造函数中包括了使用initializer_list的构造函数
initializer_list其本质是也是个容器,不同点在于并不存储数据,而是提供两个迭代器指向数据的头和尾;并且initializer_list存在于所有容器的构造函数中
举个栗子:判断数据类型
int main() { auto il = { 1,2,3,4,5 }; cout << typeid(il).name() << endl; return 0; }
将initializer_list应用到其他容器中
int main() { list<int> lt = { 1,2,3 }; map<string, string> dict = { {"东","east"},{"西","west"} }; return 0; }
声明
C++11提供了多种简化声明的方式,尤其是在使用模板时
decltype
关键字decltype将变量的类型声明为表达式指定的类型
举个栗子
int main() { int a = 0; decltype(a) b; cout << typeid(b).name() << endl; return 0; }
decltype将变量a的类型声明为int,之后创建变量b
STL中的变化
增加新容器:unordered_map/multimap和unordered_set/multiset
已有容器新接口函数:移动构造和移动赋值;emplace_xxx插入接口或右值引用版本的插入接口
右值引用
左值引用和右值引用
左值是一个表示数据的表达式,可以通过获取它的地址/可以对它进行赋值;左值可以出现在赋值符号的左边或右边;左值引用就是给左值取别名
//p,a,b,*p都是左值 int* p = new int; int a = 0; const int b = 1;
右值也是表示数据的表达式,例如:字符常量,函数返回值;右值只能出现在赋值符号的右边;右值引用就是给右值取别名
C++中将右值分为两种:
纯右值:内置类型表达式的值
将亡值:自定义类型表达式的值
//0,0,i+j都是右值 int i = 0; int j = 0; int m = i + j;
注意:右值是不可以进行取地址的,但是给右值取别名后,会将右值存储到特定位置,并且可以取到该位置的地址
左值引用与右值引用比较
左值引用:
左值引用只能引用左值,不可以引用右值
被const修饰的左值即可引用左值也可以引用右值
int main() { //左值只能引用左值 int i = 0; int& ri = i; //10是右值编译报错 int& rj = 10; //const修饰的左值可以引用左值和右值 const int& ra = 10; const int& rb = i; return 0; }
右值引用
右值引用只能引用右值
右值引用可以引用move之后的左值
int main() { //右值引用只能引用右值 int&& ra = 10; //i是左值,进行右值引用会报错 int i = 0; int&& ri = i; //右值引用可以引用move之后的左值 int&& rii = std::move(i); return 0; }
右值引用使用场景和意义
上文所述中,左值引用即可引用左值又可引用右值,那么C++11为什么要提出右值引用呢???为了解决这个疑问,先来了解左值引用的意义可能会有所帮助
左值引用的意义:函数传参/函数传返回值时使用左值引用可以减少拷贝;不过这里有个前提,就是在函数栈帧销毁之后任然存在的数据才能进行引用返回,所以当待返回的数据是临时创建的变量时,就不能进行引用返回;所以不难猜测,右值引用的提出就是为了解决这个问题
接下来通过一个模拟实现string类来学习右值引用
class string { public: typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str + _size; } string(const char* str = "") :_size(strlen(str)) ,_capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, str); } void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } //拷贝构造 string(const string& s) { cout << "string(const string& s) 深拷贝" << endl; string tmp(s._str); swap(tmp); } //赋值重载 string& operator=(const string& s) { cout << "string& operator=(const string& s) 深拷贝" << endl; string tmp(s); swap(tmp); return *this; } ~string() { delete[] _str; _str = nullptr; } void reverse(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } void push_back(char ch) { if (_size >= _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reverse(newcapacity); } _str[_size] = ch; ++_size; _str[_size] = '\0'; } string& operator+=(char ch) { push_back(ch); return *this; } const char* c_str() { return _str; } private: char* _str = nullptr; size_t _size = 0; size_t _capacity = 0; };
通过一个函数先复习一下左值引用
string to_string(int value) { bool flag = 1; if (value < 0) { flag = -1; value = 0 - value; } string str; while (value > 0) { int x = value % 10; value /= 10; str += ('0' + x); } if (flag == -1) { str += '-'; } reverse(str.begin(), str.end()); return str; }
int main() { yjm::string ret = yjm::to_string(1234); return 0; }
没有右值引用时,返回临时变量还是会进行深拷贝,接下来看看当加上右值引用之后的结果又该如何?
int main() { yjm::string s1("hello yjm"); yjm::string s2(s1); //左值move之后变成右值 yjm::string s3(move(s1)); return 0; }
在没有实现右值引用时,s2调用构造函数进行初始化;s3的参数虽然是右值,但是由于构造函数是const修饰的构造函数,所以也可以调用
如果加入参数是右值引用的构造结果会怎么样呢???
//移动构造 string(string&& s) { cout << "string(string&& s) 移动构造" << endl; swap(s); } //移动赋值 string& operator=(string&& s) { cout << "string& operator=(string&& s) 移动赋值" << endl; swap(s); return *this; }
由运行结果可知:s2中的参数是左值,故匹配参数是左值引用的构造函数即深拷贝;s3中的参数是右值,故匹配参数是右值引用的构造函数即移动构造;由于参数是自定义类型,也就是将亡值,所以进行资源转移
现在回头看上面遗留的问题,当加入右值引用之后,返回临时变量是否还需要进行深拷贝呢???
通过允许结果来看,并没有进行深拷贝,只是进行了移动构造;其中编译器也进行了优化:首先变量str先拷贝构造一份临时变量,临时变量作为右值进行移动构造,编译器进行优化,直接将str识别为右值进行移动构造
同理,参数为右值引用的赋值重载也是如此,不加赘述,直接看结果
int main() { yjm::string ret; ret= yjm::to_string(1234); return 0; }
返回值先移动构造一个临时变量,临时变量作为右值再进行移动赋值
上面所学习的都是右值引用在函数返回值中的应用,其实它还可以应用到数据的插入中
举个栗子:
总结
右值引用和左值引用减少拷贝的原理不同
左值引用是取别名,直接起作用;右值引用是间接起作用,实现移动构造/移动赋值,在拷贝的过程中,如果右值是将亡值,则进行资源转移