一、引言
C++11是C++的一个非常重要的版本,在当时C++11问世的时候提供了非常具有创新性的编程方案供其他编程语言学习。比如说auto自动推导类型、范围for、右值引用、lambda表达式、装载器、绑定器等等都是C++11的杰作。由于篇幅较长,所以C++11会分为三个阶段,越往后难度会越大。
提示:内容有点抽象,大家可以跳过观看。
二、C++11的初阶语法
也就是使用{}花括号初始化。花括号会将数据自动转化为要初始化的数据。这个在我们之前也有运用。具体就是隐式类型转换,编译器根据等式两边自动转化成用户想用的类型。
auto能够自动推导类型进而去创建变量或是引用。例如:auto a = 1;推导出的类型就是int。并且auto可以作为函数返回值,但我们一般不推荐这么写,因为每个函数返回值都是auto,那那我们下次再改的时候,根本就不清楚这个类型是什么还要去查。
如果用户要遍历数组等容器的话,每次都要写一个for循环是非常麻烦,而且还容易搞错范围出错。所以C++特地为用户准备了范围for,可以很方便简单就遍历好容器内存储的数据。
#include <iostream>
#include <vector>
int main()
{
// 列表初始化。
std::vector<int> v = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 };
// 范围for
// auto自动推导类型。
for(auto& e : v)
{
std::cout << e << " ";
}
std::cout << std::endl;
return 0;
}右值引用和左值引用是相对的,C++11发布右值引用旨在解决左值引用无法解决的返回值引用问题。返回类型为什么会返回呢?按道理来讲返回值已经随着函数栈帧的销毁二销毁了(函数走到头了)。怎么会返回呢?其实返回的是一个CPU寄存器上的数据,CPU寄存器又不是内存或是硬盘,当然不支持修改,所以我们想要左值引用就要加上const常属性。但是返回的数据是一块很大的内存,是不是代表我们要么加上const左值引用无法修改它,要么就要费老半天功夫去拷贝返回值,和你不划算。所以才有了右值引用。
在没有右值引用之前很多程序员大佬们都非常喜欢使用输出型参数的概念。就是将一个指针或者一个包含指针的类左值引用来讲返回值带出来。指针指向的地址和左值引用出来的类都不属于函数栈帧。
我们将左值引用和右值引用对比着来讲,大家能根据左值引用和右值引用的不同来说明右值引用的作用。
与左值引用不同的是右值引用可以引用常量表达式、临时对象。右值引用引用的是右值属性,但是右值本身就具有左值属性,可以改值。这么看的话和指针很像,但是它的地址并没有改变。左值引用和右值引用的本质都是指针,右值引用作用是延长变量的生命周期,左值引用也可以延长变量生命周期,但是不能改变值。其实可以认为下面代码中的右值引用时创建了一个新的对象、变量,进行赋值修改。
#incldue <iostream>
int main()
{
// 左值引用一个常量是不可取的。
// int& a = 10;
// 但是右值引用可以引用常量或是运算式,弥补了一些左值引用不能减少返回值拷贝的不足。
// 引用常数:
int&& a = 10;
// 引用表达式:
int&& a1 = 10 + 12;
// 引用表达式:
int&& a3 = a1 + a;
std::cout << "ptra" << &a << std::endl;
a = 11;
std::cout << "a:" << a << std::endl;
std::cout << "a1:" << a1 << std::endl;
std::cout << "a3:" << a3 << std::endl;
std::cout << "ptra" << &a << std::endl;
return 0;
}右值引用引用左值,我们上面说右值引用要求对对象的引用类型是右值类型,左值很明显是左属性,如果右值引用和左值引用不能转化,那C++委员会的大佬又有什么必要创造出右值引用。我们在一些不方便修改左值引用的过程中使用右值引用修改,还是挺方便的。
#incldue <iostream>
int main()
{
// 另外右值引用虽然对赋值要求要有右属性,但是它本身具有左属性。
// 右值引用可以改值。
int t = 0;
// move函数的底层就是强转
// int&& p = (int&&)t;
int&& p = move(t);
p = 10;
std::cout << "p:" << p << " t:" << t << std::endl;
// 虽然这样子很方便,但是右值引用可以夺取变量和对象。
// 左值转右值时请慎重。
return 0;
}接下来是最后的内容也是右值引用最方便的内容。前面我们提到过返回值无法返回,C++委员会创建了右值引用。那我们就使用右值引用来实现返回值的拷贝构造。在函数执行完后右值引用就相当于免死金牌,更确切的说是特别通行证。如果没有返回对象用移动构造在函数结束时接住返回值,返回值就会销毁。移动构造就相当于一辆特别通行车。如果返回的对象没有移动构造,那只能被销毁了,相当于你有钱但是在荒山野岭买不到公交车,因为根本就没有。
那接下来我们来介绍移动构造是什么?和拷贝构造相似。移动构造要求参数必须有右值引用的类型,当然返回类型也要是右值引用才能使用右值引用。他可以直接将返回值中的内存转移到返回的对象中。下面的代码我们只用字符串char* 类型做做实验,只要能验证两个类对象的char*指针指向的地址是否一致,即可证明是否发生移动构造。
#include <iostream>
class Date
{
public:
Date(const int year = 1970 , const int month = 6 , const int day = 10,char* year_sum = nullptr)
:_year(year)
,_month(month)
,_day(day)
,_year_sum(year_sum)
{}
// 移动构造
Date(Date&& d1)
{
// 左值加const可以延续变量的生命周期,但是无法被修改。
// 右值引用却可以很好转移。
cout << "移动构造" << endl;
// 右值引用具有左值引用的属性
swap(d1);
}
// 这个是随便乱写的演示函数。
// 还请大家不要模仿。(太不规范了)
void swap(Date& d1)
{
int tmp = _year;
_year = d1._year;
d1._year = tmp;
tmp = _month;
_month = d1._month;
d1._month = tmp;
tmp = _day;
_day = d1._day;
d1._day = tmp;
char* tmp_str = _year_sum;
_year_sum = d1._year_sum;
d1._year_sum = tmp_str;
}
// 在函数传值返回中移动赋值优于拷贝赋值
Date& operator = (Date&& d)
{
cout << "移动赋值" << endl;
swap(d);
cout << (void*)_year_sum << endl;
cout << (void*)d._year_sum << endl;
return *(this);
}
Date& operator = (const Date& d)
{
cout << "拷贝赋值" << endl;
_day = d._day;
_month = d._month;
_year = d._year;
_year_sum = d._year_sum;
}
char* get_ptr()
{
return _year_sum;
}
private:
int _year;
int _month;
int _day;
// 年度总结,一些年度报告。
char* _year_sum;
};
int main()
{
char s[] = "随便写的";
cout << s << endl;
cout << (void*)s << endl;
// 由于编译器太过于新,所以优化太明显了!(直接将返回值取为接收值的别名的一个引用)
// Date ret1 = Creat_Date(1, 1, 1, s);
// cout << &ret << endl;
// 为了方便演示,所以搞了一个反常的对象。
Date ret2;
// 构造函数。
ret2 = Creat_Date(1,1,1,s);
Date&& ret3 = Creat_Date(1, 1, 1, s);
// 打印结果为空是因为返回值不能通过右值引用完好无损的保存。
// 只能够暂时保存。
cout << *(ret3.get_ptr()) << endl;
// 可以用地址判断,这些对象是否为同一个值。
cout << &ret2 << endl;
// cout << (void*)ret2.get_ptr() << endl;
return 0;
}最后再介绍引用折叠和万能引用。引用折叠可以理解为控制"&"的数量,防止出现更多的定义奇葩的引用类型报错。可以视为消消乐多出来的两个“&”自动消除。
万能引用一般和完美转发一起使用,完美转发就是防止编译器推导类型错误,直接拿之前的类型用。
万能引用意为任何类型引用都可以使用,我们一般会将一些模版当做万能模版。万能引用肯定也会有模版。再根据引用折叠。模版+"&&"。
以下内容只需看看就行了,无需掌握。
// 引用折叠
typedef int& lref;
typedef int&& rref;
int n;
lref& r1 = n; // int& (规则: T& & → T&&)
lref&& r2 = n; // int& (规则: T& && → T&)
rref& r3 = n; // int& (规则: T&& & → T&)
rref&& r4 = 1; // int&& (规则: T&& && → T&&)
// 万能模版
template<typename T>
void func(T&& param);
// 完美转发
std::forward<类型>(参数)