引子:我们在之前学过,auto(底层为迭代器),范围for,{}初始化(可以省略=),initializer_list,decltype,nullptr(Null字面上为0),arrray(与数组相比,对于出错的处理不同),forward_list(与普通的相比少一点空间),还学过unordered_map与unordered_set(底层为哈希表,详见我前一篇封装)新容器等内容,这些其实是C++11新增的内容,今天我们就来讲一讲另一个C++11的内容------左右值的引用!
一,初解:何为左值,何为右值?
左值(Lvalue)和右值(Rvalue)是表达式的两种分类。
左值指的是可以出现在赋值表达式的左边的表达式,它表示一个对象的身份,即内存地址。
右值则是不能出现在赋值表达式的左边的表达式,它表示一个临时的值或者一个无法获取地址的表达式。
左值引用就是给左值的引用,给左值取别名。----&
右值引用就是对右值的引用,给右值取别名----&&
为什么用&&来右值引用?
我认为:
一,通过使用右值引用,可以确保只有对象的值被移动,而不是对象的引用或指针
二,临时对象通常是右值,它们在表达式中产生并立即被使用。使用&&
可以高效地利用这些临时对象,通过移动它们的资源而不是进行复制
举例:左右值:如下
cpp
// 左值
int* p = new int(0);
int b = 1;
const int c = 2;
double x = 1.1, y = 2.2;
// 右值
10;
x + y;
fmin(x, y);
举例:左右值引用:如下
cpp
// 左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
// 右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
二,再解:左右值引用的转换。
左值引用总结: 1. 左值引用只能引用左值,不能引用右值。 2. 但是const左值引用既可引用左值,也可引用右值
右值引用总结: 1. 右值引用只能右值,不能引用左值。 2. 但是右值引用可以move以后的左值。
move是什么?
其实也不用太复杂!我认为就是一个强转!就是转成右值!
补:其实在底层上,都开了空间,但是在语法上不显示
引用转换:
cpp
//左转右
int h ;//左值
int& d = 10;//左值引用,,错误
double& n =x+y;//左值引用,,错误
double& j = fmin(x, y);//左值引用,错误
const int& a = 10;//左值引用
const double& k = x+y;//左值引用
const double&l = fmin(x,y);//左值引用
//右转左
int g=10;
int&& t =g;//右值引用,错误
int&& t1 = move(g); //右值引用
如上图错误:
四,后解:右值引用的应用场景与作用!
主要有以下几种:
一,临时对象:例如函数返回的值,它们是右值。
二,移动语义:通过std::move
将资源从一个地方移动到另一个地方,避免复制的开销。
移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不 用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己
三,右值引用:允许以右值引用的方式访问右值,这在现代C++编程中用于实现资源的高效转移。
四,完美转发:在模板编程中,完美转发允许将参数以左值或右值的形式传递给其他函数,保持其值类别
我认为右值引用是为了弥补左值引用无法解决传值的问题,可以节省一个拷贝构造!
参考代码:
cpp
class Resource {
public:
void swap(Resource& s)
{
std::swap(size, s.size);
std::swap(data, s.data);
}
Resource(int size)
: size(size)
, data(new int[size])
{
cout << "初始构造" << endl;
}
// 移动构造函数
Resource(Resource&& other)
: size(0)
, data(nullptr)
{
cout << "移动构造" << endl;
swap(other);
}
// 拷贝构造函数
Resource(const Resource& s)
{
cout << "拷贝构造函数" << endl;
Resource cmp(s.size);
swap(cmp);
}
// 移动赋值运算符
Resource& operator=(Resource&& other)
{
if (this != &other) {
cout << "移动赋值运算" << endl;
swap(other);
}
return *this;
}
// 拷贝赋值运算
Resource& operator=(const Resource& s)
{
cout << "拷贝赋值" << endl;
Resource cmp(s.size);
swap(cmp);
}
~Resource()
{
delete[] data;
}
private:
int* data;
int size;
};
int main()
{
Resource h1(3);
Resource h2(h1);
Resource h3(move(h1));
Resource h4 =move(h2);
return 0;
}
补:其实右值引用的本质上还是左值!
五,万能的转发(补)
万能的转发是指在模板函数中使用std::forward
来转发参数,它可以保持参数的原始值类别(左值或右值)。这在模板编程中非常有用,因为它允许函数模板接受不同类型的参数,并将这些参数以它们原始的值类别传递给其他函数
测试代码:
cpp
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
/*引用
传左值->左值引用
传右值->右值引用*/
template<typename T>
void PerfectForward(T&& t)
{
// 模版实例化是左值引用,保持属性直接传参给Fun
// 模版实例化是右值引用,右值引用属性会退化成左值,转换成右值属性再传参给Fun
Fun(forward<T>(t));
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
结果:
下集:我们进入可变参数的摸板分享!