在编程时对赋值运算符的重载是有时需要进行考虑的部分,今天在这里对这一部分的内容进行一下记录。
首先是MyString类的定义部分:
#include<iostream>
using namespace std;
class MyString
{
private:
char* m_pData;
public:
MyString(const char* pData = nullptr);
MyString(const MyString& str);
~MyString(void) {}
MyString& operator=(const MyString & str);
void Print() { cout << m_pData << endl; }
};
MyString::MyString(const char * pData)
{
m_pData = new char[strlen(pData) + 1];
strcpy_s(m_pData, strlen(pData) + 1, pData);
}
MyString::MyString(const MyString & str)
{
m_pData = new char[strlen(str.m_pData) + 1];
strcpy_s(m_pData, strlen(str.m_pData), str.m_pData);
}
MyString::~MyString(void)
{
delete[] m_pData;
}
在运算符重载中,我们有几个地方需要特殊的注意:
首先,operator=的运算符重载最好将函数的返回类型定义为类的引用,即为MyString &,尽管C++允许我们将返回类型定义为值类型或void类型,但在某些情况下会产生一定的问题并且在效率上有一定缺陷。首先,函数如果返回值是值类型,则需要在返回时再次调用一次拷贝构造,效率较低;而如果是进行连续赋值,例如a = b = c时,首先会对return的值类型对象进行一次拷贝构造,然后将这个新的对象返回,所以执行a = b后得到的是一个右值,不能作为左值,就会报错。而如果使用void作为返回值,在大部分情况不会出现问题,但是在连续赋值,如a = b = c的时候,除非b = c返回的是a的同类型的东西(虽然一般都是),连续赋值就会报错,另一种情况是,如果在代码
mytype obj;
while ((obj = read_obj(cin)) != END_OBJ) {
...
}
这种地方,void是不能使用的。因此,使用引用作为返回值是最好的选择。
第二点需要注意的是,传入的参数最好声明为const 类型的引用,能够最大可能的提高代码效率。
第三点是,在进入函数后,要首先判断传入参数和当前的实例是否为同一个实例,否则在释放当前实例的时候,传入参数的值同样被释放,就会造成尴尬的局面。
第四点是,在开辟新的空间放置新内容时,要首先释放掉原有空间,保证没有内存泄漏发生。
MyString& MyString::operator=(const MyString & str)
{
if (this == &str)
{
return *this;
}
delete[] m_pData;
m_pData = nullptr;
m_pData = new char[strlen(str.m_pData) + 1];
strcpy_s(m_pData, strlen(str.m_pData), str.m_pData);
return *this;
}
int main()
{
char c;
const char* c1 = "aaa";
const char* c2 = "bbb";
const char* c3 = "ccc";
MyString* s1 = new MyString(c1);
MyString* s2 = new MyString(c2);
MyString* s3 = new MyString(c3);
s1 = s2 = s3;
s2->Print();
s3->Print();
cin >> c;
return 0;
}
而在考虑了代码的安全性后,我们可以将程序修改为如下所示:
MyString& MyString::operator=(const MyString & str)
{
if (this != &str)
{
MyString tmpStr(str);
char* tmp = tmpStr.m_pData;
tmpStr.m_pData = m_pData;
m_pData = tmp;
}
return *this;
}
在这一段代码中,我们可以防止在new时,如果出现内存不足分配失败的错误时,防止原有对象的值被清空。在if作用域内部定义tmpStr,并将其指针指向目标对象所指内存地址,再声明char*指针记录目标地址,然后将tmpStr指针指向当前指针后不去管它,这样就会在离开if作用域后自动析构,释放掉原地址内存,而将当前对象的指针指向新声明的char*指针指向的目标内存。这样如果在构造函数中的new中发现报错时,还没有释放原有内存,就防止了内存丢失的发生。