剑指offer (01):赋值运算符函数 (C++ 实现)

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 就是一个空指针,容易导致程序崩溃,也丢失了我们自身原来的内容。

要想解决这个问题,有两个办法:

  1. new 分配新内容,再delete 释放已有内容。这样及时分配内存失败,亦可以保留原先的实例不会被修改。
  2. 更好的办法是用赋值的实例创建一个临时实例,再交换临时实例和原来的实例。
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值