题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数。
class CMyString
{
public:
CMyString(char* pData = NULL);
CMyString(const CMyString& str);
~CMyString(void);
private:
char* m_pData;
};
赋值运算符函数和拷贝构造函数类似,都是用来改变一个对象的私有变量,两者的区别是,拷贝构造函数是在对象创建时进行初始化,而赋值运算符是在一个已有的对象上修改。
调用了拷贝构造函数:
情形1:
CMyString s1("Hello world");
情形2:
CMyString s2 = "Hello world";
调用了赋值运算符:
CMyString s3;
s3 = s1;
方法1:
CMyString& CMyString::operator=(const CMyString& s)
{
if(this != &s)
{
char* tmp = new char[strlen(s.m_pData) + 1];
strcpy(tmp,s.m_pData);
delete[] m_pData;
m_pData = tmp;
}
return *this;
}
关注点:
1.返回值为自身的引用,允许连续赋值。
2.参数类型声明为常量引用,从时间的角度上,少调用了一次拷贝构造函数和析构函数,从空间的角度上,节省了一个对象的空间。赋值运算符函数不会改变传入的实例的状态,因此使用const修饰。
3.判断传入的参数和当前的实例是不是同一个实例,如果是同一个,则没有赋值的必要。
4.要先开辟空间再释放原先的内存,如果由于内存不足抛出异常,我们还没有修改原来实例的状态,因此实例的状态还是有效的,这也就保证了异常安全性。
这个代码还可以进行优化,借助构造函数完成内存分配和strcpy以及异常处理。
方法2:
CMyString& CMyString::operator=(const CMyString& s)
{
if(this != &s)
{
CMyString tmp(s);
swap(tmp.m_pData,m_pData);
}
return *this;
}
这段代码先创建了一个临时实例tmp,接着把tmp.m_pData和实例自身的m_pData作交换。tmp是一个临时变量,在if结束后tmp生命周期终止,自动调用析构函数,释放掉交换后的空间,被s实例初始化的内存保存在实例自身的m_pData中。
这段代码还有一种写法
方法3:
CMyString& CMyString::operator=(CMyString s)
{
if(this != &s)
{
swap(s.m_pData,m_pData);
}
return *this;
}
参数不用const,也不用引用,这样就形成了一个天然的临时变量,函数调用时,实参通过拷贝构造函数初始化形参s,将s的m_pData和实例自身的m_pData交换,当函数结束时,s自动调用析构函数。