剑指offer - 赋值运算符函数

时间:2022-01-21 01:40:20

如下为类型CMyString的声明,为该类型添加赋值运算符函数:

class CMyString{
public :
CMyString(char* pData = NULL);
CMyString(const CMyString& str);
~CMyString(void);
private :
char* m_pData;
};

在添加赋值运算符时,应该关注以下几点:

1、把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用,即 *this。 只有返回一个引用,才允许使用连续赋值。否则如果函数的返回值是void, 应用该赋值运算符将不能使用连续赋值,即不能使用 str1 = str2 = str3 形式,其中str1 2 3 是CMyString的对象;


2、把传入的参数类型声明为常量引用,如果不是引用而是实例,那么从形参到实参会调用一个拷贝构造函数。把参数声明为引用可避免这样无谓的消耗,提高代码效率。同时我们在赋值运算符内不应改变传入的实例状态,因此应为传入的引用参数加上const关键字;


3、赋值过程中应该及时释放实例自身已有的内存,如果我们忘记在分配新内存之前释放它自身已有的内存,那么程序将出现内存泄漏;


4、在进行赋值之前务必判断传入的参数和当前实例(*this)是否属于同个实例。如果是同个实例,则不进行赋值操作直接返回。如果不判断便进 行赋值,一旦他们属于同个实例,我们在释放实例本身内存的时候,便会将传入的参数的内存也同时释放,然后再也找不到需要赋值的内容了

C++代码实现:

CMyString& CMyString::operator =(const CMyString& str){
if(this == &str)
return *this;
delete []m_pData;
m_pData = NULL;

char* m_pData = new char[strlen(str.length)+1];
strcpy(m_pData, str.m_pData);
return *this;
}

注:上面的代码一般情况下都没什么问题,但是还是有很小的概率导致程序崩溃。在重新为m_pData分配内存之前,我们是释放了实例m_pData的内存,如果此时内存不足导致new char抛出异常,则m_pData将会是一个空指针。因此,要想维持异常安全性,我们可以在释放内存前先分配内存,只有内存分配成功了我们才释放掉原先的。此外,也可以用另外一种更好的办法,就是先创建一个临时实例并将临时实例与原来的实例进行交换。

CMyString& CMyString::operator=(const CMyString& str){
if(this != &str){
CMyString strTemp(str);
char *pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData;
m_pData = pTemp;
}
return *this;
}

在上面这种方法中,它创建了一个临时实例strTemp,且将strTemp.m_pData与实例自身的m_pData进行交换。因为strTemp是个局部变量,当程序执行完 if 语句后,就会执行strTemp的析构函数, 并将 strTemp.m_pData 所指向的内存释放掉。因为交换后strTemp.m_pData指向的是原先实例m_pData指向的内存,就相当于是调用析构函数释放实例的内存了