网站首页 > 基础教程 正文
在学习C++的过程中会遇到很多难以理解的概念,今天我们就聊一聊C++中的左值引用和右值引用。在C++11以前还没有左值引用和右值引用的概念,只有引用的概念。右值引用是由C++11引入的,为了方便和右值引用区分,我们把C++11以前的常规引用称为左值引用。
什么是左值引用和右值引用
首先我们区分什么是左值和右值,有一个很好区分左值和右值的方式,就是是否可以对表达式取地址!!可以获取地址的表达式就是左值,且持久性变量都是左值,反之则是右值。左值引用和右值引用都是对象的别名而已,左值引用就是左值的别名,右值引用就是右值的别名。所以左值引用只能绑定左值,右值引用只能绑定右值,我们不能将一个右值引用类型变量绑定到一个右值引用。因为变量都是左值!我们可以对右值引用类型变量取地址!但是有一个例外,const左值引用可以绑定到右值。这看起来好像有点难以理解,我们用代码实际解释一下。
int i = 42; //i是左值,可以对i取地址
int &r = i; //r是左值引用,绑定左值i
可以对变量i取地址,所以是i是左值。
int &&rr = i; //错误!i是左值,不能绑定到右值引用rr
int &&rr3 = rr; //错误!!!!rr是一个右值引用类型的变量,是一个左值。
右值引用无法和右值引用类型变量绑定,因为右值引用类型变量是一个左值,所有持久性变量都是左值。可以取地址的变量是持久性变量,反之是临时性变量。那么是否可以用左值引用绑定右值引用类型变量吗?左值引用绑定左值引用类型变量?当然可以了。我们来看一下下面代码示例。
int &r2 = rr; //正确,rr是右值引用类型变量,变量都是左值。
int &r3 = r; //正确,r是左值引用类型变量,变量都是左值。
接下来我们看一下表达式,产生临时变量或字面常量的表达式都是右值,反之则是左值。我们依旧拿代码作为示例。
int &&rr_result = Add(1, 2); //不能对表达式取地址,所以表达式结果是一个右值,可以绑定到右值引用
int &&rr2 = i * 42; //不能对表达值取地址,所以表达式结果是右值,可以绑定到右值引用
int &&rr1 = 42; //不能对字面常量取地址,所以字面常量是右值,可以绑定到右值引用
Add函数产生一个临时变量,所以是右值。i * 42产生一个临时变量,是右值。42则是字面常量,所以也是右值。
int result = 0;
int *ptr = &(result = i * 12); //正确,可以对result取地址
int& r3 = (result = i * 12); //正确,表达式的结果存储在变量result中,可以对表达式取地址
result = i * 12表达式结果存储在result中,可以对result取地址,所以表达式是左值。
const int &r2 = i * 42; //不能对表达值取地址,所以表达式结果是右值,可以绑定到const左值引用
i * 42是一个右值,但是const左值引用类型可以绑定到右值上。
拷贝和移动的区别
C++有拷贝构造函数和移动构造函数,拷贝赋值运算符和移动赋值运算符。移动和拷贝两者最大的区别是:拷贝会产生新的内存,而移动不会。通过拷贝获得的对象状态改变,不会影响到源对象,而通过移动获得的对象状态改变,会影响到源对象,而且被移动的源对象失去所有资源的控制权!拷贝会增加内存申请和数据复制的开销,而移动不会。
要实现移动语义,就必须要有:移动构造函数和移动赋值运算符
下面我们举个例子来学习一下拷贝和移动的区别。
class Object{
private:
enum{
NAME_LEN = 50,
};
public:
Object(): Name(new char[NAME_LEN]){}
Object(const char *NameStr);
~Object();
Object(const Object& B); //拷贝构造函数
Object& operator=(const Object& B); //拷贝赋值运算符
Object(Object&& B); //移动构造函数
Object& operator=(Object&& B); //移动赋值运算符
void Print();
bool NameIsEmpty();
private:
char *Name;
};
我们新建一个Object类,有char*类型Name成员变量,我们在构造函数中申请内存,并拷贝传入的字符串,在析构函数中释放内存。之所以这么做,主要是为了突出拷贝和移动的区别。
拷贝构造函数和拷贝赋值运算符源码实现:
Object::Object(const Object& B)
{
if(this == &B){
return;
}
Name = new char[NAME_LEN];
memcpy(Name, B.Name, strlen(B.Name));
std::cout << "Object copy constructor" << std::endl;
}
Object& Object::operator=(const Object& B)
{
if(this == &B){
return *this;
}
memset(Name, 0, NAME_LEN);
memcpy(Name, B.Name, strlen(B.Name));
std::cout << "Object copy assignment" << std::endl;
return *this;
}
移动构造函数和移动赋值运算符源码的实现:
Object::Object(Object&& B)
{
//防止自我移动
if(this == &B){
return;
}
Name = B.Name;
B.Name = nullptr;
std::cout << "Object move constructor" << std::endl;
}
Object& Object::operator=(Object&& B)
{
//防止自我移动赋值
if(this == &B){
return *this;
}
if(!Name){
delete[] Name;
}
Name = B.Name;
B.Name = nullptr;
std::cout << "Object move assignment" << std::endl;
return *this;
}
实现了移动构造函数和移动赋值运算符,那么就可以使用std::move()标准库函数将左值变为右值,并调用他们。我们用代码示例来具体看一下如何使用std::move()库函数,并调用我们实现的移动构造函数和移动赋值运算符。
Object B1("Tangmeimei");
Object B2(B1); //调用拷贝构造函数
B2 = B1;
B1.Print();
B2.Print();
Object B4(std::move(B2));
if(B2.NameIsEmpty()){
std::cout << "B2 Object name is empty" << std::endl;
}
B4.Print();
Object B3;
B3 = std::move(B1);
if(B1.NameIsEmpty()){
std::cout << "B1 Object name is empty" << std::endl;
}
B3.Print();
运行结果和总结
从运行结果我们可以看出,调用拷贝构造函数和拷贝赋值运算符最多会经历一次内存释放,一次内存申请和一次数据拷贝。而调用移动构造函数和移动赋值运算符最多会经历一次内存释放。因此数据的移动远比数据的拷贝性能高,且移动会使得源对象失去资源的所有权。
下一期我们会详细讲解一下,C++的引用折叠和完美转发。
猜你喜欢
- 2024-10-12 全面剖析 C++ Boost 智能指针!| CSDN 博文精选
- 2024-10-12 C++设计模式——原型模式 设计模式之原型模式
- 2024-10-12 如何攻克 C++ 中复杂的类型转换? c++中四种类型转换的方式
- 2024-10-12 C++|由成员函数到运算符重载(类内、类外、友元方式重载)
- 2024-10-12 C++11新特性(49)- 用移动类对象代替拷贝类对象
- 2024-10-12 C++类的默认成员函数 c++类中定义的成员默认访问属性为( )
- 2024-10-12 C++的23种设计模式(上篇-创建型模式)
- 2024-10-12 C++构造函数和析构函数详解 c语言构造函数和析构函数
- 2024-10-12 c++——默认成员函数 c++成员变量默认值
- 2024-10-12 C++|类中实现操作符重载,用操作符代替成员函数名
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)