前文:C++ 面向?qū)ο?類接口和類的實(shí)現(xiàn)
帶參數(shù)的構(gòu)造函數(shù)
承接上文,這里先說帶參數(shù)的構(gòu)造函數(shù),自定義的構(gòu)造函數(shù)必須申明在類接口當(dāng)中,例如person.hh文件當(dāng)中虐沥。
class Person{
.....
public:
Person();
Person(
std::string const &pname,
std::string const &paddr,
size_t const &age);
.....
}
如果我們?nèi)匀幌M軌驑?gòu)造一個(gè)普通的Person對象而沒有任何特定的數(shù)據(jù)成員初始值,那么也必須顯式地聲明默認(rèn)構(gòu)造函數(shù)私股。 因此上面的Person類將支持兩個(gè)構(gòu)造函數(shù).
默認(rèn)構(gòu)造函數(shù)不必初始化Person對象的string據(jù)成員茂卦。 由于這些數(shù)據(jù)成員本身就是對象豁生,因此它們被自己的默認(rèn)構(gòu)造函數(shù)初始化為空字符串酸纲。 但是捣鲸,還有一個(gè)size_t數(shù)據(jù)成員。 該成員是內(nèi)置類型的變量闽坡,并且此類變量沒有構(gòu)造函數(shù)栽惶,因此不會(huì)自動(dòng)初始化愁溜。 因此,除非顯式初始化d_age數(shù)據(jù)成員的值外厂,否則其值為:
- 對于局部的Person對象內(nèi)部會(huì)隨機(jī)分配一個(gè)整數(shù)值
- 對于全局的Person對象會(huì)分配0冕象。
對于局部的Person對象來說,age的隨機(jī)值,可能不是一個(gè)好注意,所以我們可以在實(shí)現(xiàn)默認(rèn)的構(gòu)造函數(shù)來面初始化數(shù)據(jù)成員d_age
Person::Person(){
d_age=0;
}
接下來說明使用帶有參數(shù)和不帶參數(shù)的構(gòu)造函數(shù)汁蝶。 對象yang由定義非空參數(shù)列表的構(gòu)造函數(shù)初始化渐扮,addy對象即會(huì)調(diào)用默認(rèn)的構(gòu)造函數(shù)初始化。
使用需要參數(shù)的構(gòu)造函數(shù)構(gòu)造對象時(shí)掖棉,建議用花括號(hào)括起參數(shù)席爽。 通常也可以使用括號(hào),有時(shí)甚至必須使用括號(hào)啊片。這是顯示兩個(gè)構(gòu)造函數(shù)調(diào)用的示例:
int main(int argc, char const *argv[])
{
Person yang{"Yang Ming", "東風(fēng)路42號(hào)E棟二層 812號(hào)", 35};
Person addy; //初始化默認(rèn)的構(gòu)造函數(shù)
return 0;
}
如果使用默認(rèn)的參數(shù)值定義Person對象,則必須將相應(yīng)的構(gòu)造函數(shù)添加到Person的接口玖像。除了重載類構(gòu)造函數(shù)之外紫谷,還可以為構(gòu)造函數(shù)提供默認(rèn)參數(shù)值。 必須使用類接口中的構(gòu)造函數(shù)聲明指定這些默認(rèn)參數(shù)捐寥,如下所示:
class Person{
public:
Person(
std::string const &pname,
std::string const &paddr = "--未知--",
std::string const &phone = "--未知--",
size_t age = 0
);
}
通常笤昨,構(gòu)造函數(shù)使用高度相似的實(shí)現(xiàn)。 這是因?yàn)闃?gòu)造函數(shù)的參數(shù)通常是為了方便而定義的:
Person的構(gòu)造函數(shù)不需要phone,但需要age,這樣的構(gòu)造函數(shù)不能使用默認(rèn)構(gòu)造函數(shù)定義握恳,因?yàn)閜hone不是構(gòu)造函數(shù)的最后一個(gè)參數(shù)瞒窒,因此,特殊的構(gòu)造函數(shù)不需要在其參數(shù)列表中包含phone.
類構(gòu)造函數(shù)的奇葩問題
示例1:
在C++中,定義一個(gè)類的對象變量,如果你之前有其他類似Java,C#的開發(fā)語言經(jīng)驗(yàn)話,這類語言通常會(huì)使用括號(hào)"( )"調(diào)用構(gòu)造函數(shù),但在C++可能會(huì)導(dǎo)致意外的后果乡洼。如下示例會(huì)說明這一問題, 假設(shè)Foo類是以下類接口:
#include <iostream>
class Foo
{
public:
char a = '\0';
char b = '\0';
Foo();
Foo(char);
Foo(char, char);
void display();
};
Foo類的實(shí)現(xiàn)
Foo::Foo(){};
Foo::Foo(char x){
a = x;
}
Foo::Foo(char x, char y){
a = x;
b = y;
}
void Foo::display()
{
std::cout << "----打印Foo內(nèi)部數(shù)據(jù)成員-----" << std::endl;
std::cout << "Foo::a=" << a << std::endl;
std::cout << "Foo::b=" << b << std::endl;
}
我們在調(diào)用的main函數(shù)中定義兩個(gè)Foo實(shí)例崇裁,分別使用第一個(gè)和第二個(gè)構(gòu)造函數(shù),同時(shí)在對象定義中使用括號(hào)束昵。 代碼看起來像這樣:
int main(int argc, char const *argv[])
{
Foo c1();
Foo c2('A');
c1.display();
c2.display();
return 0;
}
C++ 11之后的編譯器在編譯前已經(jīng)報(bào)錯(cuò)了,更不用說編譯了:
error: request for member ‘display’ in ‘c1’, which is of non-class type ‘Foo()’
c1.display();
這里發(fā)生了什么拔稳? 首先,請注意編譯器引用的數(shù)據(jù)類型:Foo()是一個(gè)函數(shù)而不是Foo類型,那()是做什么的呢?
在回答這個(gè)問題之前锹雏,不妨拓寬一下我們的思路巴比。 假設(shè)在程序庫中有一個(gè)工廠函數(shù)fooFactory。 工廠函數(shù)創(chuàng)建并返回特定類型的對象礁遵。
Foo fooFactory(){
return Foo();
}
此fooFactory函數(shù)返回一個(gè)Foo對象使用Foo類現(xiàn)實(shí)中的默認(rèn)構(gòu)造函數(shù)構(gòu)造轻绞。 因此,fooFactory不需要參數(shù)佣耐。 我們想在程序中使用fooFactory函數(shù)政勃,但必須聲明該函數(shù),所以我們將聲明添加到main,因?yàn)檫@是fooFactory將被使用的唯一位置兼砖。 它是一個(gè)函數(shù)稼病,不需要參數(shù)选侨,返回一個(gè)Foo對象:
//函數(shù)聲明
Foo fooFactory();
int main(){
Foo obj=fooFactory();
}
這與我們上文示例1中main函數(shù)中定義的c1非常相似
Foo c1();
問題剖析:Foo c1()顯然不是c1對象的定義,而是c1()的函數(shù)聲明然走,返回一個(gè)Foo對象援制。 那么,這里發(fā)生了什么芍瑞,我們應(yīng)該如何使用Foo的默認(rèn)構(gòu)造函數(shù)定義Foo對象晨仑?
事實(shí)上,類似Foo c1()這樣的語句,你可以說它是一個(gè)函數(shù)的原型聲明也行,也可以是使用定義了Foo類型的一個(gè)對象拆檬,這個(gè)對象通過其Foo的默認(rèn)構(gòu)造函數(shù)構(gòu)建洪己。這是C ++語法的模糊性,根據(jù)語言的標(biāo)準(zhǔn)竟贯,C++編譯器總是優(yōu)先認(rèn)為Foo c1()類似的語句一個(gè)函數(shù)聲明
其次再是一個(gè)對象定義答捕。
使用類的默認(rèn)構(gòu)造函數(shù)定義對象時(shí),有幾種方法可以避免這種模糊性的。
- 比如上文示例中提過的和其他基本數(shù)據(jù)類型,例如Foo c1;
- 使用花括號(hào){}初始化對象,例如:Foo c1{};
- 使用賦值運(yùn)算符和匿名默認(rèn)構(gòu)造的Foo對象:Foo c1 = Foo {},或者Foo c1 = Foo()屑那。
上述上下文中的Foo()定義了默認(rèn)構(gòu)造的匿名Foo對象拱镐。 對于Foo c1 = Foo()這樣的語句。 新版本的C++編譯器持际,能夠分辨是Foo類型沃琅,而不是Foo()的函數(shù)聲明。
對于帶參數(shù)的構(gòu)造函數(shù),以下定義一個(gè)Foo的對象的語句是等價(jià)的蜘欲。
Foo c1('A');
Foo c1{'A'};
Foo c1 = Foo('A');
Foo c1 = Foo{'A'};
但為了好辨認(rèn),建議使用花括號(hào)定義類的對象
類型 Foo 和 Foo()的區(qū)別
首先讓我們看一下Foo中的第二個(gè)構(gòu)造函數(shù)益眉。 它期望一個(gè)char類型。 當(dāng)使用第二個(gè)構(gòu)造函數(shù)定義另一個(gè)Foo對象姥份,但想使用char()將默認(rèn)的char類型的值傳遞給構(gòu)造函數(shù)郭脂。 我們知道這定義了一個(gè)默認(rèn)的char值,因?yàn)閏out << char()<<endl很好地顯示了''澈歉,并且char x = char() 也將x初始化為空字符朱庆。因此我們定義下面的一個(gè)很怪僻的帶char()作為參數(shù)的Foo構(gòu)造函數(shù)
Foo c1(char()) ;
error: request for member ‘display’ in ‘c1’, which is of non-class type ‘Foo(char (*)())’
糟糕,并沒有我們預(yù)期的那樣闷祥? 這怎么又跟指針突然指針扯上關(guān)系了呢娱颊?
像int(),char(),double(),或用戶定義的class/struct類型這類基本數(shù)據(jù)類型的初始化表達(dá)式,我們統(tǒng)一用一個(gè)符號(hào)表示為
Type()
它不僅表示Type類型的默認(rèn)值,而且它也是指向?qū)?yīng)函數(shù)的匿名指針的簡寫符號(hào)
而對于類似Foo這樣的類接口(或者叫類聲明)
實(shí)際上就是要告知C++編譯器,需要分配多少內(nèi)存空間容納Foo的數(shù)據(jù)成員以及成員函數(shù)
請牢記對于類似Type()的表達(dá)式,C++的編譯器它會(huì)優(yōu)先解析為一個(gè)函數(shù)原型的聲明,而函數(shù)聲明實(shí)質(zhì)就是告知編譯器對應(yīng)的函數(shù)指針,那么當(dāng)你使用Foo c1(char())的時(shí)候,C++編譯器實(shí)際上會(huì)理解為就是調(diào)用Foo c1(char (*)())凯砍,而這與我們Foo類接口中構(gòu)造函數(shù)Foo::Foo(char)是不符的箱硕。