1.左值与右值
- 左值的几种定义
a.可以取地址的,有名字的就是左值(但const常量不能做为左值)
b.左值则是有名称的,能够被操作的对象
c.在内存中有独立的内存空间,并且内存空间的内容是可变的,也就是通常所说的变量 - 右值的几种定义
a.不能直接取地址,没有名称的,将立即被销毁的对象
b.右值是自动产生的,不被用户直接操作的
int a; // a左值
int b = a + 1; // (a + 1)是右值,b是左值
2. 左值引用(&)
- 用法:Type & 左值引用名 = 左值表达式;
- 注意:声明时必须初始化,初始化之后无法再改变(即无法将引用再绑定到另一个变量);对别名的一切操作都等价于对原来变量的操作。
- 左值引用和指针都相当于是通过地址来访问具体的值,因此可以修改
int & r = val + 1; //此句不合法,因为右值无法赋值给非常量左值引用
const int& r = val + 1;//合法
//解释:资料说c++中临时变量默认const属性,所以只能传给const的引用。规定右值不能绑定到非 const 限定的左值引用。
3. 右值引用(&&)
- C++11 新特性
- 用法:Type && 右值引用名 = 右值表达式;
- 在上面的代码中,我们无法建立 int &rb = a + 1 这样的语法,因为a + 1 此时是作为一个右值来使用的,我们无法把一个右值赋值给一个左值引用。(也就是左值引用相当于把一个变量的地址赋给另一个变量,这两个变量可以访问同一个内存,右值仅仅是一个数,而非内存中的某块地址,因此无法把右值复制给左值引用)。
4. 移动语义(std::move)
- 解决的是各种情形下对象的资源所有权转移的问题
- 用法:int && rrval = std::move(val);
- 用途:对于左值,如果我们明确放弃对其资源的所有权,则可以通过std::move()来将其转为右值引用。当你不需要在使用一个变量的时候,可以直接通过该构造函数来实现把该变量的数据转换到另一个变量中,省去调用默认的赋值构造或者拷贝构造函数带来额外的开销
- 注意:在调用完std::move之后,不能再使用val,只能使用 rrval,这一点用于基本类型可能没什么直接影响,当应用到类函数的时候,用好std::move 可以减少构造函数数的次数
#include <iostream>
#include <utility> //std::move头文件
#include <vector>
#include <string>
int main()
{
std::string str = "Hello";
std::vector<std::string> v;
// 使用 push_back(const T&) 重载,
// 表示我们将带来复制 str 的成本
v.push_back(str);
std::cout << "After copy, str is \"" << str << "\"\n";
// 使用右值引用 push_back(T&&) 重载,
// 表示不复制字符串;而是 str 的内容被移动进 vector
// 这个开销比较低,但也意味着 str 现在可能为空。
v.push_back(std::move(str));
std::cout << "After move, str is \"" << str << "\"\n";
std::cout << "The contents of the vector are \"" << v[0]
<< "\", \"" << v[1] << "\"\n";
}
运行结果:看到move后str为空,不再可用
After copy, str is "Hello"
After move, str is ""
The contents of the vector are "Hello", "Hello"
5.移动构造函数
/**
* 拷贝构造函数调用时机:
* 1. 对象作为函数参数
* 2. 对象作为函数返回值
* 3. 用一个对象初始化另一个对象:
* T t1;
* T t2(t1);
* T t3 = t1; 此处的 = 不是赋值运算符
*
* 拷贝赋值运算符:
* T t1;
* T t2;
* t1 = t2;
* 除了 类名 对象 = 对象 外的 = 应该都是赋值运算符
*
* 移动构造函数:
* 用右值初始化对象。
* std::move(对象)将对象转为右值
*
* 移动赋值运算符
* T t1;
* t1 = std::move(T());
*/
#include <iostream> // std::cout
class A
{
public:
int a;
//一个参数的构造函数(也叫做转换构造函数)
A(int i):a(i)
printf("construct is called! %d \n",i);
~A()
printf("deconstruct is called! \n");
//拷贝构造函数
A(const A &v)
{
this->a = v.a;
printf("copy construct is called %d\n", a);
}
//拷贝赋值运算符
A& operator = (const A &v)
{
printf("= is called \n");
if(this == &v)
return *this;
a = v.a;
return *this;
}
//移动构造函数
A(A &&v)
{
this->a = v.a;
printf("move construct is called %d\n", a);
}
//移动赋值运算符
A& operator = (A &&v)
{
printf("move = is called \n");
if(this == &v)
return *this;
a = v.a;
return *this;
}
};
int main ()
{
A a(1); //调用构造函数
A b(a); //调用拷贝构造函数
A c = a; //调用拷贝构造函数
A d(2);
d = a; //调用拷贝赋值运算符
A e = std::move(A(3)); //调用移动构造函数
A f(4);
f = std::move(A(5)); //调用移动赋值运算符
return 0;
}
参考
https://blog.csdn.net/lengyue_wuxin/article/details/78720085