1 题目
如下为类型 CMystring
的声明,请为该类型添加赋值运算符函数。
class CMyString
{
public:
CMyString(char* pData = nullptr);
CMyString(const CMyString& str);
~CMyString(void);
private:
char* m_pData;
};
2 题解
这道题就是一道重载赋值运算符为成员函数的题。需要考虑以下几点:
- 是否把返回值的类型声明为该类型的引用(
CMystring& ...
),并在函数结束后返回实例的引用(*this
)。只有返回一个,才可以连续赋值。否则,如果返回值是void,将不能连续赋值。 - 是否把传入的参数的类型声明为常量引用(
const CMystring &str
)。如果传入的不是引用而是实例,那么从形参到实参会调用一次复制构造函数。把参数声明为引用可以避免这样的无谓消耗,提高效率。同时,我们在函数内不会修改传入的实例的状态,因此应该加上const
关键字。 - 是否释放实例自身已有的内存。若未在分配空间前释放自身已有的空间,将会造成内存泄漏。
- 判断传入的参数和当前的实例(
*this
)是不是同一个实例。如果是,则不赋值。如果不判断,在释放自身内存时,也会删除原有的内存,就再也找不到赋值的内容。
2.1 经典解法,初级程序员
CMyString& CMyString::operator=(const CMystring& str)
{
// 判断是否为自身
if(this == &str)
return *this;
// 赋值前先删除原有内容
delete []m_pData;
m_pData = nullptr;
m_pData = new char[strlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
return *this;
}
注意:
这样做乍一看很不错,但是在分配内存之前先用 delete
释放了实例 m_pData
的内存。此时,若内存不足导致new char
抛出异常,则 m_pData
就是一个空指针,容易导致程序崩溃,也丢失了我们自身原来的内容。
要想解决这个问题,有两个办法:
- 先
new
分配新内容,再delete
释放已有内容。这样及时分配内存失败,亦可以保留原先的实例不会被修改。 - 更好的办法是用赋值的实例创建一个临时实例,再交换临时实例和原来的实例。
2.2 高级解法,考虑异常安全性
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
是一个局部变量,当程序运行到if
的外面时,也就出了改变量的作用域,就会自动调用 strTemp
的析构函数, 释放掉原有的内存。
同时我们在 CMystring
的构造函数里用 new
分配内存。若内存不足抛出诸如 bad_alloc 异常,但我们还没有修改原来实例的状态,这也就保证了异常安全性。
3 完整代码
#define _CRT_SECURE_NO_WARNINGS // 否则报不安全,VS2015
#include <cstdio>
#include <cstring>
class CMyString
{
public:
CMyString(char* pData = nullptr);
CMyString(const CMyString& str);
~CMyString(void);
CMyString& operator = (const CMyString& str); // 重载运算符 =
void Print();
private:
char* m_pData;
};
CMyString::CMyString(char * pData)
{
if (pData == nullptr) // 判断是否为空指针
{
m_pData = new char[1];
m_pData[0] = '\0';
}
else
{
int length = strlen(pData);
m_pData = new char[length + 1];
strcpy(m_pData, pData);
}
}
CMyString::CMyString(const CMyString & str)
{
int length = strlen(str.m_pData);
m_pData = new char[length + 1];
strcpy(m_pData, str.m_pData);
}
CMyString::~CMyString()
{
delete[] m_pData;
}
/*
// 初级做法,未考虑内存不足时的异常安全性
CMyString& CMyString::operator=(const CMystring& str)
{
// 判断是否为自身
if(this == &str)
return *this;
// 赋值前先删除原有内容
delete []m_pData;
m_pData = nullptr;
m_pData = new char[strlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
return *this;
}
*/
// 高级做法 A = B
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;
}
void CMyString::Print()
{
printf("%s", m_pData);
}
// ============================= 测试用例 =================================
// 正常赋值
void Test1()
{
printf("Test1 begins:\n");
char* text = "Hello world";
CMyString str1(text);
CMyString str2;
str2 = str1;
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str2.Print();
printf(".\n");
}
// 赋值给自己
void Test2()
{
printf("Test2 begins:\n");
char* text = "Hello world";
CMyString str1(text);
str1 = str1;
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str1.Print();
printf(".\n");
}
// 连续赋值
void Test3()
{
printf("Test3 begins:\n");
char* text = "Hello world";
CMyString str1(text);
CMyString str2, str3;
str3 = str2 = str1;
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str2.Print();
printf(".\n");
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str3.Print();
printf(".\n");
}
int main(int argc, char* argv[])
{
Test1();
Test2();
Test3();
return 0;
}