0
点赞
收藏
分享

微信扫一扫

【C++】左值、右值、移动拷贝构造函数、移动赋值函数

624c95384278 2022-02-16 阅读 58

左值和右值的定义以及区别:

先来看在C语言中的左值和右值 :

        左值:可赋值的值为左值

        右值:不可赋值的值为右值

再来看C++中左值右值的定义如下:

        左值:可以取地址的值(有名字的值、非临时量)
        右值:不可取地址的值(没有名字的值、临时量)
       

左值引用:
int a = 10;   //整型a = 10
int& b = a;   //可以编译通过  左值引用就相当于变量的别名   
             

const int a =  10;  //左值引用字面常量的时候  改为常引用const就可以引用了
                    //左值引用要求右值必须能够取地址,如果不能取地址,则必须为常引用



右值引用:
int&& a = 10;   //字面常量10为纯右值,可以编译通过
int&& b = a;    //不可以编译通过  因为a是字面常量10的右值引用   a以及有名字了

说完了上面的定义来看下面这些代码:

//我们下面的String是一个了类
String fun()
{
    String s2("hello");
    return s2;
}

String & fun()
{
    String s2("hello");
    return s2;
}

String && fun()
{
    String s2("hello");
    return s2;
}
int main()
{    
    String s1;
    s1 = fun();
}

上面分别是用String,String &,String &&作为返回,通过下面的主函数去调用fun函数的时候哪些可以通过编译呢?

 移动拷贝构造函数、移动赋值函数

#include<iostream>
#include<cstring>
using namespace std;

class String
{
	char* str;

public:
	String(const char* p = NULL) :str(NULL)  //构造函数
	{
        cout << "construct :" << this << endl;
		if (p != NULL)
		{
			str = new char[strlen(p) + 1];
			strcpy(str, p);
		}
		else
		{
			str = new char[1];
			*str = '\0';
		}
	}

	~String()   //析构函数
	{
        cout << "destroy : " << this << endl;
		if (str != NULL)
		{
			delete[] str;
		}
		str = NULL;
	}
	String(const String& s) :str(NULL)  //拷贝构造函数
	{
		cout << "copy construct : " << this << endl;
		str = new char[strlen(s.str) + 1];
		strcpy(str, s.str);
	}
	char* Relese(char* p)
	{
		char* old = str;
		str = p;
		return old;
	}
    String(String&& s)
	{
		cout << " move copy construcgt :" << this << endl;
		str = s.str;
		s.str = NULL;
	}
	String& operator=(String&& s)
	{
		if (this != &s)
		{
			s.str = Relese(s.str);
		}
		cout << this << " move operator=: " << &s << endl;
		return *this;
	}
};

String fun()
{
	String s2("hello");
	return s2;
}

int main()
{
	String s1;
	s1 = fun();

	return 0;
}

先来看上述代码,创建s1对象,s1对象堆区申请空间,进入fun函数中,创建s2对象,s2堆区申请空间,return时将s2通过拷贝构造函数创建临时对象,临时对象申请空间,s2释放堆区空间,临时空间释放堆区空间,到main程序结束时,释放s1对象空间,下面来看上述代码执行结果:

 上述代码表明了刚刚所推断的都是正确的,但是简单的几行代码对堆区进行多次操作,而我们想要达到的目的只是将对象s2中的字符串转移到对象s1中,于是乎移动拷贝构造函数、移动赋值函数就有了,顾名思义,它的作用是移动对象中的资源,并不是移动对象

结果如下:

 

上述调用移动拷贝构造和移动赋值具体如下:

1.s1对象创建,堆区申请空间给个'\0' 

2.调用fun函数

3.调用拷贝构造函数创建s2对象,堆区申请空间

4.return时调用移动拷贝构造(这里为什么会调用移动拷贝构造呢,本来临时对象通过拷贝构造函数传参s2来构建对象,但是现在有移动拷贝构造函数(参数为右值引用),因为我们前面说过右值引用可以是临时量,所以这块就是临时对象调用移动拷贝构造将临时对象指针指向hello堆区,s2对象指针置为空,临时对象调用完移动拷贝构造之后,s2对象指针已经为NULL,s2对象被析构不会释放掉堆区内存)。----》对了,return s2底层调用了rerun std::move(s2),将s2强转为右值对象!

5.这一步相当于右值引用传参来重载等号运算符,也是一样的,因为临时对象给s1赋值,所以临时对象右值引用来进行传参,进入到移动赋值函数时,将s1对象指针指向hello堆区,并将临时对象指针置为NULL。

tips:这块为什么临时对象就会调用移动拷贝构造函数、移动赋值函数?因为临时对象符合右值引用的条件,如果你没有写出移动拷贝构造函数、移动赋值函数的话,就会调用普通的哪些函数!

还有一点上述那个代码中的s1对象指向堆区的'\0'会造成内存泄漏,所以得写一个Relese函数释放s1堆区空间!

移动拷贝构造函数、移动赋值函数就是为了减少对堆区的操作次数,仅仅移动对象的资源,并不是移动对象!

如果本篇文章对你有帮助的话,可以点个赞哦,如有错误,敬请指出,共同学习,共同进步!

举报

相关推荐

0 条评论