分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!
一、一般情況
先看乙個例子:
//例程1
#include
using
namespace
std;class
complex complex(double r,double i) friend complex operator+(const complex &c1, const complex &c2); friend ostream& operator
<< (ostream& output,const complex& c);private: double real; double imag;};//複數相加:(a+bi)+(c+di)=(a+c)+(b+d)i. complex operator+(const complex &c1, const complex &c2)//輸出的運算子過載ostream& operator
<< (ostream& output,const complex& c)int
main
()
注意對相加運算過載函式的定義:
complex operator+(const complex &c1, const complex &c2)
從變數的作用域角度講,complex c是函式的區域性變數,意味著當函式執行完後,c占用的記憶體空間將被釋放。那麼,在main()函式中呼叫c3=c1+c2時,c3能夠得到正確的結果嗎?答案是肯定的,在operate+(c1,c2)的最後,執行return c 的時候,c 被返回,通過c3=c1+c2中的賦值(=),c 的值被(複製)賦值給了c3,在完成使命之後,c 瀟灑謝幕。
物件的賦值(=)運算子的過載是預設的,不需要專門定義對「=」的過載去完成自定義類中物件的賦值。但是,這裡有乙個前提,類中不能包括動態分配的資料,否則「可能出現嚴重的後果」(譚浩強教材p293頁)。那這個嚴重的後果是什麼呢?稍後講。
與物件的賦值相類似的還有物件的複製。其實,在函式返回值為物件的時候,系統會將其中返回的物件複製出乙個新的臨時物件,並傳遞給該函式的呼叫處。在複製中,需要用到複製建構函式,但這個複製建構函式一般也不需要使用者定義,系統可以自動完成。這個「一般」暗示著什麼?返回的物件中不能包括動態分配的資料。
那我們勇敢一些,去以身試法,看看如果物件中包含了動態分配的資料後究竟會發生什麼事情。領教一下後果不是目的,目的在於找到解決的辦法。因為這也是實際應用中必須面臨的問題。
二、以身試法——返回包含動態分配資料的臨時物件
也從乙個例子開始。下面的例子建立乙個二維陣列類douary,完成矩陣的輸入、輸出和相加操作。與例程1 的區別是,資料成員中有指標,並其指標指向的空間在建構函式中動態分配,在析構函式中釋放。
//例程2
#include
using
namespace
std;class
douary;douary::douary(int m, int n) //建構函式:用於建立動態陣列存放m行n列的二維陣列(矩陣)元素,並將該陣列元素初始化為douary::~douary() //析構函式:用於釋放動態陣列所占用的儲存空間istream &operator>>(istream &input, douary &d)//過載運算子「>>」輸入二維陣列,其中d為dousry類物件ostream &operator
<
<
main
()
在operate+函式中,與例程1的operate+ 一樣,宣告了乙個臨時的區域性變數,經過一些運算後,函式返回這個區域性變數。
那結果又如何呢?看來是領教「嚴重後果」的時候了。
程式執行的結果是這樣的:
輸入d1(2,3):1 2 34 5 6輸入d2(2,3):9 8 76 5 4d1+d2=-17891602 1 00 -33686019 -1414812757請按任意鍵繼續. . .
我們看到,相加結果是錯誤的!在某些時候,類似的程式是彈出乙個視窗,報告記憶體溢位。
原因何在?在例程2中,第62 行return d; 後仍然也執行預設的複製建構函式將 d 物件複製給main()函式中的乙個臨時變數再賦值給了物件d3(第73行),複製完後,d 的空間被釋放。d3的array(指標)指向的空間,此時顯然已經不能由d3繼續使用,而是可以被系統分配了。d3的array指向乙個無法控制的空間,後果真的很嚴重。
三、解決辦法
究其原因,是因為預設的建構函式過於簡單,幹不了複製「帶有需要動態分配空間的資料成員」類的「瓷器活」。實際上,當類中無動態分配空間的資料成員時,複製工作也就是對應的成員逐一複製,而有了動態分配空間的資料成員,那是各有各的樣,沒法統一。於是在這個時候,需要我們做的是,自己定義複製建構函式,關鍵是在複製的時時候,動態分配相應的空間,將完整的物件複製下來。
例程2改進之後為:(注意新增加的複製建構函式douary(const douary &d);的宣告(第8行)和定義(第28-36行)即可,其他位置同例程2完全一樣)
#include
using
namespace
std;class
douary;douary::douary(int m, int n) //建構函式:用於建立動態陣列存放m行n列的二維陣列(矩陣)元素,並將該陣列元素初始化為douary::douary(const douary &d)istream &operator>>(istream &input, douary &d)//過載運算子「>>」輸入二維陣列,其中d為dousry類物件ostream &operator
<
<
main
()
四、總結
當類中的資料成員需要動態分配儲存空間時,不可以依賴預設的複製建構函式。在需要時(包括這種物件要賦值、這種物件作為函式引數要傳遞、函式返回值為這種物件等情況),要考慮到自定義複製建構函式。
另外,複製建構函式一經定義,賦值運算也按新定義的複製建構函式執行。
《本文完》
給我老師的人工智慧教程打call!
C 何時需要定義賦值 複製建構函式
繼承和動態記憶體分配 假設基類使用了動態記憶體分配,而且定義了析構函式 複製建構函式和賦值函式,但是在派生類中沒有使用動態記憶體分配,那麼在派生類中不需要顯示定義析構函式 複製建構函式和賦值函式。當基類和派生類採用動態記憶體分配時,派生類的析構函式 複製建構函式 賦值運算子都必須使用相應的基類方法來...
詳談C 何時需要定義賦值 複製建構函式
繼承和動態記憶體分配 假設基類使用了動態記憶體分配,而且定義了析構函式 複製建構函式和賦值函式,但是在派生類中沒有使用動態記憶體分配,那麼在派生類中不需要顯示定義析構函式 複製建構函式和賦程式設計客棧值函式。當基類和派生類採用動態記憶體分配時,派生類的析構函式 複製建構函式 賦值運算子都必須使用相應...
C 何時需要自定義析構函式呢?
物件銷毀時 如果我們自己沒有寫析構方法,編譯器會幫我們寫乙個然後呼叫。那麼問題來了,既然我不寫,編譯器會幫我寫,那我幹嘛要寫?有木有什麼情況必須我自己寫的?處理記憶體的時候,也就是把之前retain的物件 都release一次 include using namespace std 日期類 clas...