1. 程式人生 > >劍指offer (01):賦值運算子函式 (C++ 實現)

劍指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;
}