【c++11】 左值引用和右值引用

简介: 【c++11】 左值引用和右值引用

右值引用

c++从出现就有着引用的语法,但是在c++11后又新增了右值引用的新特性,以往所学的引用成了左值引用。非左即右

无论是左值引用还是右值引用,都是给对象取别名

左值引用和右值引用

左值引用

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

//a,c,p都是左值
int a=0;
const int c=5;
int *p=&a;
//ref都是对以上左值的引用
int& ref1=a;
int& ref2=c;
int*& ref3=p;

右值引用

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,**右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。**右值引用就是对右值的引用,给右值取别名。

int x=10;int y=20;
//以下都是常见的右值
10;
x+y;
min(x,y)//函数返回值
//以下是右值引用
int&& ref1=10;
int&& ref2=x+y;
int&& ref3=min(x+y);

区分左值和右值最常见的方法就是能不能取地址

int a=10;
int *p=&a;//左值
//错误的
int *p1=&10;//右值无法取地址

比较

左值引用总结:

  1. 左值引用只能引用左值,不能引用右值。
  2. 但是const左值引用既可引用左值,也可引用右值。

右值引用总结:

  1. 右值引用只能右值,不能引用左值。
  2. 但是右值引用可以move以后的左值。
int a=10;
//左值引用引用左值
int &ref1=a;
//左值引用引用右值
const int& ref2=10;
//右值引用引用右值
int&& ref3=10;
//右值引用引用左值
int&& ref4=std::move(a)//右值引用可以引用move以后的左值,不能直接引用

右值引用的应用

右值引用一般用于自定义类型,右值又分为纯右值 (prvalue)、将亡值 (xvalue)

xvalue(eXpiring value)字面意思可理解为生命周期即将结束的值(将亡值),它是某些涉及到右值引用的表达式的值,左值也可以是将亡值,例如:调用一个返回类型为右值引用的函数的返回值就是xvalue。

prvalue(pure rvalue)字面意思可理解为纯右值,传统意义上的右值,例如临时对象和字面值常量(字符串字面值除外)等。

左值引用的短处

当我们在写一个函数解决某些需求时,返回值通常是需要返回函数栈帧里创建的临时变量,因为是临时变量,只能进行传值返回,但是当函数执行结束后,函数栈帧会自动销毁,函数栈帧里的局部变量也会销毁,如果我们要返回函数栈帧里创建的临时变量,就需要编译器的处理,在c++11之前:

  1. 编译器会对返回的对象做一份临时拷贝,拷贝出一个临时对象
  2. 将原对象销毁
  3. 在将这个临时变量返回,赋值或者调用拷贝构造给main函数栈帧里的对像

会进行两次拷贝构造,编译器会对其进行优化,优化成为一次拷贝构造,但是一些老旧编译器不会优化,仍是两次拷贝构造。

如果是内置类型,那么拷贝消耗不大,但如果是自定义类型或者是stl中的容器,

如:vector<vector> vector<list>

或者你定义了一个内存消耗极大的自定义类型,如果需要拷贝的话,会有极大的消耗,于是c++11中,提出了右值引用的语义。

右值引用解决问题

可以看到,在函数返回的时候,函数的局部变量可以视为一个将亡值,也就是它的资源将要被销毁,但是如果要返回某一个局部对象,那么可不可以将该局部变量的资源进行转移,而不是对它进行拷贝后在销毁。

移动构造

移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不

用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

举个例子:

这是自定义的一个string 类型的部分代码,在它每次调用拷贝或者赋值的时候会打印出调用的拷贝或者赋值类型,便于我们观察和分析。

  // 拷贝构造
    string(const string& s)
      :_str(nullptr)
    {
      cout << "string(const string& s) -- 拷贝构造" << endl;
      string tmp(s._str);
      swap(tmp);
    }
    // 赋值重载
    string& operator=(const string& s)
    {
      cout << "string& operator=(string s) -- 拷贝赋值" << endl;
      string tmp(s);
      swap(tmp);
      return *this;
    }
     //移动构造
    string(string&& s)
      :_str(nullptr)
      , _size(0)
      , _capacity(0)
    {
      cout << "string(string&& s) -- 移动构造" << endl;
      swap(s);
    }
    // 移动赋值
    string& operator=(string&& s)
    {
      cout << "string& operator=(string&& s) -- 移动语义" << endl;
      swap(s);
      return *this;
    }

在看一个简单的函数

string func()
    {
      string tmp("hello world!");
      return tmp;
    }
int main()
{
lx::string s1("hello  world");
lx::string s3=s1.func();
return 0;
}

可以看的,我们在func函数中返回了一个临时对象tmp,看移动拷贝是转移右值的资源的。

在函数中,tmp的地址为0X3353d0;

我们可以看的,在函数返回后,s3的地址也是0X3353d0,且tmp和s3所拥有的资源是相同的

说明调用了一次移动构造,直接转移了tmp中的资源,而不是在重新进行深拷贝。

我们来分析一下过程:

  1. 因为函数栈帧中,tmp对象还是一个左值,所以对其进行深拷贝,拷贝出一个临时变量。
  1. 也会对tmp进行销毁
  2. 临时变量是一个右值,所以调用一次移动构造,对s3进行构造

因为我用的是比较新的编译器,所以编译器对其进行了优化,所以最后显示的只有一次移动构造。

还有同学对tmp和s3的地址相同绝得很诧异。

为什么tmp都已经被销毁了,s3怎么就像是原封不动的对tmp进行copy了一样?

其实这也是编译器对此进行了一些特别复杂的处理,有兴趣的同学可以去深度挖掘一下。

STL的改动

c++11后,STL中所有容器都增加了移动构造和移动赋值。

c++11后,STL中所有容器插入函数中都增加了右值引用版本。

move()函数

这是一个帮助程序函数,用于强制对值进行移动语义, 直接使用返回值会导致被视为右值。

看以下代码:

int main()
{
  string s1("hello world");
  string s2(s1);
  string s3(move(s1));
  return 0;
}

对其进行调试,观察

对s2正常拷贝构造

往下走一步

可以看到,在对s3进行构造后,s1自身的资源被**“偷走”**了。

因为move对其进行了移动语义,转换为了右值属性,然后调用了移动构造,

将s1的资源转移了。所以会显示出这样一个结果。

所以对move()函数的调用需要谨慎。

结语

本次的博客就到这了。

我是Tom-猫,

如果觉得有帮助的话,记得

一键三连哦ヾ(≧▽≦*)o。

咱们下期再见。

相关文章
|
7月前
|
存储 安全 C++
浅析C++的指针与引用
虽然指针和引用在C++中都用于间接数据访问,但它们各自拥有独特的特性和应用场景。选择使用指针还是引用,主要取决于程序的具体需求,如是否需要动态内存管理,是否希望变量可以重新指向其他对象等。理解这二者的区别,将有助于开发高效、安全的C++程序。
48 3
|
7月前
|
存储 自然语言处理 编译器
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
|
7月前
|
C++
C++基础知识(二:引用和new delete)
引用是C++中的一种复合类型,它是某个已存在变量的别名,也就是说引用不是独立的实体,它只是为已存在的变量取了一个新名字。一旦引用被初始化为某个变量,就不能改变引用到另一个变量。引用的主要用途包括函数参数传递、操作符重载等,它可以避免复制大对象的开销,并且使得代码更加直观易读。
|
7月前
|
存储 自然语言处理 编译器
|
1天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
1月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
68 19
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
50 13
|
1月前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
50 5
|
1月前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
40 5