参考:
《剑指offer 名企面试官精讲典型编程题 纪念版-何海涛 著》
题目:为如下类型添加赋值运算符函数
class CMyString
{
public:
CMyString(char* pData = NULL);
CMyString(const CMyString& str);
~CMyString(void);
private:
char* m_pData;
};
1 经典解法
需要注意的:
返回值:返回实例自身的引用,才能支持对象的连续赋值
传入参数:常量引用,内部不会改变传入对象的状态
注意释放实例自身已有内存
判断传入参数和当前实例是否是同一个实例(同一个则不用赋值,不是同一个才能释放内存),
判断是否是同一实例应当最先判断。
缺点:
未考虑内存不足时,new char将抛出异常,导致m_pChar将是空指针,进而导致程序崩溃的情况。
/* 经典解法,未考虑内存不足时,new char将抛出异常,导致m_pChar将是空指针,会导致程序崩溃 需要注意的: 返回值:返回实例自身的引用,才能支持对象的连续赋值 传入参数:常量引用 注意释放实例自身已有内存 判断传入参数和当前实例是否是同一个实例(同一个则不用赋值,不是同一个才能释放内存), 判断是否是同一实例应当最先判断。 */
CMyString& CMyString::operator = (const CMyString& str){
if(this == &str)
return *this;
delete []m_pData;
m_pData = NULL; // 删除后要置空
m_pData = new char[strlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
return *this;
}
2考虑异常安全性的解法
考虑异常安全性的解法:
内存不足时,new char会抛出异常
两种解决办法:
(1)先new,判断是否成功分配内存,再delete释放原有内容。若new分配异常,则可保持原有内容。
(2)创建临时实例,再交换临时实例和原实例
第(2)种更优:
如下面的代码所示:if作用域内创建临时实例,超出作用域后临时实例自动调用析构函数
临时实例的构造函数中new分配内存,如果内存不足抛出异常,则还没有修改实例,原有实例依然有效,保证了异常安全性。
需要注意的:if的作用域必不可少,巧妙利用了类超出作用域时自动调用析构函数的规则。
/* 考虑异常安全性的解法: 内存不足时,new char会抛出异常 两种解决办法: (1)先new,判断是否成功分配内存,再delete释放原有内容。若new分配异常,则可保持原有内容。 (2)创建临时实例,再交换临时实例和原实例 第(2)种更优: if作用域内创建临时实例,超出作用域后临时实例自动调用析构函数 临时实例的构造函数中new分配内存,如果内存不足抛出异常,则还没有修改实例,原有实例依然有效,保证了异常安全性 */
CMyString& CMyString::operator = (const CMyString& str){
if(this != &str)
{
CMyString strTemp(str);
// 交换两个实例的内容,
// 构造函数已保证m_pChar不为空指针,
// 交换则保证对方的m_pChar不为空指针,且临时实例能够正确析构
char* pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData;
m_pData = pTemp;
}
return *this;
}
完整实现与测试:
// 剑指offer 面试题1 赋值运算符函数
#include <iostream>
#include <string.h>
using std::cout;
using std::endl;
// 为如下类型添加赋值运算符函数
class CMyString
{
public:
CMyString(char* pData = NULL);
CMyString(const CMyString& str);
~CMyString(void);
// 赋值运算符函数,重载运算符'='
CMyString& operator = (const CMyString& str);
void Print();
private:
char* m_pData;
};
CMyString::CMyString(char* pData){
if(pData == NULL){
m_pData = new char[1]; // 注意,空串也要添加'\0'字符串结束标志,否则运行时错误,无法打印
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;
}
void CMyString::Print()
{
cout << m_pData;
}
/* 经典解法,未考虑内存不足时,new char将抛出异常,导致m_pChar将是空指针,会导致程序崩溃 需要注意的: 返回值:返回实例自身的引用,才能支持对象的连续赋值 传入参数:常量引用 注意释放实例自身已有内存 判断传入参数和当前实例是否是同一个实例(同一个则不用赋值,不是同一个才能释放内存), 判断是否是同一实例应当最先判断。 */
/* CMyString& CMyString::operator = (const CMyString& str){ if(this == &str) return *this; delete []m_pData; m_pData = NULL; // 删除后要置空 m_pData = new char[strlen(str.m_pData) + 1]; strcpy(m_pData, str.m_pData); return *this; } */
/* 考虑异常安全性的解法: 内存不足时,new char会抛出异常 两种解决办法: (1)先new,判断是否成功分配内存,再delete释放原有内容。若new分配异常,则可保持原有内容。 (2)创建临时实例,再交换临时实例和原实例 第(2)种更优: if作用域内创建临时实例,超出作用域后临时实例自动调用析构函数 临时实例的构造函数中new分配内存,如果内存不足抛出异常,则还没有修改实例,原有实例依然有效,保证了异常安全性 */
CMyString& CMyString::operator = (const CMyString& str){
if(this != &str)
{
CMyString strTemp(str);
// 交换两个实例的内容,
// 构造函数已保证m_pChar不为空指针,
// 交换则保证对方的m_pChar不为空指针,且临时实例能够正确析构
char* pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData;
m_pData = pTemp;
}
return *this;
}
// 赋值给另一对象
void Test1(){
cout << "Test1 begins:" << endl;
char* text = "Hello World";
CMyString str1(text);
CMyString str2;
str2 = str1;
cout << "The expected result is: " << text << endl;
cout << "The actual result is: ";
str2.Print();
cout << endl;
}
// 赋值给自己
void Test2(){
cout << "Test2 begins:" << endl;
char* text = "Hello World";
CMyString str1(text);
str1 = str1;
cout << "The expected result is: " << text << endl;
cout << "The actual result is: ";
str1.Print();
cout << endl;
}
// 连续赋值
void Test3(){
cout << "Test3 begins:" << endl;
char* text = "Hello World";
CMyString str1(text);
CMyString str2, str3;
str3 = str2 = str1;
cout << "The expected result is: " << text << endl;
cout << "The actual result is: ";
str2.Print();
cout << endl;
cout << "The expected result is: " << text << endl;
cout << "The actual result is: ";
str3.Print();
cout << endl;
}
int main(){
Test1();
Test2();
Test3();
return 0;
}
运行结果: