C++ 賦值運算符'='的重載(淺拷貝错敢、深拷貝)

01 賦值運算符重載的需求

有時候希望賦值運算符兩邊的類型可以不匹配,比如:把一個 int 類型變量賦值給一個Complex(復數(shù))對象厂画,或把一個 char* 類型的字符串賦值給一個字符串對象凸丸,此時就需要重載賦值運算符‘=’。

需要注意的是:賦值運算符 = 只能重載為成員函數(shù)袱院。


02 賦值運算符重載的例子

下面我們以自定義一個自己的字符串類代碼的例子屎慢,講解賦值運算符的重載函數(shù)。

class MyString // 字符串類
{
public:
    // 構造函數(shù)忽洛,默認初始化1個字節(jié)的字符
    MyString ():m_str(new char[1])
    {
        m_str[0] = 0;
    }
    
    // 析構函數(shù)腻惠,釋放資源
    ~MyString()
    {
        delete [] m_str;
    }
    
    const char* c_str()
    {
        return m_str;
    }
    
    // 賦值運算符重載函數(shù)
    // 重載=號使得 obj = "Hello" 能夠成立
    MyString & operator= (const char *s)
    {
        // 釋放舊字符串資源
        delete [] m_str;
        
        // 生成新字符串的空間大小,長度多+1的目的是存放\0
        m_str = new char[strlen(s) +1 ];
        
        // 拷貝新字符串的內容
        strcpy(m_str, s);
        
        // 返回該對象
        return *this;
    }
    
private:
    char * m_str; // 字符串指針
};

int main() {
    
    MyString s;
    
    s = "Hello~"; // 等價于s.operator=("Hello~");
    std::cout << s.c_str()  << std::endl;
    
    // MyString s2 = "Hello!"; // 這條語句要是不注釋就會編譯報錯
    
    s = "Hi~"; // 等價于s.operator=("Hi~");
    std::cout << s.c_str()  << std::endl;

    return 0;
}

輸出結果:

Hello~
Hi~

重載=號運算符函數(shù)后欲虚,s = "Hello~"; 語句就等價于 s.operator=("Hello~");集灌。

需要注意的一點是:上面的MyString s2 = "Hello!";語句實際上是初始化語句,而不是賦值語句复哆,因為是初始化語句欣喧,所以需要調用構造函數(shù)進行初始化,那么這時就需要有char*參數(shù)的構造函數(shù)梯找,由于我們沒有定義此構造函數(shù)唆阿,所以就會編譯出錯。


03 淺拷貝和深拷貝

還是依據上面的例子锈锤,假設我們要實現(xiàn)最后一個語句的方式:

MyString s1,s2;
s1 = "this"; // 調用重載的賦值語句
s2 = "that"; // 調用重載的賦值語句
s1 = s2; // 如何實現(xiàn)這個酷鸦??

s1 = s2;語句目的希望是s1對象放的字符串和s2對象放的字符串象要一樣牙咏,由于 = 號兩邊的類似都是對象,編譯器會用原生的賦值運算符函數(shù)嘹裂,但是這個原生的賦值運算符函數(shù)對于有指針成員變量的對象來說妄壶,是非常危險的!

淺拷貝

如果用原生的賦值運算符函數(shù)去賦值有指針成員變量的對象寄狼,就會使得兩個對象的指針地址也是一樣的丁寄,也就是兩個對象的指針成員變量指向的地址是同一個地方氨淌,這種方式就是淺拷貝。

這時當一個對象釋放了指針成員變量時伊磺,那么另外一個對象的指針成員變量指向的地址就是空的了盛正,再次使用這個對象時,程序就會奔潰了屑埋,因為該對象的指針成員函數(shù)已經是個不合法的指針了豪筝!

淺拷貝 .png

深拷貝

如果對象里面有指針成員變量,則我們需要對原生的賦值運算符函數(shù)摘能,防止出現(xiàn)程序出錯現(xiàn)象的發(fā)生续崖。

因此要在 class MyString 類里加上如下成員函數(shù):

MyString & operator=(const MyString & s)
{
    // 釋放舊字符串資源
    delete [] m_str;
    
    // 生成新字符串的空間大小,長度多+1的目的是存放\0
    m_str = new char[strlen(s.m_str) +1 ];
    
    // 拷貝新字符串的內容
    strcpy(m_str, s.m_str);
    
    // 返回該對象
    return *this;
}

這么做就夠了嗎团搞?還有什么需要改進的地方嗎严望?

我們在考慮下面的語句:

MyString s;
s = "Hello";
s = s; // 是否會有問題?

最后一個語句是否會有問題逻恐?

s = s;等價于s.operator=(s)像吻,由于s和s是相同的對象,那么就沒必要完全執(zhí)行重載的賦值 = 的函數(shù)了复隆,我們再加個判斷拨匆,當左右兩邊是相同對象時,就直接返回該對象就好:

MyString & operator=(const MyString & s)
{
    // 當左右兩邊是相同對象時昏名,就直接返回該對象就
    if(this == &s)
        return *this;

    delete [] m_str;
    m_str = new char[strlen(s.m_str) +1 ];
    strcpy(m_str, s.m_str);
    return *this;
}

對operator=返回值類型的討論

  • void 好不好涮雷?
  • MyString 好不好?
  • 為什么是MyString &轻局?

當我們重載一個運算符的時候洪鸭,好的風格應該是盡量保留運算符原本的特性

考慮:

  • a = b = c; 這個賦值語句的順序是先b = c,然后在a = (b = c)仑扑。如果返回的void類型览爵,那么a = (void)顯然是不成立的;
  • (a = b) = c; 這個賦值語句會修改a的值镇饮,如果返回的類型是MyString對象蜓竹,那么就無法修改a的值了。

分別等價于:

  • a.operator=(b.operator=(c));
  • (a.operator=(b)).operator=(c);

所以綜上考慮储藐,operator=返回值類型是MyString &是比較好的俱济。

04 復制(拷貝)構造函數(shù)

上面的MyString類是否就沒有問題了?

MyString s;
s = "Hello";
MyString s1(s); // 要考慮這種情況钙勃,那就要重載復制(拷貝)構造函數(shù)

如果使用默認的復制(拷貝)構造函數(shù)蛛碌,那就對有指針成員變量的對象會有問題,因為會默認的復制(拷貝)構造函數(shù)會導致兩個對象的指針成員變量指向同一個的空間辖源。

所以需要對復制(拷貝)構造函數(shù)重載蔚携,并實現(xiàn)深拷貝的方式:

MyString (const MyString &s)
{
    m_str = new char[strlen(s.m_str) + 1];
    strcpy(m_str, s.m_str);
}

05 小結

最后的所有代碼希太,如下:

class MyString // 字符串類
{
public:
    // 構造函數(shù),默認初始化1個字節(jié)的字符
    MyString ():m_str(new char[1])
    {
        m_str[0] = 0;
    }
    
    // 復制(拷貝)構造函數(shù)
    MyString (const MyString &s)
    {
        m_str = new char[strlen(s.m_str) + 1];
        strcpy(m_str, s.m_str);
    }
    
    // 析構函數(shù)酝蜒,釋放資源
    ~MyString()
    {
        delete [] m_str;
    }
    
    const char* c_str()
    {
        return m_str;
    }
    
    // 賦值運算符重載函數(shù)
    // 重載=號使得 obj = "Hello" 能夠成立
    MyString & operator= (const char *s)
    {
        // 釋放舊字符串資源
        delete [] m_str;
        
        // 生成新字符串的空間大小誊辉,長度多+1的目的是存放\0
        m_str = new char[strlen(s) +1 ];
        
        // 拷貝新字符串的內容
        strcpy(m_str, s);
        
        // 返回該對象
        return *this;
    }
    
    // 賦值運算符重載函數(shù)
    // 重載=號使得 obj1 = obj2 能夠成立
    MyString & operator=(const MyString & s)
    {
        // 當左右兩邊是相同對象時,就直接返回該對象就
        if(this == &s)
            return *this;
    
        delete [] m_str;
        m_str = new char[strlen(s.m_str) +1 ];
        strcpy(m_str, s.m_str);
        return *this;
    }
    
private:
    char * m_str; // 字符串指針
};

int main() 
{
    
    MyString s1,s2;
    
    s1 = "Hello~"; // 等價于s1.operator=("Hello~");
    std::cout << s1.c_str()  << std::endl;
    
    
    s2 = "Hi~"; // 等價于s2.operator=("Hi~");
    std::cout << s2.c_str()  << std::endl;
    
    s1 = s2;   // 等價于s1.operator=(s2);
    std::cout << s1.c_str()  << std::endl;
    
    MyString s3(s1);  // 復制構造函數(shù)
    std::cout << s3.c_str()  << std::endl;

    return 0;
}

輸出如下:

Hello~
Hi~
Hi~
Hi~
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末亡脑,一起剝皮案震驚了整個濱河市堕澄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌远豺,老刑警劉巖奈偏,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異躯护,居然都是意外死亡惊来,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門棺滞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裁蚁,“玉大人,你說我怎么就攤上這事继准⊥髦ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵移必,是天一觀的道長室谚。 經常有香客問我,道長崔泵,這世上最難降的妖魔是什么秒赤? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮憎瘸,結果婚禮上入篮,老公的妹妹穿的比我還像新娘。我一直安慰自己幌甘,他們只是感情好潮售,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锅风,像睡著了一般酥诽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上皱埠,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天盆均,我揣著相機與錄音,去河邊找鬼漱逸。 笑死泪姨,一個胖子當著我的面吹牛,可吹牛的內容都是我干的饰抒。 我是一名探鬼主播肮砾,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼袋坑!你這毒婦竟也來了仗处?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤枣宫,失蹤者是張志新(化名)和其女友劉穎婆誓,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體也颤,經...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡洋幻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了翅娶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片文留。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖竭沫,靈堂內的尸體忽然破棺而出燥翅,到底是詐尸還是另有隱情,我是刑警寧澤蜕提,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布森书,位于F島的核電站,受9級特大地震影響谎势,放射性物質發(fā)生泄漏凛膏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一它浅、第九天 我趴在偏房一處隱蔽的房頂上張望译柏。 院中可真熱鬧,春花似錦姐霍、人聲如沸鄙麦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胯府。三九已至,卻和暖如春恨胚,著一層夾襖步出監(jiān)牢的瞬間骂因,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工赃泡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留寒波,地道東北人乘盼。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像俄烁,于是被迫代替她去往敵國和親绸栅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內容

  • C++運算符重載-下篇 本章內容:1. 運算符重載的概述2. 重載算術運算符3. 重載按位運算符和二元邏輯運算符4...
    Haley_2013閱讀 1,435評論 0 49
  • C++運算符重載-上篇 本章內容:1. 運算符重載的概述2. 重載算術運算符3. 重載按位運算符和二元邏輯運算符4...
    Haley_2013閱讀 2,291評論 0 51
  • 基本上我們進行運算符重載時有兩種形式页屠,類內的運算符重載和頂層函數(shù)位置的運算符重載粹胯。 操作符重載指的是將C++提供的...
    飛揚code閱讀 1,668評論 0 4
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,511評論 1 51
  • 我是你們大二的學姐 ,進來講活動的 辰企。 大家把手上的事情都暫時放一下风纠,要我接下來要講的事情對大家來說挺重要的 。就...
    你很完美閱讀 65評論 0 0