C++11是C++的第二个主要版本,并且是从C++98起的最重要更新。它引入了大量更改,标准化了既有实践,并改正了C++程序员可用的抽象。在它最终由ISO在2011年8月12日采纳前,人们曾使用名称“C++0x”,因为它曾被期待在2010年之前发布。C++03与C++11期间花了8年时间,故而这是迄今为止最长的版本间隔。从那时起,C++有规律地每3年更新一次

ok,接下来,我们就开始学习C++11的相关知识~
也许会有很多UU看到这个标题之后,会想到我们之前学的初始化列表,ok,初始化列表和列表初始化列表是没有任何关系的,这两个不是一回事儿~
在C++98中,我们通常使用 { } 进行数组或者结构体的初始化——
struct A
{
int _a1;
int _a2;
};
void test1()
{
//使用{ }对数组初始化
int a[] = { 1,2,3,4,5,6,7 };
//使用{ } 对结构初始化
A b={ 1,2};
}但是在C++11中使用{ } 的方法更高级点——
ok,我们通过代码来看一下——

其实我们看到这个 { } 初始化对于内置类型的便捷之处好像没有那么明显,那我们接着看对于自定义类型——
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
private:
int _year;
int _month;
int _day;
};
void Insert(const Date& d)
{
}
Date func()
{
/*Date d{ 2025,11,16 };
return d;*/
//return { 2025,11,16 };
//返回默认构造
Date d;
return d;
//return {};
}
int main()
{
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
Date d2 = 1025;//单参数的隐式类型转换
Date d2 = { 2025,11,17 };
Date d3{ 2025,11,17 };
Date d4{};//调用默认构造,用缺省值
Date d5; //也是调用默认构造,用缺省值
Insert(2025);
Insert({ 2025,11,17 });
// C++11⽀持的
// 内置类型⽀持
int x1 = { 2 };
// ⾃定义类型⽀持
// 这⾥本质是⽤{ 2025, 1, 1}构造⼀个Date临时对象
// 临时对象再去拷⻉构造d1,编译器优化后合⼆为⼀变成{ 2025, 1, 1}直接构造初始化d1
// 运⾏⼀下,我们可以验证上⾯的理论,发现是没调⽤拷⻉构造的
Date d1 = { 2025, 1, 1 };
// 这⾥d2引⽤的是{ 2024, 7, 25 }构造的临时对象
const Date& d2 = { 2024, 7, 25 };
// 需要注意的是C++98⽀持单参数时类型转换,也可以不⽤{}
Date d3 = { 2025 };
Date d4 = 2025;
// 可以省略掉=
Point p1{ 1, 2 };
int x2{ 2 };
Date d6{ 2024, 7, 25 };
const Date& d7{ 2024, 7, 25 };
// 不⽀持,只有{}初始化,才能省略=
// Date d8 2025;
vector<Date> v;
v.push_back(d1);
v.push_back(Date(2025, 1, 1));
// ⽐起有名对象和匿名对象传参,这⾥{}更有性价⽐
v.push_back({ 2025, 1, 1 });
return 0;
}


我们可以从图中看到,内置类型同样也可以用{ }初始化,但是我们看习惯了直接赋值,那么这里还要用{ }初始化的意义是什么呢?
上面的初始化已经很方便,但是对象容器初始化还是不太方便,比如一个 vector 对象,我想用 N 个值去构造初始化,那么我们得实现很多个构造函数才能支持,vector<int> v1 = {1,2,3};vector<int> v2 = {1,2,3,4,5};
initializer_list - C++ Reference
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int> v2{ 7,8,9 };
//vector(initializer_list<T> l) vector传给initializer_list<T>
//{
// for (auto e : l)
// push_back(e)
//}
map<string, string>dict = { {"sort","排序"},{"string","字符串"} };
v1 = { 10,20,30 };
auto il = { 10,20,30 };
cout << typeid(il).name() << endl;
std::initializer_list<int> mylist;
mylist = { 10, 20, 30 };
cout << sizeof(mylist) << endl;
// 这⾥begin和end返回的值initializer_list对象中存的两个指针
// 这两个指针的值跟i的地址跟接近,说明数组存在栈上
int i = 0;
cout << mylist.begin() << endl;
cout << mylist.end() << endl;
cout << &i << endl;
return 0;
}在C++98中的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,C++11之后我们就将之前学习的引用叫做左值引用。但是无论是左值引用还是右值引用,都是给对象去别名。(并且底层都是由指针实现的)
ok,那接下来,我们就来看看什么是左值,什么是右值?
左值是一个数据的表达式:
一般是有持久状态,存储在内存中,我们就可以获取它的地址。
左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边。定义时const修饰符后的左值,不能给它赋值,但是可以取它的地址
左值通常具有一个相对持久的一个状态,比如说是一个局部变量,他至少在当前的函数的栈帧里面是持续存在的
总结:左值的关建在于:可以取地址的都是左值
int main()
{
//左值:可以取地址
int* p = new int(0);
int b = 1;
const int c = b;
//c = 2; //定义时const修饰符后的左值,不能给它赋值,但是可以取它的地址
*p = 10;
string s("11111");
s[0] = 'x';
cout << &p << endl;
cout << &b << endl;
cout << &c << endl;
cout << &s << endl;
cout <<(void*) & s[0] << endl;
return 0;
}右值也是一个表示数据的表达式:
右值可以出现在赋值符号的右边,但是一般不能出现在赋值符号的左边,右值是不能取地址的
总结:右值是不能取地址的!!!
int main()
{
//右值是不能取地址的
double x = 1.1, y = 2.2;
x + y;//表达式
10;//常量
string("11111");//类型转换
//取地址会报错
//cout << &10 << endl;
//cout << &(x+y) << endl;
//cout << &string("11111") << endl;
}左值的英文简写为lvalue,右值的英文简写为rvalue。传统认为它们分别是left value、right value的缩写。现代C++中,lvalue被解释成loactor value的缩写,可意为存储在内存中、有明确存储地址可以取地址的对象,而rvalue被解释为read value,指的是那些可以提供数据值,但是不可以寻址,例如:临时变量,字面量常量,存储于寄存器中的变量等。
也就是说左值和右值的核心区别就是能否取地址(可以取地址的就是左值,不能取地址的就是右值)
通过上面,我们知道,所谓引用无非就是取别名:
那左值引用是否可以引用右值,右值引用是否可以引用左值呢?我们接着往下学——
左值引用和右值引用相对应的写法:
左值引用就是给左值取别名,右值引用就是给右值取别名。
那左值引用是否可以引用右值,右值引用是否可以引用左值呢?
其实是可以的,只是需要在限定的条件下——
为什么左值引用不能直接引用右值,但是const左值引用可以引用右值?
这里就涉及权限问题,我们知道右值是一些常量、临时变量……,常量和临时变量都是不能被修改的,如果左值引用可以直接引用右值,那就导致右值的权限被放大(这是被允许的)
move是库里面的一个函数模板,本质是进行强制类型转换,move(左值)就是将属性从左值转换成右值
int main()
{
//左值:可以取地址
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("11111");
s[0] = 'x';
//右值是不能取地址的
double x = 1.1, y = 2.2;
x + y;//表达式
10;//常量
string("11111");//类型转换
//左值引用给左值取别名
int& r1 = b;
int*& r2 = p;
const int& r3 = c;
int& r4 = *p;
string& r5 = s;
char& r6 = s[0];
//右值引用给右值取别名
double&& rr1 = x + y;
int&& r2 = 10;
string&& r3 = string("11111");//引用类型转换中间产生临时对象
//左值引用不能直接引用右值,但是const左值引用可以引用右值
const double& p1 = x + y;
const int& p2 = 10;
const string& p3 = string("11111");
//右值引用不能直接引用左值,但是右值引用可以引用move(左值)
int*&& pp1 = move(p);
int&& pp2 = move(b);
const int&& pp3 = move(c);
int&& pp4 = move(*p);
string&& pp5 = move(s);
char&& pp6 = move(s[0]);
}注意:(后面会有大用处!!!)

右值引用可用于为临时对象延长生命周期,const的左值引用也可以延长临时对象生存期,但这些临时对象无法被修改

上面所说修改的是s1+s1临时变量,通过s1+s1的别名——r3 来修改!!!
我们通过代码来验证一下:
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
A aa1;
//延长匿名对象A()的生命周期,延长到和ref1,ref2一样
const A& ref1 = A();
A&& ref2 = A();
cout << "main end" << endl;
return 0;
}
在C++98中,我们实现一个const左值引用作为参数的函数,那么实参传递左值和右值都是可以匹配的
void f(const int& x)
{
cout << "const int& x" << endl;
}
int main()
{
int a = 20;//左值
f(a);
f(10);//实参传递右值
return 0;
}运行结果:

只有const 左值引用作为参数的函数,那么实参传递左值和右值都是可以匹配的
但是C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的函数,会是什么情况
//形参左值引用
void f(int& x)
{
cout << "左值引用重载" << endl;
}
//形参const左值引用
void f(const int& x)
{
cout << "到 const 的左值引⽤重载 f(" << x << ")\n";
}
//形参右值引用
void f(int&& x)
{
cout << "右值引⽤重载 f(" << x << ")\n";
}
int main()
{
int a = 20;//左值
const int b = 30;
f(a);//调用f(int&)
f(b);//调用f(const int&)
f(10);//调用f(int&&),如果没有f(int&&)重载则会调用f(const int&)
}运行结果——

通过上面的代码,我们可以看出当分别重载左值引用、const左值引用、右值引用作为形参的函数,左值会去调用左值引用的,const左值会去调用const左值引用的,右值会去调用右值引用的
ok,通过上面的代码我们还能验证上面的:右值引用本身的属性是左值

学到这里,不知道有没有uu会有这种想法:为什么要有右值引用?我们左值引用不是用的挺好的嘛,还要搞右值引用干嘛?
ok,既然搞了右值引用,那肯定就说明有些东西仅靠左值引用是无法完成的,必须要靠右值引用才能完成,左值引用和右值引用可以提高效率

左值引用主要使用场景是在函数中左值引用传参和左值引用传返回值时减少拷贝,同时还可以修改实参和修改返回对象的价值。
左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回,如下面的addStrings和generate函数,返回值是一个临时对象,出了函数就销毁了,这种情况下C++98中的解决方案只能是被迫使用传值返回(但是代价有点太大了)
class Solution {
public:
// 传值返回需要拷⻉
string addStrings(string num1, string num2) {
string str;
int end1 = num1.size() - 1, end2 = num2.size() - 1;
// 进位
int next = 0;
while (end1 >= 0 || end2 >= 0)
{
int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
int ret = val1 + val2 + next;
next = ret / 10;
ret = ret % 10;
str += ('0' + ret);
}
if(next == 1)
str += '1';
reverse(str.begin(), str.end());
return str;
}
};
class Solution {
public:
// 这⾥的传值返回拷⻉代价就太⼤了
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv(numRows);
for (int i = 0; i < numRows; ++i)
{
vv[i].resize(i + 1, 1);
}
for (int i = 2; i < numRows; ++i)
{
for (int j = 1; j < i; ++j)
{
vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
}
}
return vv;
}
};那么C++11以后这里可以使用右值引用作为返回值解决问题吗?显然是不能的。

这里的本质是返回对象是一个局部对象,函数结束这个对象就析构销毁了,右值引用也无法实现访问销毁的对象
虽然在编译器的优化下,代价不是那么大。

但是编译器优不优化不是标准规定的,在一些比较新的编译器中会有优化,在一些老的编译器中就没有优化,那我们该咋搞呢?那就要靠下面的利器——
移动构造函数是一种构造函数,类似拷贝构造函数,移动构造函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用,如果还有其他参数,额外的参数必须有缺省值
也就是说:构造是对左值引用(&),移动构造是对右值引用(&&)
移动赋值是一个赋值运算符的重载,它跟拷贝复制构成函数重载,类似拷贝赋值函数,移动赋值函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用
也就是说:移动赋值是对右值拷贝,拷贝赋值是对左值赋值拷贝
现在呢?如果是右值呢,他就不想去拷贝了,而是想去完成移动。这是啥意思?

对于自定义类型,以前只有左值时,对于函数返回值为局部对象,我们只能去调用拷贝构造——

但是呢,好像有点浪费~
大佬们是这样搞的——
既然你是一个右值,并且马上就要析构了,我就不去执行拷贝了,直接把你的资源转移给我,然后我再去转移给其他人,这样就不浪费了~

在上面参数匹配的时候——

所以右值引用的核心意义是为了区分出左值和右值!!!
对于左值,我们就执行拷贝那一套;对于右值,我们就不拷贝,而是进行资源的转移
对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类,移动构造和移动赋值才有意义。
因为移动构造和移动赋值的第⼀个参数都是右值引用的类型,他的本质是要“窃取”引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,从而提高效率。



图1


图2




C++11有了移动构造和移动赋值,有了移动构造和移动赋值的代价足够的低!!!
ok,也许会有uu会有这种想法:
传值返回在编译器的优化下已经没有拷贝了,是否意味上面的右值引用和移动语义就没意义了?

但是当我们运行上面代码的时候,会发现调不到右值引用的insert。
传右值时可以调用右值的push_back,但是右值的push_back复用右值的insert时,却调不到右值引用的insert,这是什么原因?

也就是说——

x的本身属性是左值,那么调用的insert还是左值的那个
我们先来看看为什么要这么设计?


解决方案:

#include"List.h"
int main()
{
bit::list<bit::string> lt;
cout << "*************************" << endl;
bit::string s1("111111111111111111111");
lt.push_back(s1);
cout << "*************************" << endl;
lt.push_back(bit::string("22222222222222222222222222222"));
cout << "*************************" << endl;
lt.push_back("3333333333333333333333333333");
cout << "*************************" << endl;
lt.push_back(move(s1));
cout << "*************************" << endl;
return 0;
}
都看到这里啦!那请大佬不要忘记给博主来个“一键三连”哦!
૮₍ ˶ ˊ ᴥ ˋ˶₎ა