91-理解C++的右值引用并进行应用(CMyString)

1、CMyStirng代码的问题分析

在这里插入图片描述
在这里插入图片描述

由于问题场景的特殊,子函数调用时我们无法返回一个临时对象。
而且我们也只能用赋值的方式接收一个函数调用的返回值。

我们来分析分析上面的代码:

  • 1、首先,str1和str2都是调用普通构造函数。
    实参str1到形参str是引用的方式接收,没有产生构造。
  • 2、然后普通构造tmpStr局部对象。 tmpStr这个对象占4个字节,有一个char*指针,指向外部的一个new出来的堆内存,堆内存存放的是字符串。然后return tmpStr;tmpStr是局部对象。
  • 3、只能调用拷贝构造函数,在main函数的栈帧中构造一个临时对象(没有办法用GetString函数栈帧上的tmStr);
    在这里插入图片描述
  • 4、tmpStr拷贝构造新对象,然后调用析构函数析构tmpStr对象,这样就白耗费资源了,tmpStr资源不要就早说,直接给函数栈帧上的对象就好啦。
    在这里插入图片描述

问题1

在这里插入图片描述
在GetString函数中,先构造一个tmpStr对象,将其拷贝构造给main函数上的临时对象,再释放tmpStr,可以看到tmpStr自己开辟又释放是非常浪费资源的。

问题2

在这里插入图片描述

  • 用临时量给str2赋值,str2是原本已经存在的对象,它也有一个指针mptr,原先也指向了一个空间。
  • 对于赋值来说,排除自赋值,然后把原先指向的空间释放掉,然后按照str的尺寸开辟空间,然后拷贝数据进行来。即按照临时对象的字符串大小开辟空间,然后把数据一个一个拷贝进来。
    在这里插入图片描述
    然后出语句,把这个临时对象析构及它指向的堆内存空间释放掉。
    瞎折腾!直接把临时对象的外部资源给str2不就完了吗?

在main函数上有一个临时对象,但是这个临时对象又会拷贝赋值给str2,然后临时对象自己又释放,和上面的tmpStr一样,非常浪费资源。

在这里插入图片描述
在这里插入图片描述

2、右值引用

右值引用绑定右值的,左值引用绑定左值的;

左值: 有内存,有名字;

右值(常量数字、临时量):没名字(临时量)、没内存(C++都是将临时量当做右值的)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一个右值引用变量,本身是一个左值。

在这里插入图片描述

3、用右值引用优化CMyStirng代码

在这里插入图片描述

带右值引用的拷贝构造:
在这里插入图片描述
带左值引用参数的赋值重载函数:
在这里插入图片描述

示意图如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 只是添加了两个右值引用函数,运行原来程序;
  • 直接避免上面临时对象操作过程中开辟新空间的问题。
  • 没有任何内存的开辟和释放和数据的拷贝
#include <iostream>
using namespace std;

class CMyString
{
public:
	CMyString(const char* str = nullptr)
	{
		cout << "CMyString(const char*)" << endl;
		if (str != nullptr)
		{
			mptr = new char[strlen(str) + 1];
			strcpy(mptr, str);
		}
		else
		{
			mptr = new char[1];
			*mptr = '\0';
		}
	}
	~CMyString()
	{
		cout << "~CMyString" << endl;
		delete[]mptr;
		mptr = nullptr;
	}
	//带左值引用参数的拷贝构造
	CMyString(const CMyString& str)
	{
		cout << "CMyString(const CMyString&)" << endl;
		mptr = new char[strlen(str.mptr) + 1];
		strcpy(mptr, str.mptr);
	}
	//带右值引用参数的拷贝构造
	CMyString(CMyString&& str)//str引用的就是一个临时对象
	{
		cout << "CMyString(CMyString&&)" << endl;
		mptr = str.mptr;
		str.mptr = nullptr;
	}
	//带左值引用参数的赋值重载函数
	CMyString& operator=(const CMyString& str)
	{
		cout << "operator=(const CMyString&)" << endl;
		if (this == &str)
			return *this;

		delete[]mptr;

		mptr = new char[strlen(str.mptr) + 1];
		strcpy(mptr, str.mptr);
		return *this;
	}
	//带右值引用参数的赋值重载函数
	CMyString& operator=(CMyString&& str)//str引用的是临时对象
	{
		cout << "operator=(CMyString&&)" << endl;
		if (this == &str)
			return *this;

		delete[]mptr;

		mptr = str.mptr;
		str.mptr = nullptr;
		return *this;
	}
	const char* c_str()const { return mptr; }
private:
	char* mptr;
};

CMyString GetString(CMyString& str)
{
	const char* pstr = str.c_str();
	CMyString tmpStr(pstr);
	return tmpStr;
}

int main()
{
	CMyString str1("aaaaaaaaaaaaaaaaaaaaa");
	CMyString str2;
	
	str2 = GetString(str1);
	cout << str2.c_str() << endl;

	return 0;
}

4、MyString在vector上的应用

在这里插入图片描述
上面的CMyString是不变的。
下面的所有讨论,只围绕 +重载运算符号。

例1:"+"重载函数写法

在这里插入图片描述
在这里插入图片描述
运行结果:
在这里插入图片描述
问题:+重载函数里的ptmp指针指向的内存无法释放内存泄漏

解决方法:
在这里插入图片描述
这样可以将ptmp内存释放了,但是构造了tmpStr,又是一个开辟相同大小内存空间(然后拷贝数据,然后出函数,又自己析构了,效率低)。

改进方法:

修改“+重载”函数。
在这里插入图片描述
在这里插入图片描述
运行结果:
在这里插入图片描述
3:构造"+重载函数的" tmpStr,5是它的析构函数

4:说明了在用 “+重载函数中的返回值 tmpStr 拷贝构造 str3时”,匹配的是右值引用的拷贝构造。相当于让tmpStr的堆资源,直接让str3指向了,将tmpStr置为nullptr,右值引用的拷贝构造后面的析构函数,其实没有析构什么;
在这里插入图片描述
说明在执行上面的函数过程中,没有涉及任何的内存开辟,释放,拷贝(效率非常高)。

到此,上面的"+"重载函数就好了。

最终代码:
在这里插入图片描述

#include <iostream>
using namespace std;

class CMyString
{
public:
	CMyString(const char* str = nullptr)
	{
		cout << "CMyString(const char*)" << endl;
		if (str != nullptr)
		{
			mptr = new char[strlen(str) + 1];
			strcpy(mptr, str);
		}
		else
		{
			mptr = new char[1];
			*mptr = '\0';
		}
	}
	~CMyString()
	{
		cout << "~CMyString" << endl;
		delete[]mptr;
		mptr = nullptr;
	}
	//带左值引用参数的拷贝构造
	CMyString(const CMyString& str)
	{
		cout << "CMyString(const CMyString&)" << endl;
		mptr = new char[strlen(str.mptr) + 1];
		strcpy(mptr, str.mptr);
	}
	//带右值引用参数的拷贝构造
	CMyString(CMyString&& str)//str引用的就是一个临时对象
	{
		cout << "CMyString(CMyString&&)" << endl;
		mptr = str.mptr;
		str.mptr = nullptr;
	}
	//带左值引用参数的赋值重载函数
	CMyString& operator=(const CMyString& str)
	{
		cout << "operator=(const CMyString&)" << endl;
		if (this == &str)
			return *this;

		delete[]mptr;

		mptr = new char[strlen(str.mptr) + 1];
		strcpy(mptr, str.mptr);
		return *this;
	}
	//带右值引用参数的赋值重载函数
	CMyString& operator=(CMyString&& str)//str引用的是临时对象
	{
		cout << "operator=(CMyString&&)" << endl;
		if (this == &str)
			return *this;

		delete[]mptr;

		mptr = str.mptr;
		str.mptr = nullptr;
		return *this;
	}
	const char* c_str()const { return mptr; }
private:
	char* mptr;

	friend CMyString operator+(const CMyString& lhs,
		const CMyString& rhs);
	friend ostream& operator<<(ostream& out, const CMyString& str);
};

CMyString operator+(const CMyString& lhs,
	const CMyString& rhs)
{
	//char *ptmp = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
	CMyString tmpStr;
	tmpStr.mptr = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
	strcpy(tmpStr.mptr, lhs.mptr);
	strcat(tmpStr.mptr, rhs.mptr);
	//delete []ptmp;
	return tmpStr;//直接调用右值拷贝构造函数
	//return CMyString(ptmp);
}
ostream& operator<<(ostream& out, const CMyString& str)
{
	cout << str.mptr;
	return out;
}


CMyString GetString(CMyString& str)
{
	const char* pstr = str.c_str();
	CMyString tmpStr(pstr);
	return tmpStr;
}

int main()
{
	CMyString str1 = "hello ";
	CMyString str2 = "world!";
	cout << "--------------------------" << endl;
	CMyString str3 = str1 + str2;
	cout << "--------------------------" << endl;
	cout << str3 << endl;
}

例2:vector相关问题

CMyString类和例1的完全相同。
在这里插入图片描述
匹配左值: 调用左值引用的拷贝构造;vec.push_back(str1);

匹配右值: 调用右值引用的拷贝构造;vec.push_back(CMyString("bbb"));
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
C++中的字符串类CMyString实现了加法运算符重载函数,该函数接收两个CMyString对象,并返回一个新的对象。在重载函数中,通过获取两个对象的长度,并根据它们的长度创建一个新的字符数组。然后使用strcpy和strcat函数将两个对象的字符串连接起来,最后返回一个临时对象。 为了解决这个字符串类的问题,可以使用定义一个类对象来接收函数的返回值。这样在函数结束时,可以调用定义的析构函数,释放内存。修改后的重载函数中,创建了一个临时字符串对象tmp,并为其字符串指针mptr分配了新的内存空间。然后使用strcpy和strcat函数将两个对象的字符串***可以使用C语言中的字符串函数实现。通过在C语言中使用字符串函数,可以对任意长度的数字进行操作。具体的实现方法可以参考引用中提供的示例代码。该示例代码详细介绍了如何使用C语言中的字符串函数来进行任意长度数字的加减法操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [C++学习11-String类型 & 大数加减](https://blog.csdn.net/sunshine612/article/details/105008241)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [使用C++string实现任意长度的正小数、整数之间加减法方法实例](https://download.csdn.net/download/weixin_38669793/12778784)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liufeng2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值