class Base
{
public:
Base(int data = 0)
:b(data)
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
void B()
{
cout << "Base::B()" << endl;
}
void B(int b)
{
cout << "Base::B(int)" << endl;
}
//B()與B(int b)構成了函數重載
//因為上面兩個函數是在同一作用域中
int b;
};
class Derive :public Base
{
public:
Derive()
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
void B(int a, int b)
{
cout << "Derive::B(int,int)" << endl;
}
//不會與Base類中的兩個B名的函數構成重載
//因為作用域不同
};
下面這個圖僅僅代表函數之間的關系,不代表內存布局!
那麼上面的原則中提到:
virtual關鍵字在函數重載中可有可無
那麼我們看一下加不加virtual對函數重載的影響。
(1).不加virtual
//定義一個測試函數
void Test()
{
Base b;
b.B();
b.B(1);
}
//main函數調用測試函數
運行結果為:
(2).加virtual
a.一個函數加virtual
class Base
{
public:
Base(int data = 0)
:b(data)
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
void B()
{
cout << "Base::B()" << endl;
}
virtual void B(int b)
{
cout << "Base::B(int)" << endl;
}
//B()與B(int b)構成了函數重載
//因為上面兩個函數是在同一作用域中
int b;
};
運行結果為:
我們對代碼進行一下反匯編查看,
可以看到,我們Base b中b一共有八個字節,前四個字節為指向虛表的指針,保存的是虛表的地址,後四個字節是Base類中int b的值,關於虛表的問題可以去我的上一篇博文學習查看 C++多態篇1一靜態聯編,動態聯編、虛函數與虛函數表vtable。
看過我上一篇博文後,或者對虛表有一定了解後,我們可以參照匯編代碼看,我們可以看到在匯編代碼中,調用重載函數是根據地址不同調用的,調用B(1)時,是進入虛表中調用的,但是不影響函數重載。
有的人可能要問,那麼不加virtual的函數編譯器在哪尋找呢?
實際上,編譯器將類的對象存儲時是按下圖這樣存儲的
成員函數是單獨存儲的,所以編譯器在存儲成員函數那尋找函數即可
b.兩個函數都加virtual
class Base
{
public:
Base(int data = 0)
:b(data)
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
virtual void B()
{
cout << "Base::B()" << endl;
}
virtual void B(int b)
{
cout << "Base::B(int)" << endl;
}
//B()與B(int b)構成了函數重載
//因為上面兩個函數是在同一作用域中
int b;
};
運行結果依然是: 我們在上面的代碼中,分別在基類和派生類中定義了同名同參數的函數Test(),看一下運行結果,看會調用基類的函數還是派生類的函數: 從代碼可以看出在基類定義了三個虛函數,根據我們以前所說的知識,我們可以猜測基類會生成一個虛函數表,那麼派生類中我們定義了兩個同名同參數的函數,為了讓函數覆蓋的現象更加明顯,我特意沒有將Test3()定義,那麼我們現在看一下運行結果: 運行結果為: 運行結果還是: 我們在基類中定義了Test()函數,在派生類中定義了Test(int a)函數,這就是同名不同參數情況。 我們可以看出,編譯器報錯:Test函數不能為0參數。 運行成功,結果為: 編譯運行依然報錯: 那麼將main函數改變一下: 運行成功,結果為:
我們進行反匯編和在內存中查看可以得到:
我們可以看到,因為B名的函數均為虛函數,所以均在虛表中存儲。
當編譯器調用時,就在虛表中查找調用。
c.多個函數加virtual<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjxiciAvPg0K0vLOqtTauq/K/dbY1NjW0KOs1Nqyu82swODT8tbQyseyu7m5s8m6r8r91tjU2LXEoaPL+dLUyc/D5s7Sw8e2vNa7t9bO9sHL1Nq7+cDg1tC1xNbY1NijrLKix9K2vNLRwb249tbY1Ni6r8r91/fOqsD919OjrLWryse24Lj2uq/K/bm5s8nW2NTY0rLKx7/J0tS1xKOstuC49rqvyv2803ZpcnR1YWy1xMfpv/a1yM2s09rBvbj2uq/K/ba8vNN2aXJ0dWFstcTH6b/2o6y2vLvhvavQ6bqvyv2808jr0Om6r8r9se3W0KOs1Nq199PDyrG9+Mjr0Om6r8r9se3W0L340NC199PDtcShozwvcD4NCjxwPs/W1Nq6r8r91tjU2NOmuMO+zcO709DOysziwcuwyX48YnIgLz4NCjxzdHJvbmc+tv6horqvyv24srjHPC9zdHJvbmc+PGJyIC8+DQrKssO0yse6r8r9uLK4x8TYo788YnIgLz4NCjxzdHJvbmc+uLK4x8rH1rjFycn6wOC6r8r9uLK4x7v5wOC6r8r9o6zM2NX3yscgPC9zdHJvbmc+PGJyIC8+DQqjqDGjqbK7zay1xLe2zqejqLfWsfDOu9PaxcnJ+sDg0+u7+cDgo6mjuzxiciAvPg0Ko6gyo6m6r8r9w/vX1s/gzayjuzxiciAvPg0Ko6gzo6myzsr9z+DNrKO7PGJyIC8+DQqjqDSjqbv5wOC6r8r9sdjQ69PQdmlydHVhbCC52Lz819ahozxiciAvPg0KtbHFycn6wOC21M/ztffTw9fTwODW0LjDzazD+7qvyv3Ksbvh19S2r7X308PX08Dg1tC1xLiyuMew5rG+o6y2+LK7yse4uMDg1tC1xLG7uLK4x7qvyv2w5rG+o6zV4tbWu/rWxr7NvdDX9riyuMehozxiciAvPg0Kuq/K/biyuMfT687Sw8fJz8Pmy7W1xLqvyv3W2NTY09DKssO0x/ix8MTYo788YnIgLz4NCsrXz8ijrLqvyv3W2NTY0qrH89TazazSu7j21/fTw9Pyo6y2+Lqvyv24srjH0OjSqtTasrvNrLe2zqfE2qGjPGJyIC8+DQrIu7rzvs3Kx7qvyv3W2NTY0qrH87LOyv2yu8/gzayjrLWryse6r8r9uLK4x9Kqx/Oyzsr9sdjQ68/gzayhozxiciAvPg0K1+6689K7teO+zcrHuq/K/dbY1NjW0LzTsru803ZpcnR1YWy2vL/J0tSjrLWrysfU2rqvyv24srjH1tC7+cDguq/K/dbQsdjQ69KqvNN2aXJ0dWFsudi8/NfWoaM8YnIgLz4NCr6tuf3Jz8PmtcS31s72ztLDx9aqtcDBy6OsztLDx9Tau/nA4LrNxcnJ+sDg1tC31rHwtqjS5cP719bP4M2so6yyzsr9srvNrLXEuq/K/aOs1Nq688PmtffTw7XEyrG68qOsseDS68b3zt63qL2ry/y0psDtzqq6r8r91tjU2KGjPGJyIC8+DQrEx8O0uq/K/biyuMfT1srHyrLDtMfpv/bE2KGjPGJyIC8+DQrG5Mq1uq/K/biyuMe31s6qwb3W1sfpv/ajujxiciAvPg0KPHN0cm9uZz4xLrbUz/O199PDuq/K/bXEx+m/9jwvc3Ryb25nPjxiciAvPg0KxcnJ+sDgttTP87X308O1xMrHxcnJ+sDgtcS4srjHuq/K/TxiciAvPg0Ku/nA4LXEttTP87X308O7+cDgtcS6r8r9PGJyIC8+DQrPwsPmv7S0+sLro7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">
class Base
{
public:
Base(int data = 1)
:b(data)
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
virtual void Test()
{
cout << "Base::Test()" << endl;
}
int b;
};
class Derive :public Base
{
public:
Derive(int data = 2)
:d(data)
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
void Test()
{
cout << "Derive::Test()" << endl;
}
int d;
};
int main()
{
Derive d;
d.Test();
return 0;
}
因為我在基類和派生類的構造函數中都輸出了語句,而且是打斷點調試的,所以沒有調用析構函數。
運行結果可以表明:
這裡的Test()函數發生了函數覆蓋。
那我們進入內存中看一下:
PS:因為是我自己截圖畫圖的,不知道為什麼傳上來就壓縮了,如果大家看不清,可以ctrl+向上鍵放大看一下。
這張圖能夠更清楚地看到,在派生類的虛表中,只有一個函數,就是Derive::Test(),沒有從Base類繼承下來的Test(),所以能夠更清楚的看到發生了函數的覆蓋。
如果這樣你還沒太理解,那麼我就再多加幾個函數。
看下面的代碼:
class Base
{
public:
Base(int data = 1)
:b(data)
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
virtual void Test1()
{
cout << "Base::Test1()" << endl;
}
virtual void Test2()
{
cout << "Base::Test2()" << endl;
}
virtual void Test3()
{
cout << "Base::Test3()" << endl;
}
int b;
};
class Derive :public Base
{
public:
Derive(int data = 2)
:d(data)
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
void Test1()
{
cout << "Derive::Test1()" << endl;
}
void Test2()
{
cout << "Derive::Test2()" << endl;
}
int d;
};
int main()
{
Base b;
b.Test1();
b.Test2();
b.Test3();
Derive d;
d.Test1();
d.Test2();
d.Test3();
return 0;
}
由結果可知,基類對象調用的是基類的函數。派生類對象調用的是什麼呢?
我們進入內存中查看一下:
由上圖我們可以看到,我們在派生類中定義了的函數,在派生類虛函數表中將基類函數覆蓋了,即派生類虛函數表中綠色的部分,而派生類沒有定義的函數,即Test3(),基類和派生類的函數地址完全相同。
這就更清楚的看出了,派生類中定義了同名同參數的函數後,發生了函數覆蓋。
2.指針或引用調用函數的情況
指向派生類的基類指針調用的也是派生類的覆蓋函數
還是上面的例子,我們將調用者換一下:
class Base
{
public:
Base(int data = 1)
:b(data)
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
virtual void Test()
{
cout << "Base::Test()" << endl;
}
int b;
};
class Derive :public Base
{
public:
Derive(int data = 2)
:d(data)
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
void Test()
{
cout << "Derive::Test()" << endl;
}
int d;
};
int main()
{
Base *pb;
Derive d;
pb = &d;
pb->Test();
return 0;
}
在內存布局為:
由內存布局可以看出,指針pb指向的虛表就是派生類對象d所擁有的虛表,所以當然調用的是派生類已經覆蓋了的函數。
所以說:
多態的本質:不是重載聲明而是覆蓋。
虛函數調用方式:通過基類指針或引用,執行時會根據指針指向的對象的類,決定調用哪個函數。
三、函數隱藏
經過上面的分析我們知道,在不同的類域定義不同參數的同名函數,是無法構成函數重載的。
那麼當我們這麼做的時候,會發生什麼呢。
實際上,這種情況叫做函數隱藏。
* 隱藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下*
(1)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)
(2)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
首先來看第一種情況。
1.同名同參數
那麼在上面的例子中我們試一下不加virtual關鍵字看看。
即將基類改為:
class Base
{
public:
Base(int data = 1)
:b(data)
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
void Test()
{
cout << "Base::Test()" << endl;
}
int b;
};
class Derive :public Base
{
public:
Derive(int data = 2)
:d(data)
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
void Test()
{
cout << "Derive::Test()" << endl;
}
int d;
};
int main()
{
Derive d;
d.Test();
return 0;
}
這就是發生了函數的隱藏
再看下第二種情況
2.同名不同參數
(1)基類函數不加virtual
class Base
{
public:
Base(int data = 1)
:b(data)
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
void Test()
{
cout << "Base::Test()" << endl;
}
int b;
};
class Derive :public Base
{
public:
Derive(int data = 2)
:d(data)
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
void Test(int a)
{
cout << "Derive::Test()" << endl;
}
int d;
};
int main()
{
Derive d;
d.Test();
return 0;
}
編譯運行一下:
編譯器報錯:
Error 1 error C2660: 'Derive::Test' : function does not take 0 arguments e:\demo\blog\project1\project1\source.cpp 105 1 Project1
如果我們將main函數改變一下:
int main()
{
Derive d;
d.Test(1);
return 0;
}
這就是發生了函數隱藏~
(2)基類函數加virtual
class Base
{
public:
Base(int data = 1)
:b(data)
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
virtual void Test()
{
cout << "Base::Test()" << endl;
}
int b;
};
class Derive :public Base
{
public:
Derive(int data = 2)
:d(data)
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
void Test(int a)
{
cout << "Derive::Test()" << endl;
}
int d;
};
int main()
{
Derive d;
d.Test();
return 0;
}
Error 1 error C2660: 'Derive::Test' : function does not take 0 arguments e:\demo\blog\project1\project1\source.cpp 105 1 Project1
int main()
{
Derive d;
d.Test(1);
return 0;
}
這也是發生了函數隱藏。
現在函數隱藏應該沒有問題了吧~
總結一下前面的:
1.函數重載必須是在同一作用域的,在繼承與多態這裡,在基類與派生類之間是不能進行函數重載。
2.函數覆蓋是多態的本質,在基類中的虛函數,在派生類定義一個同名同參數的函數,就可以用派生類新定義的函數對基類函數進行覆蓋。
3.函數隱藏是發生在基類和派生類之間的,當函數同名但是不同參數的時候,不論是不是虛函數,都會發生函數隱藏。
這篇文章就暫且寫到這裡,本來是想對虛函數表進行深度剖析的,但是寫那一篇的時候發現,會用到這裡的知識,害怕初學者這裡還不清楚,所以現將這些問題整理一下,再更新下一篇文章。
如有問題歡迎批評指正,人無完人,文無完文,希望大家共同進步!