剑指offer 面试题1 赋值运算符函数

参考:

《剑指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;
}

运行结果:
《剑指offer 面试题1 赋值运算符函数》

点赞