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"));