一. 左值和右值
左值
: 可以取地址的对象
右值
: 不可以取地址的对象
python
double x=1.0, y = 2.0;
1; // 字面量, 不可取地址, 是右值
x + y; // 表达式返回值, 不可取地址, 是右值
max(x, y); // 传值返回函数的返回值 (非引用返回)
总结就是: 根据是否可以取地址来区分是左值还是右值
二.左值引用和右值引用
左值引用: 对左值的引用, 给左值起别名, 主要是为了避免对象拷贝
.
python
int a = 1;
int& la = a;
右值引用: 对右值的引用, 给右值起别名, 主要是为了延长对象的生命周期
python
int&& ra = 10;
注意
, 右值引用变量, 其实是左值
, 可以对它取地址和赋值
python
int x = 1, y = 2;
int&& right_ref = x + y;
cout << right_ref << endl;
right_ref = 10;
cout << right_ref << endl;
2.1 左值引用指向右值 和 右值引用指向左值
左值引用可以指向右值, 需要const来修饰
, 这一点在方法里很常用, 比如push_back接口, 并且const修是, 所以即使传进来的是右值, 也没关系.
python
void test(const int& a)
{
cout << a << endl;
}
int main() {
int a = 1;
int&& ra = move(a);
test(a);
test(ra);
}
右值引用可以指向左值, 需要std::move(v)
python
int a = 1;
int&& ra = move(a);
cout << ra << endl;
a = 2;
cout << ra << endl;
ra = 3;
cout << a << endl;
三. 左值引用的意义
在传参
和返回值
时, 避免对象拷贝, 节省了内存, 提搞了效率
python
class A
{
public:
int _a;
A(int a) : _a(a) {};
// 返回值时, 避免拷贝
A& operator +=(const int& i)
{
_a += i;
return *this;
}
};
// 传参时, 避免拷贝
void test(const A& a)
{
cout << a._a << endl;
}
int main() {
A a(1);
test(a);
a += 1;
test(a);
}
但是在返回值
返回左值引用时, 如果返回的是局部变量, 那么除了函数作用域是不行的
python
class A
{
public:
int _a;
A(int a) : _a(a) {};
A& operator +=(const int& i)
{
A a(_a);
// 返回局部变量, 会直接报错
return *a;
}
};
int main() {
A a(1);
a += 1;
}
四. 右值引用的意义
将一个对象中的资源移动到另一个对象(资源控制权的转移)。
拷贝构造
: const左值引用
移动构造
: 右值引用
移动赋值
: const左值引用, 是运算符重载
用例如下
python
class A
{
public:
int _a;
A(int a) : _a(a)
{
cout << "构造函数" << endl;
};
A(const A& a)
{
cout << "拷贝构造函数" << endl;
_a = a._a;
}
A(A&& a) noexcept
{
cout << "移动构造函数" << endl;
_a = a._a;
}
A& operator=(const A& a) noexcept
{
cout << "移动赋值函数" << endl;
_a = a._a;
return *this;
}
};
A test()
{
A a(1);
cout << &a << endl;
// 但是现代编译器中, 这里已经不执行移动构造函数了, 用了更好的优化方式
return a;
}
int main() {
A a = test();
cout << &a << endl;
A a2 = a;
A a3 = std::move(a);
A a4(1);
a4 = std::move(a3);
}
执行结果
五. 完美转发
5.1 前提知识
函数模板中的&&
不表示右值引用, 而表示万能引用
不完美转发的例子
python
void f(int& x)
{
cout << "左值引用" << endl;
}
void f(const int& x)
{
cout << "const 左值引用" << endl;
}
void f(int&& x)
{
cout << "右值引用" << endl;
}
void f(const int&& x)
{
cout << "const右值引用" << endl;
}
template<typename T>
void test(T&& t)
{
f(t);
}
int main() {
int a = 1;
test(a);
const int b = 1;
test(b);
test(1);
const int d = 1;
test(std::move(d));
}
执行结果
和我们猜想的差别很大, 是因为, 右值引用
本身是左值, 所以在模板函数中, 我们传进去的是右值, 但是等到调用f
函数的时候, 已经全部是左值了
5.2完美转发
由上面的例子我们可以看到, 是一个不完美转发
, 因为右值
失去了它的右值属性
, 于是便提出了完美转发
, 核心方法是
std::forward
, 它在传参过程中, 保留对象原生类型的属性, 我们稍加修改一下上面的例子
python
void f(int& x)
{
cout << "左值引用" << endl;
}
void f(const int& x)
{
cout << "const 左值引用" << endl;
}
void f(int&& x)
{
cout << "右值引用" << endl;
}
void f(const int&& x)
{
cout << "const右值引用" << endl;
}
template<typename T>
void test(T&& t)
{
f(std::forward<T>(t));
}
int main() {
int a = 1;
test(a);
const int b = 1;
test(b);
test(1);
const int d = 1;
test(std::move(d));
}
执行结果
和我们预想的一致了
总结
右值引用是c++11引入的最重要的新特性之一, 配合着移动语义和完美转发, 是的c++程序运行更加高效.