C++中經(jīng)常出現(xiàn)函數(shù)名字一樣,但參數(shù)列表或返回值不同的函數(shù),要搞清楚函數(shù)的正確調(diào)用關(guān)系,需理清三個(gè)概念:重寫(xiě)(override)、重載(overload)、重定義(redefine)。
一、三個(gè)基本概念
1、重定義(redefine):派生類(lèi)對(duì)基類(lèi)的成員函數(shù)重新定義,即派生類(lèi)定義了某個(gè)函數(shù),該函數(shù)的名字與基類(lèi)中的函數(shù)名字一樣。
特點(diǎn):(1)不在同一個(gè)作用域(分別位于基類(lèi)、派生類(lèi)) (2)函數(shù)的名字必須相同 (3)對(duì)函數(shù)的返回值、形參列表無(wú)要求
特殊情況:若派生類(lèi)定義的該函數(shù)與基類(lèi)的成員函數(shù)完全一樣(返回值、形參列表均相同),且基類(lèi)的該函數(shù)為virtual,則屬于派生類(lèi)重寫(xiě)基類(lèi)的虛函數(shù)。
作用效果:若重新定義了基類(lèi)中的一個(gè)重載函數(shù),則在派生類(lèi)中,基類(lèi)中該名字的函數(shù)(即其他所有重載版本)都被自動(dòng)隱藏,包括同名的虛函數(shù)。
2、重載(overload):函數(shù)名字相同,但它的形參個(gè)數(shù)或者順序,或者類(lèi)型不同,但是不能靠返回類(lèi)型來(lái)判斷。
特點(diǎn):(1)位于同一個(gè)類(lèi)中 (2)函數(shù)的名字必須相同 (3)形參列表不同(可能是參數(shù)個(gè)數(shù) or 類(lèi)型 or 順序 不同),返回值無(wú)要求
特殊情況:若某一個(gè)重載版本的函數(shù)前面有virtual修飾,則表示它是虛函數(shù)。但它也是屬于重載的一個(gè)版本
不同的構(gòu)造函數(shù)(無(wú)參構(gòu)造、有參構(gòu)造、拷貝構(gòu)造)是重載的應(yīng)用
作用效果和原理:編譯器根據(jù)函數(shù)不同的參數(shù)表,將函數(shù)體與函數(shù)調(diào)用進(jìn)行早綁定。重載與多態(tài)無(wú)關(guān),只是一種語(yǔ)言特性,與面向?qū)ο鬅o(wú)關(guān)。
3、重寫(xiě)(override):派生類(lèi)重定義基類(lèi)的虛函數(shù),即會(huì)覆蓋基類(lèi)的虛函數(shù) (多態(tài)性)
特點(diǎn):(1)不在同一個(gè)作用域(分別位于基類(lèi)、派生類(lèi)) (2)函數(shù)名、形參列表、返回值相同 (3)基類(lèi)的函數(shù)是virtual
特殊情況:若派生類(lèi)重寫(xiě)的虛函數(shù)屬于一個(gè)重載版本,則該重寫(xiě)的函數(shù)會(huì)隱藏基類(lèi)中與虛函數(shù)同名的其他函數(shù)。
作用效果:父類(lèi)的指針或引用根據(jù)傳遞給它的子類(lèi)地址或引用,動(dòng)態(tài)地調(diào)用屬于子類(lèi)的該函數(shù)。這個(gè)晚綁定過(guò)程只對(duì)virtual函數(shù)起作用
具體原理是由虛函數(shù)表(VTABLE)決定的,在第三節(jié)介紹。
二、程序?qū)嵗?/p>
1、兩個(gè)類(lèi):基類(lèi)( 取名Test)和派生類(lèi)( 取名XX) 名字不規(guī)范,哈哈隨便取得!
基類(lèi)和派生類(lèi)的結(jié)構(gòu)
//Base class
class Test
{
public:
int a;
Test()
{
cout<<"Test() 無(wú)參構(gòu)造函數(shù)!"<<endl;
}
Test(int data)
{
a = data;
cout<<"Test(int data) 有參構(gòu)造函數(shù)!"<<endl;
}
Test(const Test &tmp)
{
a = tmp.a;
cout<<"Test 拷貝構(gòu)造函數(shù)!"<<endl;
}
//基類(lèi)中對(duì)函數(shù)名f,進(jìn)行了重載。其中最后一個(gè)重載函數(shù)為虛函數(shù)
void f()const
{
cout<<"調(diào)用 void Test::f()"<<endl;
}
//overload
int f(int data) const
{
cout<<"調(diào)用 Test f(int data)"<<endl;
return 1;
}
//overload 虛函數(shù)
virtual double f(int dataA,int dataB)
{
cout<<"調(diào)用 Test f(int a,int b)"<<endl;
return dataA*dataB/2.0;
}
};
class XX: public Test
{
public:
Test atest;//先調(diào)用基類(lèi)的構(gòu)造函數(shù),然后對(duì)象成員的構(gòu)造函數(shù),最后才是派生類(lèi)的構(gòu)造函數(shù)
XX()
{
cout<<"XX() 無(wú)參構(gòu)造函數(shù)被調(diào)用!"<<endl;
}
//對(duì)基類(lèi)的函數(shù)名f,進(jìn)行了重定義。則會(huì)隱藏基類(lèi)中的其他f函數(shù)
//redefine
int f() const
{
cout<<" 調(diào)用 XX f()函數(shù)"<<endl;
return 1;
}
//重寫(xiě)基類(lèi)的虛函數(shù)
//redefine override
double f(int dataA,int dataB)
{
cout<<"調(diào)用 XX f(int dataA,int dataB)函數(shù)"<<endl;
return (dataA+dataB)/2.0;
}
};
分析:基類(lèi)class Test中定義了名為f的3個(gè)重載函數(shù),其中最后一個(gè)是虛函數(shù)
派生類(lèi)class XX中對(duì)f進(jìn)行了重定義,所以會(huì)隱藏基類(lèi)中名為f的版本。其中派生類(lèi)的double f(int dataA,int dataB)屬于對(duì)虛函數(shù)的重寫(xiě)
測(cè)試---主程序
int main()
{
//-----test 1------------------------
cout<<"-------test 1------------"<<endl;
//Base class
Test aaTest;
aaTest.f();
aaTest.f(12);
aaTest.f(10,20);
//derived class
XX d;
d.f();
// d.f(2); //error C2661: 'f' : no overloaded function takes 1 parameters
d.f(10,20);
//--------test 2----------------------------------
cout<<"-------test 2------------"<<endl;
Test b = d;
b.f();
b.f(10,20);//調(diào)用的是基類(lèi)的函數(shù),不發(fā)生多態(tài)
//--------test 3----------------------------------------
cout<<"-------test 3------------"<<endl;
Test &bR = d;//引用
b.f();//f()不是虛函數(shù),調(diào)用基類(lèi)的函數(shù)
bR.f(10,20);//調(diào)用的是派生類(lèi)的函數(shù),發(fā)生多態(tài)
//--------test 4--------------------------------------
cout<<"-------test 4------------"<<endl;
Test* pB = &d;
b.f();
pB->f(10,20);//調(diào)用的是派生類(lèi)的函數(shù),發(fā)生多態(tài)
return 1;
}
分析:(1)test 1中進(jìn)行了重載測(cè)試,根據(jù)傳遞參數(shù)的不一樣,調(diào)用不同的函數(shù) (早綁定,與多態(tài)無(wú)關(guān))
(2)test 2中Test b = d;定義了一個(gè)基類(lèi)對(duì)象,用派生類(lèi)對(duì)象來(lái)進(jìn)行初始化。這會(huì)調(diào)用基類(lèi)的拷貝構(gòu)造函數(shù),生成基類(lèi)的對(duì)象b,基類(lèi)的拷貝構(gòu)造函數(shù)初始化b的VPTR,指向b的VTABLE。因此所有的函數(shù)調(diào)用都只發(fā)生在基類(lèi),不會(huì)產(chǎn)生多態(tài)。
這是一個(gè)對(duì)象切片過(guò)程(參見(jiàn)《C++編程思想.第二版》P370),對(duì)象切片是當(dāng)它拷貝到一個(gè)新的對(duì)象時(shí),會(huì)去掉原來(lái)對(duì)象的一部分,而不是像使用指針或引用那樣簡(jiǎn)單地改變地址的內(nèi)容。
(3)test 3和test 4中,定義的基類(lèi)指針和引用,故會(huì)發(fā)生多態(tài)。
三、晚綁定原理:虛函數(shù)表
編譯器會(huì)對(duì)每一個(gè)包含虛函數(shù)的類(lèi)(或者從包含虛函數(shù)的基類(lèi)派生的類(lèi))創(chuàng)建一個(gè)表(VTABLE),里面存放特定類(lèi)的虛函數(shù)的地址。然后編譯器秘密地放置一指針vpointer(VPTR),指向這個(gè)對(duì)象的vtable。當(dāng)通過(guò)基類(lèi)指針做虛函數(shù)調(diào)用時(shí)(即多態(tài)調(diào)用時(shí)),編譯器靜態(tài)地插入能取得這個(gè)VPTR并在VTABLE表中查找函數(shù)地址的代碼,這樣就能調(diào)用正確的函數(shù)并引起晚綁定的發(fā)生。