解釋一:
衆所周知,c++中類沒有給出複製構造函數的話,編譯器會自動補上一個,然而對於深拷貝來說編譯器給的複製構造函數是無法勝任的。這時候,我們就要自己動手實現這個複製構造函數。
例如
-
class Animal
-
{
-
public:
-
Animal()
-
{
-
name = NULL;
-
age = 0;
-
}
-
-
Animal(const Animal &a)
-
{
-
cout << "copy constructor & runnig..." << endl;
-
int len = strlen(a.name);
-
name = new char[len + 1];
-
strcpy(name, a.name);
-
age = a.age;
-
//......
-
-
}
- ~Animal()
- {
- if (name != NULL) {
- delete []name;
- }
-
}
-
// protected:
-
char *name;
-
int age;
- };
-
- int main()
- {
- Animal a;
- Animal b = a;
-
- return 0;
- }
我們往往被告知複製構造函數的格式是固定的,如下:
類名 (類名 &ref)
{
......
}
可是當時就沒有想想它的形參爲什麼設計成引用類型,而不是對象或者指針類型呢?
首先,我想編譯器這麼規定是有他的道理的!! 以下愚見只供參考。
1) 引用比較高效:
如果形參是對象類型的,那麼它是否也要實例化? 而且整個對象進行拷貝,效率不高,很不划算。
如果形參是指針類型的,從編譯的角度來看 :
程序在編譯時分別將指針和引用添加到符號表上,符號表上記錄的是變量名及變量所對應地址。指針變量在符號表上對應的地址值爲指針變量的地址值,而引用在符號表上對應的地址值爲引用對象的地址值。指針相當於中介,租房的時候直接跟房東談比較划算 還是通過中介介紹划算,我想這個不用我說了吧(雖然我還沒租過房)。
2) 形參爲對象類型是行不通的:
複製構造函數也是構造函數,只不過它比較特別而已。上一條也提到了,如果形參是對象,在被調用時形參也需要實例化(構造)。 那麼它的構造是不是也有點類似於, 形參 = 實參 ;這麼說來,這不是在調用複製構造函數麼? 之後調用複製構造函數的形參又要實例化,又調用複製構造函數,沒完沒了。在複製構造函數的形參中調用了自個兒,這不就是遞歸調用麼? 這還不怎麼要緊,如果是我們平常使用遞歸往往會有出口點,但這裏的是個死遞歸,這纔要了命!(其實也要不了命,呵呵)
綜上所述,以引用作爲形參最爲合適而且高效
解釋二:
在C++中, 構造函數,拷貝構造函數,析構函數和賦值函數(賦值運算符重載)是最基本不過的需要掌握的知識。 但是如果我問你“拷貝構造函數的參數爲什麼必須使用引用類型?”這個問題, 你會怎麼回答? 或許你會回答爲了減少一次內存拷貝? 很慚愧的是,我的第一感覺也是這麼回答。不好還好,我有理性這個好品質。思索一下以後,發現這個答案是不對的。
看下面一個小例子:
- #include <iostream.h>
- class CExample
- {
- int m_nTest;
- public:
- CExample(int x):m_nTest(x) //帶參數構造函數
- {
- cout << "constructor with argument/n";
- }
- CExample(const CExample & ex) //拷貝構造函數
- {
- m_nTest = ex.m_nTest;
- cout << "copy constructor/n";
- }
- CExample& operator = (const CExample &ex)//賦值函數(賦值運算符重載)
- {
- cout << "assignment operator/n";
- m_nTest = ex.m_nTest;
- return *this;
- }
- void myTestFunc(CExample ex)
- {
- }
- };
- int main()
- {
- CExample aaa(2);
- CExample bbb(3);
- bbb = aaa;
- CExample ccc = aaa;
- bbb.myTestFunc(aaa);
- return 0;
- }
看這個例子的輸出結果:
constructor with argument
//
CExample aaa(2);
constructor with argument
//
CExample bbb(3);
assignment operator //
bbb = aaa;
copy constructor //
CExample ccc
= aaa;
copy constructor
// bbb.myTestFunc(aaa);
如果你能一眼看出就是這個結果的話, 恭喜你,不用再往下看了。
如果你的結果和輸出結果有誤差, 那拜託你謙虛的看完。
第一個輸出: constructor with argument // CExample aaa(2);
第二個輸出:constructor with argument // CExample bbb(3);
分析同第一個
第三個輸出: assignment operator // bbb = aaa;
第四個輸出: copy constructor // CExample ccc = aaa;
這兩個得放到一塊說。 肯定會有人問爲什麼兩個不一致。原因是, bbb對象已經實例化了,不需要構造,此時只是將aaa賦值給bbb,只會調用賦值函數,就這麼簡單, 但是ccc還沒有實例化,因此調用的是拷貝構造函數,構造出ccc,而不是賦值函數
第五個輸出: copy constructor // bbb.myTestFunc(aaa);
實際上是aaa作爲參數傳遞給bbb.myTestFunc(CExample ex), 即CExample ex = aaa;和第四個一致的, 所以還是拷貝構造函數,而不是賦值函數,
通過這個例子, 來分析一下爲什麼拷貝構造函數的參數只能使用引用類型。
看第四個輸出: copy constructor // CExample ccc = aaa;
構造ccc,實質上是ccc.CExample(aaa); 我們假如拷貝構造函數參數不是引用類型的話, 那麼將使得 ccc.CExample(aaa)變成aaa傳值給ccc.CExample(CExample ex),即CExample ex = aaa,因爲 ex 沒有被初始化, 所以 CExample ex = aaa 繼續調用拷貝構造函數,接下來的是構造ex,也就是 ex.CExample(aaa),必然又會有aaa傳給CExample(CExample ex), 即 CExample ex = aaa;那麼又會觸發拷貝構造函數,就這下永遠的遞歸下去。
所以繞了那麼大的彎子,就是想說明拷貝構造函數的參數使用引用類型不是爲了減少一次內存拷貝, 而是避免拷貝構造函數無限制的遞歸下去。
所以, 拷貝構造函數是必須要帶引用類型的參數的, 而且這也是編譯器強制性要求的