题目:给出如下类型为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=str2=str3这样的赋值语句将会出错。
2.是否把传入的参数的类型声明为常量引用。形参到实参会调用拷贝构造函数,而把参数声明为引用可以避免内存消耗,提高代码执行效率。 同时,在赋值运算符中不会改变传入实例的属性,所以应在传参前加上(const)关键字。
3.是否是否实例自身内存。程序结束后如果没有释放实例所在的内存,会造成内存泄漏。
4.是否判断传入的参数和当前的实例(*this)是否是同一个。如果是同一个,不用赋值,直接返回。当(*this)与传入的的参数是同一个实例时,一旦自身内存释放,传入参数的内存也会被释放,就无法找到需要赋值的内容。
基于以上要求可以写成如下代码:
CMyString& CMyString::operator=(const CMyString& str)
{
//检查自赋值
if(this == &str)
{
return *this;
}
//释放原资源
delete [] m_pData;
//分配新内存
m_pData = new char(strlen(str.m_pData) + 1);
strcpy_s(m_pData, strlen(str.m_pData) + 1, str.m_pData);
//返回引用
return *this;
}
可以看出,在分配内存前用delete释放了pData的内存。如果此时内存不足导致new char抛出异常,
pData将是一个空指针,这样容易导致程序崩溃 。
所以就有了如下改进:
CMyString& CMyString::operator=(const CMyString& str)
{
if (this != &str) //检查自赋值
{
CMyString tmp(str); //拷贝构造临时变量
char* p = tmp.m_pData;//交换对象与临时对象的数据
tmp.m_pData = this->m_pData;
this->m_pData = p;
}
return *this;
}
在这个函数中,我们先创建一个临时实例tmp,接着把
tmp.m_pData和实例自身的m_pData做交换。由于tmp是局部变量,但程序运行到if的外面时就出了该变量的作用域,就会自动调用
tmp的析构函数,把 tmp.m_pData所指向的内存释放掉。由于
tmp.m_pData指向的内存就是实例之前mpData的内存,这就相当于自动调用析构函数释放实例的内存
测试:
在类中加入Show函数,显示当前各对象的值
void Show() //测试
{
if (m_pData == nullptr) cout << "NULL" <<endl;
else
cout << this->m_pData << endl;
}
main函数:
int main()
{
char str[] = "Hello C++";
//实例化三个对象
CMyString str1;
CMyString str2;
CMyString str3;
str1 = str; //赋值
cout << "str1是: " ;
str1.Show();
str1 = str1;//自赋值
cout << "str1是: ";
str1.Show();
str3 = str2 = str1;//连续赋值
cout << "str2是: ";
str2.Show();
cout << "str3是: ";
str3.Show();
return 0;
}
运行结果: