【C++】右值引用、移动构造函数

本文详细介绍了C++中的右值引用、左值与右值的区别,以及右值引用如何通过延长临时对象的生命周期来提高效率。重点讨论了移动构造函数和拷贝构造函数在资源管理上的差异,阐述了移动构造函数如何避免不必要的拷贝,提升程序性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

右值引用(&&)提供了一个函数重载的概念。

void fun(int &&a)
void fun(int b)
void fun(const b)

左值和右值的区别:

  1. 右值不可以取地址,左值可以
  2. 不可以直接修改,可以通过右值引用来修改,左值可以
  3. 右值只能放在括号的右边,左值两边都能放
  4. 右值不具名 ,左值具名
    首先:

1.什么是左值和右值

左值:

  1. 能够用&取地址的表达式是左值表达式
  2. 左值可以修改
  3. 左值可以放在等号的左右两边。(int a = 10;int b = a)
    a就是左值。

如:函数名和变量名,返回左值引用的函数调用,前置自增、自减(++i,–i)由赋值运算符或符合运算符连接的表达式(a=b,a+=b,a%=b),解引用表达式*p,字符串字面值"abc"

右值:(纯右值和将亡值)

  1. 右值不可以取地址
  2. 不可以直接修改,可以通过右值引用来修改
  3. 右值只能放在括号的右边
  4. 右值不具名
    如:
    字面值,(3,false,12.23)
    求值结果相当于字面值或者不具名的临时对象(值传递,函数的返回值,)
  • i++/i–就是右值,举个例子,i = 6;i++得到结果为6,这个6是i+1之前的一个副本,没有名字,i不是它的名字,i的值此时是7.
  • 解引用表达式*p是左值,而取地址表达式&a是纯右值。&(*p)是对的,因为 *p的p指向一个实体,&就是取这个实体的地址。
    而对于&a而言,本身就是地址,相当于一个字面值,不能再取地址的地址,就是纯右值。
  • a+b,a&&b,a==b都是纯右值。 a+b得到的是一个不具名的临时对象来存储它的值,而其余返回的是true和false,相当于字面值。

将亡值:

【c11之前右值和纯右值等价,c11中将亡值随着右值引用的引入,而新引入的。】
换句话说:“将亡值”产生的概念是因为右值引用的产生而引起的。

  • 将亡值表达式
    <1> 返回右值引用的函数的调用表达式
    如:int a = f(3);f(3)的返回值是右值,副本拷贝给a,然后消失
    <2> 转换为右值引用的转换函数的表达式。
    表达式(x+y),(x+y)是右值
    详细来说
    在C11中,我们用左值去初始化一个对象或者给一个已有对象赋值的时候,会调用拷贝构造函数或者拷贝赋值运算符来拷贝资源(所谓资源就是new出来的空间),而**当我们用右值来初始化或赋值,**调动的是移动构造函数和移动赋值运算符来移动资源,避免了拷贝。提高了效率。(下面还会提到)。
    当该右值准备初始化和赋值时,他的资源已经转移给被初始化和被赋值者,同时将该右值马上析构。
    从另一个角度说:当一个右值准备完成初始化和赋值这个动作的时候,已经处于将亡的状态了。上面两个表达式的结果都是不具名的右值引用,因此属于右值。
    且:
    1>这种右值是C11的新生事物——“右值引用”相关的“新右值”。
    2>这种右值常用来完成移动构造和移动赋值的特殊任务,且扮演“将亡”的角色,因此被称为将亡值。
    如:
  int fun()
  {
    int  a =10;
    return a;
  }
  int main()
  {
     int x = 0;
     x = fun();
  }

对于fun()函数,在return时,并不是返回a本身,a在fun()函数结束时,生存期便结束了,a的值会被存储到临时量中,这个临时量就是将亡值。

左右值使用的原则:

  1. 右值可以赋给左值,左值不能赋给右值(左值权限更大)
    如;
int a = 3;//a是左值,3是右值
int d = a; //d和a都是左值,左值可以作为右值
int &&d = a 错的//右值引用左值不可以
int &&d = 10 //右值引用右值可以
int &&d = f(10) //右值引用右值可以
  1. 右值无法被修改
    如:
int a = 10;// 10是右值常数,无法修改
  1. 编译允许左值建立引用,不允许右值建立引用
    如:
int num = 10;
int &b = num; //正确
int &b = 10;  错误

右值引用:

既然左值可以修改,那么想要对右值修改怎么办?

----- 采用右值引用
(右值引用就是对一个右值进行引用的类型。)
(右值是不具名的,所以我们必须通过引用才能找到。)
左值右值必须初始化
无论声明左值引用还是右值引用都必须进行初始化,因为引用类型本身不拥有绑定对象的内存,只是该对象的别名。
如:

int &&a; 错误的,没有初始化

不能使用左值对右值引用进行初始化。

int num =10;
int && a= num; 错误
int&&a = 10; 正确

右值引用可以对右值进行修改,

int &&a =10;
a = 100; 将右值10修改为100

可以看到,通过右值引用,既可以修改右值的值,换可以修改地址,从功能上,上升为左值。
右值引用的本质就是不用拷贝的左值

生存期延长:

通过右值引用的声明,可以让右值的声明周期延长,只要该变量还或者,该右值临时变量一会一直活下去。

右值引用的好处

函数传参 分为值传递和引用传递(不谈指针传递)。
两者相比,引用传递的优势就是通过传递地址,来减少一次拷贝,值传递需要将实参的值放到一个临时变量中,再将这个临时变量传给形参。

  • 如果是传递对象(函数传参),换需要调用拷贝构造函数,来创建一个临时对象,进行传值。而引用传递,直接相当于给要传递的对象起了个别名,通过这个别名直接操作这个对象。就不需要调用拷贝构造函数。
  • 如果是返回一个对象,如果是值的方式返回,也需要调用拷贝构造函数,创建一个临时对象空间,将该对象暂存,在返回到调用点处(调用了一次拷贝构造函数)
  1. 函数传参:int f(int &a)
  2. 函数返回值:int &f();
    如果返回的是一个临时对象:
    如下:
int &fun()
{
   String s1;
   return s1;
}

当返回对象s1的地址时,其时s1存在一个临时对象空间中,返回给调用点左值,就会被析构,如果外界对s1的地址进行访问,显然是访问不到的。这也是左值引用的一个弊端,而右值引用就是来解决这个问题。那么右值引用怎么解决临时的对象空间被析构呢?
当返回值为右值引用时,会把返回的临时对象中的内存据为己用,仍然保持有效性,避免了拷贝。

移动构造函数

一个函数返回一个class对象时,把本来调用拷贝构造函数,但经过编译器优化之后,没有调用拷贝构造函数?
如何优化:
不调用拷贝构造函数,而是调用移动拷贝构造函数。
语法:
类名(类名 && other)
{

}

拷贝构造函数

   String(const String& other)
    {
        len = other.len;
        str = new char[len + 1];
        strcpy_s(str, len+1,other.str);
        cout << "copy construct" << endl;
    }

移动构造函数

  //移动构造函数
  String(String&& s)
    {
        cout << "move copy construct:" << endl;
        str = s.str;
        s.str = NULL;
    }

普通赋值运算符重载

    String &operator=(const String& s)//运算符重载
{
    if (this == &s)
        return *this;
    //        return;
    delete[] str;
    len = s.len;
    str = new char[len + 1];
    strcpy_s(str,len+1, s.str);
    return *this;
    //    return;
}

移动赋值运算符

    //移动赋值运算符
    String& operator=(String&& s)  //引用方式返回
    {
        if (this != &s)
        {
            delete[]str;
            str = s.str;
            s.str = NULL;
        }
        cout << this << "move operator=:" << &s << endl;
        return *this;
    }

请添加图片描述
在整个程序执行的过程中,只对堆内存空间new了一次,

  1. 首先s2调用构造函数完成对str的初始化,也就是s2指向为字符串“baiU”开辟的堆空间。
  2. return s2时,(调用的是移动构造函数)创建将亡值对象,指向 s2申请的空间,
  3. s2析构。
  4. 将亡值移动赋值给s1过程。
    拷贝构造函数和移动构造函数对于指针的处理是不一样的

移动构造和拷贝构造和区别:

拷贝构造函数所做的是深拷贝,就是a拷贝到b中,需要在b中首先开辟一片空间在将a中的内容复制过去
移动构造函数干的是浅拷贝,就是将a中的指针直接复制到b中,同时要将a中的指针指向的位置改变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值