歡迎轉載,但請標明作者 “九天雁翎”,當然,你給出這個文章的連結更好。
還記得(1)中講到的建構函式嗎?複習一下,這次我們重載一個新的預設建構函式--即當你不給出初始值時調用的建構函式,我記得我講過這個概念吧,有嗎?看下面的例子。
例6.0
#include <string>
#include <iostream>
using namespace std;
class Fruit //定義一個類,名字叫Fruit
{
string name; //定義一個name成員
string colour; //定義一個colour成員
public:
void print() //定義一個輸出名字的成員print()
{
cout<<colour<<" "<<name<<endl;
}
Fruit(const string &nst,const string &cst = "green"):name(nst),colour(cst) //建構函式
{
name +="s";
}
Fruit(istream &is = cin) //新的建構函式
{
is>>colour>>name;
}
};
int main()
{
Fruit apple("apple"); //定義一個Fruit類對象apple
Fruit apple2;
apple.print();
apple2.print();
return 0;
}
發現我重載的預設建構函式沒有?這次利用的是預設形參(istream &is =cin),學過io的就應該知道,他的意思表示,預設就是從標準裝置輸入(如鍵盤)。你運行下,就知道怎麼回事了。現在我們講一個新內容,複製建構函式,什麼意思?先看下面的例子。
例6.1:
#include <string>
#include <iostream>
using namespace std;
class Fruit //定義一個類,名字叫Fruit
{
string name; //定義一個name成員
string colour; //定義一個colour成員
public:
void print() //定義一個輸出名字的成員print()
{
cout<<colour<<" "<<name<<endl;
}
Fruit(const string &nst,const string &cst = "green"):name(nst),colour(cst) //建構函式
{
name +="s";
}
Fruit(){}
};
int main()
{
Fruit apple("apple"); //定義一個Fruit類對象apple
Fruit apple2(apple);//發現這裡有什麼問題沒有?
apple.print();
apple2.print();
return 0;
}
你會發現apple2也輸出了green apples,為什麼啊?(apple)和("apple")一樣?你這這樣理解可就錯了,肯定不一樣嘛。但是當我們使用Fruit apple2(apple);的時候調用了哪個建構函式呢?我們沒有定義一個類似的建構函式啊?按道理應該編譯失敗,不是嗎?恩,這裡調用的建構函式就叫做複製建構函式,即用一個同樣類型的物件建構另一個對象的建構函式,不過在這裡,我們沒有定義,所以由系統幫我們自動定義的,叫做預設複製建構函式。效果自然就是複製一下。你把第一個對象改成apple3你就會發現,apple2沒有辦法定義了,因為它調用的是複製Fruit對象apple的建構函式,而不是用字串"apple"那個建構函式。C++ Primer這樣定義複製建構函式,我引用一下“只有單個形參,而且該形參是對本類類型對象的引用(常用const修飾)”。我們來看看系統合成的預設複製建構函式的一個有趣應用:
例6.2:
#include <iostream>
using namespace std;
class Aint
{
public:
int aival[3];
};
int main()
{
Aint as={1,2,3};
cout<<as.aival[0]<<as.aival[1]<<as.aival[2]<<endl;
Aint bs(as);
cout<<bs.aival[0]<<bs.aival[1]<<bs.aival[2]<<endl;
return 0;
}
很簡單的例子吧,不過也很有趣,我們都知道,數組是沒有辦法通過等於來複製的,要複製只能利用迴圈遍曆,我們自己定義了一個只包含整形數組的類,而當我們利用系統合成的預設複製建構函式的時候實現了數組的複製,注意,是一次性等於複製。呵呵。這也說明了一個問題,就是系統的預設複製建構函式在對付數組時,幫我們遍曆複製了。現在我們自己定義一個複製建構函式。要說明的是,一般情況下系統定義的複製建構函式已經夠用了,當你自己要定義的時候是想實現不同的功能,比如更好的處理指標的複製等,下面的例子只是看看用法,我也只講用法而不講究有沒有實際意義。
例6.3:
#include <string>
#include <iostream>
using namespace std;
class Fruit //定義一個類,名字叫Fruit
{
string name; //定義一個name成員
string colour; //定義一個colour成員
public:
void print() //定義一個輸出名字的成員print()
{
cout<<colour<<" "<<name<<endl;
}
Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst){} //建構函式
Fruit(Fruit &aF):name(aF.name),colour(aF.colour) //這是我們自己定義的複製建構函式
{
name +="s"; //讓他和預設的不同
}
};
int main()
{
Fruit apple; //定義一個Fruit類對象apple
Fruit apple2(apple);//調用的是我們自己定義的複製建構函式
apple.print();
apple2.print(); //你會發現輸出多了個's'
return 0;
}
這裡你會看到我們自己定義的複製建構函式的作用,直觀的看到apple只輸出green apple,而apple2輸出green aples,要說明的是,這也是複製建構函式也是建構函式,也可以用初始化列表,而且在C++ Primer中還推薦你使用初始化列表。下面我們看看,假如你向讓你的類禁止複製怎麼辦啊?很簡單,讓你的複製建構函式跑到private裡面去,這時候友元和成員還可以使用複製,那你就光聲明一個複製建構函式,但是,你不定義它,在C++裡面,光聲明不定義一個成員函數是合法的,但是,使用的話就會導致編譯失敗了,(普通函數也是這樣)通過這種手段,你就能禁止一切複製的發生了(其實是發現一切本需要複製建構函式的地方了)。見下例。
例6.4:
#include <string>
#include <iostream>
using namespace std;
class Fruit //定義一個類,名字叫Fruit
{
string name; //定義一個name成員
string colour; //定義一個colour成員
public:
void print() //定義一個輸出名字的成員print()
{
cout<<colour<<" "<<name<<endl;
}
Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)
{} //建構函式
private:
Fruit(Fruit &aF); //把它定義在private下
};
int main()
{
Fruit apple("apple"); //定義一個Fruit類對象apple
// Fruit apple2(apple); //你這樣的嘗試會導致編譯失敗的,cannot access private 錯誤
apple.print();
return 0;
}
在犯了一個我半天也沒有發現的錯誤的後,我發現了,當利用形如Fruit apple2 = apple方式來定義並初始化一個對象的時候,調用的也是複製建構函式,詳情請見那個文章《警惕!C++裡面“=”不一定就是等於(賦值)。 》