1. 程式人生 > >何時需要自定義複製建構函式

何時需要自定義複製建構函式

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

  本文涉及物件的賦值和複製(也稱為克隆)。必要時,先看譚浩強教材P291-295的相關內容或PPT,重溫一下有關概念。

  一、一般情況

  先看一個例子:

//例程1#include
<iostream>
using namespace std;class Complex{public: Complex(){real=0;imag=0;} Complex(double r,double i){real=r;imag=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){ Complex c; c.real=c1.real+c2.real; c.imag=c1.imag+c2.imag; return c;}//輸出的運算子過載ostream& operator << (ostream& output,const Complex& c){ output<<"("
<<c.real; if(c.imag>=0) output<<"+";   output<<c.imag<<"i)";     return output;}int main()Complex c1(3,4),c2(5,-10),c3cout<<"c1="<<c1<<endlcout<<"c2="<<c2<<endl;        c3=c1+c2; cout<<"c1+c2="<<c3<<endl;; system("pause"); return 0;}
注意對相加運算過載函式的定義:

Complex operator+(const Complex &c1, const Complex &c2){ Complex c; c.real=c1.real+c2.real; c.imag=c1.imag+c2.imag; return c;}
  從變數的作用域角度講,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 <iostream>using namespace std;class Douary{public: Douary(int m, int n);//建構函式:用於建立動態陣列存放m行n列的二維陣列(矩陣)元素,並將該陣列元素初始化為0 ~Douary(); //解構函式:用於釋放動態陣列所佔用的儲存空間 friend istream &operator>>(istream &input, Douary &d);//過載運算子“>>”輸入二維陣列,其中d為Dousry類物件; friend ostream &operator<<(ostream &output, Douary &d);//過載運算子“<<”以m行n列矩陣的形式輸出二維陣列,其中d為Douary類物件。 friend Douary operator+(const Douary &d1,const Douary &d2);//兩個矩陣相加,規則:對應位置上的元素相加privateint *Array;       //Array 為動態陣列指標。 int row;          //row  為二維陣列的行數。 int col;          //col   為二維陣列的列數。};Douary::Douary(int m, int n) //建構函式:用於建立動態陣列存放m行n列的二維陣列(矩陣)元素,並將該陣列元素初始化為{ row=m; col=n; Array = new int[m*n]; for(int i=0; i<row; ++i)  for(int j=0; j<col; ++j)   Array[i*col+j]=0;}Douary::~Douary() //解構函式:用於釋放動態陣列所佔用的儲存空間delete [] Array;}istream &operator>>(istream &input, Douary &d)//過載運算子“>>”輸入二維陣列,其中d為Dousry類物件for(int i=0; i<d.row; ++i)  for(int j=0; j<d.col; ++j)   cin>>d.Array[i*d.col+j]; return input;}ostream &operator<<(ostream &output, Douary &d)//過載運算子“<<”以m行n列矩陣的形式輸出二維陣列,其中d為Douary類物件for(int i=0; i<d.row; ++i) {  for(int j=0; j<d.col; ++j)   cout<<d.Array[i*d.col+j]<<"\t";  cout<<endl; } cout<<endlreturn output;}Douary operator+(const Douary &d1,const Douary &d2)//兩個矩陣相加,規則:對應位置上的元素相加//在此可以先判斷d1和d2的行列是否相同,如果不相同可以報錯退出,不做運算。本參考解答忽略了這一前提 Douary d(d1.row,d1.col)for(int i=0; i<d1.row; ++i) {  for(int j=0; j<d1.col; ++j)   d.Array[i*d1.col+j]=d1.Array[i*d1.col+j]+d2.Array[i*d1.col+j]; } return d;}int main()Douary d1(2,3),d2(2,3)cout<<"輸入d1(2,3):"<<endlcin>>d1; cout<<"輸入d2(2,3):"<<endlcin>>d2; cout<<"d1+d2="<<endl; Douary d3=d1+d2; cout<<d3; system("pause"); return 0;}
  在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 <iostream>using namespace std;class Douary{public: Douary(int m, int n);//建構函式:用於建立動態陣列存放m行n列的二維陣列(矩陣)元素,並將該陣列元素初始化為0 ~Douary(); //解構函式:用於釋放動態陣列所佔用的儲存空間 Douary(const Douary &d);//複製建構函式 friend istream &operator>>(istream &input, Douary &d);//過載運算子“>>”輸入二維陣列,其中d為Dousry類物件; friend ostream &operator<<(ostream &output, Douary &d);//過載運算子“<<”以m行n列矩陣的形式輸出二維陣列,其中d為Douary類物件。 friend Douary operator+(const Douary &d1,const Douary &d2);//兩個矩陣相加,規則:對應位置上的元素相加privateint *Array;       //Array 為動態陣列指標。 int row;          //row  為二維陣列的行數。 int col;          //col   為二維陣列的列數。};Douary::Douary(int m, int n) //建構函式:用於建立動態陣列存放m行n列的二維陣列(矩陣)元素,並將該陣列元素初始化為{ row=m; col=n; Array = new int[m*n]; for(int i=0; i<row; ++i)  for(int j=0; j<col; ++j)   Array[i*col+j]=0;}Douary::Douary(const Douary &d){ row=d.row; col=d.col; Array = new int[row*col]; for(int i=0; i<row; ++i)  for(int j=0; j<col; ++j)   Array[i*col+j]=d.Array[i*col+j];}Douary::~Douary() //解構函式:用於釋放動態陣列所佔用的儲存空間delete [] Array;}istream &operator>>(istream &input, Douary &d)//過載運算子“>>”輸入二維陣列,其中d為Dousry類物件for(int i=0; i<d.row; ++i)  for(int j=0; j<d.col; ++j)   cin>>d.Array[i*d.col+j]; return input;}ostream &operator<<(ostream &output, Douary &d)//過載運算子“<<”以m行n列矩陣的形式輸出二維陣列,其中d為Douary類物件for(int i=0; i<d.row; ++i) {  for(int j=0; j<d.col; ++j)   cout<<d.Array[i*d.col+j]<<"\t";  cout<<endl; } cout<<endlreturn output;}Douary operator+(const Douary &d1,const Douary &d2)//兩個矩陣相加,規則:對應位置上的元素相加//在此可以先判斷d1和d2的行列是否相同,如果不相同可以報錯退出,不做運算。本參考解答忽略了這一前提 Douary d(d1.row,d1.col)for(int i=0; i<d1.row; ++i) {  for(int j=0; j<d1.col; ++j)   d.Array[i*d1.col+j]=d1.Array[i*d1.col+j]+d2.Array[i*d1.col+j]; } return d;}int main()Douary d1(2,3),d2(2,3)cout<<"輸入d1(2,3):"<<endlcin>>d1; cout<<"輸入d2(2,3):"<<endlcin>>d2; cout<<"d1+d2="<<endl; Douary d3=d1+d2; cout<<d3; system("pause"); return 0;}

  四、總結

  當類中的資料成員需要動態分配儲存空間時,不可以依賴預設的複製建構函式。在需要時(包括這種物件要賦值、這種物件作為函式引數要傳遞、函式返回值為這種物件等情況),要考慮到自定義複製建構函式。

  另外,複製建構函式一經定義,賦值運算也按新定義的複製建構函式執行。



<本文完>


           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述