C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型。
- struct Point
- {
- int _x;
- int _y;
- };
- 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
- int x1 = 1;
- int x2={ 2 };
- //数组
- int array1[]{ 1, 2, 3, 4, 5 };
- int array2[5]{ 0 };
- //自定义类型
- Point p{ 1, 2 };
- Date d1(2022,11,14);//用构造函数初始化
- Date d2{2022,11,14};//列表初始化
- // C++11中列表初始化也可以适用于new表达式中
- int* pa = new int[4]{ 0 };
- return 0;
- }
C++11中对{1,3,4}这样的列表定义了一个新的类型 -- initializer_list
auto il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
并在库中的容器的里支持了用initializer_list支持的构造函数。vector、list、map等都可以用它进行初始化。
vector v = { 1,2,3,4 };
list lt = { 1,2 };
map dict = { {"sort", "排序"}, {"insert", "插入"} };
自定义类型可以支持多个对象初始化,只需要增加initializer_list类型的构造函数即可。
自动推断类型。
auto不能推导函数参数的类型,因为在函数编译阶段,还没有传递参数,就无法推演出形参的实际类型。
C+11中已经去除了auto声明自动类型变量的功能,只可以用来进行变量类型推导。
关键字decltype将变量的类型声明为表达式指定的类型。
- int main()
- {
- const int x = 1;
- double y = 2.2;
- decltype(x * y) ret; // ret的类型是double
- decltype(&x) p; // p的类型是int*
- cout << typeid(ret).name() << endl;
- cout << typeid(p).name() << endl;
- return 0;
- }
array --->静态数组
forward_list --->单链表
unordered_map --->哈希表
unordered_set --->哈希表
左值:可以取它的地址的就是左值。(左值除了被const修饰的 其余都可以修改)
右值:不能出现在赋值运算符左边的,不能被取地址的。
例如:字面常量、表达式返回值、函数返回值等。
10 x+y func(x,y)
- int main()
- {
- 10;
- x + y;
- Func(x, y);
- int&& rr1 = 10;
- double&& rr2 = x + y;
- double&& rr3 = Func(x, y);
- }
tip:右值不能被获取地址,但是一旦被引用之后,就可以通过对引用的取地址/修改来影响右值。但这不是重点。
无论左值引用还是右值引用,都是给对象取别名。
左值引用只能引用左值,不能引用右值。但左值引用加了const后就可以引用右值了。
const int& ra3 = 10;
右值引用只能引用右值,不能引用左值。但是可以引用move后的左值。
int&& r3 = std::move(a);
之所以使用左值引用,目的是:
1、减少函数参数调用以及做返回值时的拷贝构造,以提高效率。
2、做输出型参数,修改返回对象。
短板:但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回, 只能传值返回。
在我们以前的实现中,当我的返回值类型非常复杂时,比如vector
拿一个string类进行说明。主要关注对象是移动构造、和移动拷贝。
- // 拷贝构造
- string(const string& s)
- :_str(nullptr)
- {
- cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;
-
- string tmp(s._str);
- swap(s);
- }
-
- // 移动构造
- string(string&& s)
- :_str(nullptr)
- , _size(0)
- , _capacity(0)
- {
- cout << "string(string&& s) -- 资源转移" << endl;
- swap(s);
- }
拷贝构造传入的参数是引用类型,以后还需要继续使用。所以不能直接交换,需要创建一个对象,用这个对象进行交换。
既然局部对象拷贝后,本来就需要销毁。那还为什么要创建一个新的临时变量以完成交换呢?我们直接使用这个局部对象进行交换不就好了吗?
如果实现了移动构造,在局部对象即将出作用域的时候,就会被识别称为将亡值,从而调用移动构造,减少了一次创建对象并拷贝的过程。
- namespace chy
- {
- class string
- {
- public:
-
- // 拷贝赋值
- string& operator=(const string& s)
- {
- cout << "string& operator=(string s) -- 拷贝赋值(深拷贝)" << endl;
- string tmp(s);
- swap(tmp);
-
- return *this;
- }
-
- // 移动赋值
- string& operator=(string&& s)
- {
- cout << "string& operator=(string s) -- 移动赋值(资源移动)" << endl;
- swap(s);
-
- return *this;
- }
- void swap(string& s)
- {
- ::swap(_str, s._str);
- ::swap(_size, s._size);
- ::swap(_capacity, s._capacity);
- }
- 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;
- }
- private:
- char* _str;
- size_t _size;
- size_t _capacity;
- };
- }
-
移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
当我们调用to_string()函数时,编译器会将其的返回值识别成右值,从而调用类型最匹配的移动拷贝。如果没实现移动构造和移动赋值,那依然走的是深拷贝。
- int main()
- {
- chy::string ret;
- ret=to_string(-3456); //移动赋值
- chy::string ret2=chy::to_string(-1234);//移动拷贝
- return 0;
- }
一些例子
- int main()
- {
- chy::string str1("hello");//拷贝构造
- chy::string str2(str1); // 拷贝构造
- chy::string str3(move(str1)); // 移动构造
-
-
- std::string s1("hello world");//拷贝构造
- std::string s2(s1); // 拷贝构造
-
- // std::string s3(s1+s2);
- std::string s3 = s1 + s2; // 移动构造
- std::string s4 = move(s1);//移动构造
-
- return 0;
- }