面試中的賦值運算符函數

   當一個面試官要求應聘者定義一個賦值運算符函數時,他會在檢查應聘者寫出的代碼中關注如下幾點:

       (1)是否把返回值的類型聲明爲該類的引用,並在函數結束的時侯返回示例自身的引用(即*this).只有返回一個引用,纔可以允許連續賦值。

      (2)是否把參數的類型聲明爲常量引用。如果傳入的參數不是引用而是實例,那麼從形參到實參會調用一次構造函數。把參數聲明爲引用時可以避免這樣無謂的消耗,能提高代碼的效率。

       (3)是否釋放實例自身已有的內存。如果忘記釋放,會導致內存的泄漏。

        (4)是否判斷傳入的參數和當前的實例是不是同一個實例。如果是同一個,則不用進行賦值,直接返回。如果不進行判斷時,會導致比較嚴重的問題就是,賦值的內容被刪除了。

     下面給出一個MyString類的實現:

#include<iostream>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>

using namespace std;

class MyString{
public:
    MyString(char *pdata = NULL);
    MyString(const MyString& str);
    MyString& operator=(const MyString& str);
    ~MyString(void);
    void Print();
private:
    char *m_pdata;
};
//構造函數
MyString::MyString(char *pdata)
{
    if(pdata == NULL){
        m_pdata = new char[1];
        m_pdata[0] = '\0';
    }else{
        int len = strlen(pdata);
        m_pdata = new char[len + 1];
        strcpy(m_pdata,pdata);
    }
}
//拷貝構造函數
MyString::MyString(const MyString& str)
{
    int length = strlen(str.m_pdata);
    m_pdata = new char[length + 1];
    strcpy(m_pdata,str.m_pdata);
}
//析構函數
MyString::~MyString()
{
    delete []m_pdata;
}
//賦值運算
MyString& MyString::operator = (const MyString& 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;
}
void MyString::Print()
{
    printf("%s", m_pdata);
}

void Test1()
{
    printf("Test1 begins:\n");

    char text[] = "Hello world";

    MyString str1(text);
    MyString 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";

    MyString 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";

    MyString str1(text);
    MyString 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;
}

我們先來看測試程序的執行結果:

  根據測試的程序來看,賦值運算符函數基本沒有什麼問題了。

     上述對於賦值運算符函數的實現只適用於初級的程序員,要想和別人不一樣,我們就需要考慮下面的問題:

  在前面的函數中,我們在分配內存之前先用delete釋放了實例m_pdata的內存。如果此時內存不足導致new char拋出異常,m_pdata將是一個空指針,這樣非常容易導致程序崩潰。也就是說一旦在賦值運算符內部拋出了一個異常,實例將不再保持原有的有效狀態,這就違背了異常安全性的原則。

    想要解決這個問題我們有兩種辦法:(1)一個簡單的辦法就是我們先用new 分配新內容再用delete釋放已有的內容。這樣只在分配i內容成功之後再釋放原來的內容,當分配失敗時,我們能確保原來的實例還存在。

 (2)第二個更好的辦法就是,先創建一個臨時實例,再交換臨時實例和原來的實例。

 下面我們給出第二種辦法的代碼:

MyString& MyString::operator = (const MyString& str)
{
    if(this != &str){
        MyString strtemp(str);
        
        char *temp = strtemp.m_pdata;
        strtemp.m_pdata = m_pdata;
        m_pdata = temp;
    }
    return *this;
}

局部的對象strtemp在函數執行完就要調用析構函數,利用析構函數釋放了原來實例的內容。。。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章