c++建構函式的知識在各種c++教材上已有介紹,不過初學者往往不太注意觀察和總結其中各種建構函式的特點和用法,故在此我根據自己的c++程式設計經驗總結了一下c++中各種建構函式的特點,並附上例子,希望對初學者有所幫助。
1. 建構函式是幹什麼的
class Counter { public: // 類Counter的建構函式 // 特點:以類名作為函式名,無返回型別 Counter() { m_value = 0; } private: // 資料成員 int m_value; }
該類物件被建立時,編譯系統物件分配記憶體空間,並自動呼叫該建構函式->由建構函式完成成員的初始化工作
eg: Counter c1;
編譯系統為物件c1的每個資料成員(m_value)分配記憶體空間,並呼叫建構函式Counter()自動地初始化物件c1的m_value值設定為0
故:建構函式的作用:初始化物件的資料成員。
2. 建構函式的種類
class Complex { private : double m_real; double m_imag; public: // 無引數建構函式 // 如果建立一個類你沒有寫任何建構函式,則系統會自動生成預設的無參建構函式,函式為空,什麼都不做 // 只要你寫了一個下面的某一種建構函式,系統就不會再自動生成這樣一個預設的建構函式,如果希望有一個這樣的無參建構函式,則需要自己顯示地寫出來 Complex(void) { m_real = 0.0; m_imag = 0.0; } // 一般建構函式(也稱過載建構函式) // 一般建構函式可以有各種引數形式,一個類可以有多個一般建構函式,前提是引數的個數或者型別不同(基於c++的過載函式原理) // 例如:你還可以寫一個 Complex( int num)的建構函式出來 // 建立物件時根據傳入的引數不同呼叫不同的建構函式 Complex(double real, double imag) { m_real = real; m_imag = imag; } // 複製建構函式(也稱為拷貝建構函式) // 複製建構函式引數為類物件本身的引用,用於根據一個已存在的物件複製出一個新的該類的物件,一般在函式中會將已存在物件的資料成員的值複製一份到新建立的物件中 // 若沒有顯示的寫複製建構函式,則系統會預設建立一個複製建構函式,但當類中有指標成員時,由系統預設建立該複製建構函式會存在風險,具體原因請查詢有關 “淺拷貝” 、“深拷貝”的文章論述 Complex(const Complex & c) { // 將物件c中的資料成員值複製過來 m_real = c.m_real; m_img = c.m_img; } // 型別轉換建構函式,根據一個指定的型別的物件建立一個本類的物件 // 例如:下面將根據一個double型別的物件建立了一個Complex物件 Complex::Complex(double r) { m_real = r; m_imag = 0.0; } // 等號運算子過載 // 注意,這個類似複製建構函式,將=右邊的本類物件的值複製給等號左邊的物件,它不屬於建構函式,等號左右兩邊的物件必須已經被建立 // 若沒有顯示的寫=運算子過載,則系統也會建立一個預設的=運算子過載,只做一些基本的拷貝工作 Complex &operator=(const Complex &rhs) { // 首先檢測等號右邊的是否就是左邊的物件本,若是本物件本身,則直接返回 if ( this == &rhs ) { return *this; } // 複製等號右邊的成員到左邊的物件中 this->m_real = rhs.m_real; this->m_imag = rhs.m_imag; // 把等號左邊的物件再次傳出 // 目的是為了支援連等 eg: a=b=c 系統首先執行 b=c // 然後執行 a= ( b=c的返回值,這裡應該是複製c值後的b物件) return *this; } };
下面使用上面定義的類物件來說明各個建構函式的用法:
void main() { // 呼叫了無參建構函式,資料成員初值被賦為0.0 Complex c1,c2; // 呼叫一般建構函式,資料成員初值被賦為指定值 Complex c3(1.0,2.5); // 也可以使用下面的形式 Complex c3 = Complex(1.0,2.5); // 把c3的資料成員的值賦值給c1 // 由於c1已經事先被建立,故此處不會呼叫任何建構函式 // 只會呼叫 = 號運算子過載函式 c1 = c3; // 呼叫型別轉換建構函式 // 系統首先呼叫型別轉換建構函式,將5.2建立為一個本類的臨時物件,然後呼叫等號運算子過載,將該臨時物件賦值給c1 c2 = 5.2; // 呼叫拷貝建構函式( 有下面兩種呼叫方式) Complex c5(c2); Complex c4 = c2; // 注意和 = 運算子過載區分,這裡等號左邊的物件不是事先已經建立,故需要呼叫拷貝建構函式,引數為c2 }
3. 思考與測驗
(1) 為什麼函式中可以直接訪問物件c的私有成員 ?
Complex(const Complex & c) { // 將物件c中的資料成員值複製過來 m_real = c.m_real; m_img = c.m_img; }
(2) 挑戰題,瞭解引用與傳值的區別
Complex test1(const Complex& c) { return c; } Complex test2(const Complex c) { return c; } Complex test3() { static Complex c(1.0,5.0); return c; } Complex& test4() { static Complex c(1.0,5.0); return c; } void main() { Complex a,b; // 下面函式執行過程中各會呼叫幾次建構函式,呼叫的是什麼建構函式? test1(a); test2(a); b = test3(); b = test4(); test2(1.2); // 下面這條語句會出錯嗎? test1(1.2); //test1( Complex(1.2 )) 呢? }
4. 淺拷貝與深拷貝
上面提到,如果沒有自定義複製建構函式,則系統會建立預設的複製建構函式,但系統建立的預設複製建構函式只會執行“淺拷貝”,即將被拷貝物件的資料成員的值一一賦值給新建立的物件,若該類的資料成員中有指標成員,則會使得新的物件的指標所指向的地址與被拷貝物件的指標所指向的地址相同,delete該指標時則會導致兩次重複delete而出錯。下面是示例:
#include <iostream.h> #include <string.h> class Person { public : // 建構函式 Person(char * pN) { cout << "一般建構函式被呼叫 ! "; m_pName = new char[strlen(pN) + 1]; //在堆中開闢一個記憶體塊存放pN所指的字串 if(m_pName != NULL) { //如果m_pName不是空指標,則把形參指標pN所指的字串複製給它 strcpy(m_pName ,pN); } } // 系統建立的預設複製建構函式,只做位模式拷貝 Person(Person & p) { //使兩個字串指標指向同一地址位置 m_pName = p.m_pName; } ~Person( ) { delete m_pName; } private : char * m_pName; }; void main( ) { Person man("lujun"); Person woman(man); // 結果導致 man 和 woman 的指標都指向了同一個地址 // 函式結束析構時 // 同一個地址被delete兩次 } // 下面自己設計複製建構函式,實現“深拷貝”,即不讓指標指向同一地址,而是重新申請一塊記憶體給新的物件的指標資料成員 Person(Person & chs); { // 用運算子new為新物件的指標資料成員分配空間 m_pName=new char[strlen(p.m_pName)+ 1]; if(m_pName) { // 複製內容 strcpy(m_pName ,chs.m_pName); } // 則新建立的物件的m_pName與原物件chs的m_pName不再指向同一地址了 }