原創(chuàng):?神秘編程?神秘編程?今天
問題拋出:為什么要使用多態(tài)?如果子類定義了與父類中原型相同的函數(shù)會發(fā)生什么?
多態(tài):同一操作作用于不同的對象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果。在運(yùn)行時,可以通過指向基類的指針,來調(diào)用實(shí)現(xiàn)派生類中的方法。
那么C++是用什么實(shí)現(xiàn)多態(tài)的呢?
虛函數(shù),抽象類,覆蓋,模板(重載和多態(tài)無關(guān))。
簡單多態(tài)例子
#include<iostream>
using namespace std;
class Base
{
public:
Base(){}
virtual void foo()
{
cout<<"This is A."<<endl;
}
private:
int a;
int b;
};
class D: public Base
{
public:
D(){}
void foo()
{
cout<<"This is B."<<endl;
}
int x;
int y;
};
int main(int argc, char *argv[])
{
Base *a = new D();
a->foo();
if(a != NULL)
delete a;
return 0;
}
如果把virtual去掉,將顯示:
This is A.
前面的多態(tài)通過使用虛函數(shù)virtual void foo()來實(shí)現(xiàn)。
到這里相信大家對多態(tài)有一丁點(diǎn)感覺了。
多態(tài)成立的個條件:
1:必須要有繼承;
2:類中存在的成員函數(shù)是虛函數(shù)(virtual);
3:要有父類的指針或者引用指向子類對象;
4:子類一定要三同(返回值、函數(shù)名、參數(shù)列表);
5:二個類外;
(1)父類的成員函數(shù)返回值為 A*;子類的成員函數(shù)返回值為 B*,現(xiàn)在打破三同,還是覆蓋。
(2)父類的析構(gòu)函數(shù)一般都要加上(virtual),就可以利用多態(tài)性級聯(lián)的調(diào)用析構(gòu)函數(shù),此時
先調(diào)用子類的,在調(diào)用父類的;(避免發(fā)生內(nèi)存泄漏)。
內(nèi)存模型
定義一個子類對象時,該子類就會有相應(yīng)的內(nèi)存情況,此時就是內(nèi)存模型;
虛函數(shù)
虛函數(shù)是一個類的成員函數(shù),其定義格式如下:virtual 返回值類型 函數(shù)名(參數(shù)表);
關(guān)鍵字virtual指明該成員函數(shù)為虛函數(shù)。
class Test()
{
public:
virtual void foo()
{
cout<<"This is A."<<endl;
}
}
虛函數(shù)剖析
(1)、當(dāng)在類中出現(xiàn)一個(virtual),虛方法時,對象中第一個成員將是:_vptr;
此時只有一個成員,應(yīng)該為4B,但是其因?yàn)橛刑摵瘮?shù)的存在,內(nèi)部的第一個成員就一定為虛表指針;
指針32位下為4字節(jié),所以此時一共為8字節(jié);(不管內(nèi)部有多少個虛函數(shù),但是虛表指針只有一個);
(2)、虛函數(shù)將在繼承體系中,一直為虛(覆蓋時); 此時的virtual可以省略不寫;
(3)、多態(tài):就是對虛方法的重寫;
(4)、虛表:裝虛函數(shù)的表,其本質(zhì)是地址的覆蓋;
多態(tài)的原理
沒有覆蓋的虛表
如果子類有一個方法與父類的虛方法三同,此時覆蓋;
但是通過父類的指針/引用永遠(yuǎn)只能訪問父類對象的部分;
或取得虛表中的函數(shù)
Base b;
cout<<&b<<endl; ? //對象的地址
printf("%p\n", *(int *)(&b)); ?//虛表中虛表指針的值(指針4字節(jié)),所以轉(zhuǎn)換整形指針,也就是虛表指針的地址;
((Fun)*((int*)*(int*)(&b) + 0))(); ?//Fun是函數(shù)指針,將獲得虛表中的第一個函數(shù);
((Fun)*((int*)*(int*)(&b) + 1))(); ?//Fun是函數(shù)指針,將獲得虛表中的第二個函數(shù);
((Fun)*((int*)*(int*)(&b) + 2))(); ?//Fun是函數(shù)指針,將獲得虛表中的第三個函數(shù);
多繼承中虛表
父類均為虛函數(shù),子類中也有虛函數(shù),且沒有進(jìn)行覆蓋,則將子類的放到第一個虛表的最后,其余的父類虛表就不用放了;因?yàn)椋?/p>
就是放了,通過父類的指針/引用也訪問不了,浪費(fèi)內(nèi)存空間;要是有覆蓋的,則每個虛表都得畫出;其余情況類似分析就行。
純虛函數(shù)與抽象類
#include<iostream>
using namespace std;
class Test{
public:
? ?virtual void fun() = 0; ?//這種形式就是存虛函數(shù),賦值為0;
? ?virtual void fun1() = 0;
? ?virtual void fun2() = 0;
? ?virtual void fun3() = 0;
};
int main(void){
? ?return 0;
}
單繼承下的虛函數(shù)表
//單繼承下虛函數(shù)表:是如何組織的
class A{
public:
virtual void func(){
cout << "A::func" << endl;
}
virtual void funcA(){
cout << "A::funcA" << endl;
}
};
class B:public A{
public:
virtual void func(){
cout << "B::func" << endl;
}
virtual void funcB(){
cout << "B::funcB" << endl;
}
};
class C:public A{
public:
virtual void func(){
cout << "C::func" << endl;
}
virtual void funcC(){
cout << "C::funcC" << endl;
}
};
typedef void (*FUNC)();
int main()
{
A a;
B b;
C c;
cout << "A::虛表:" << endl;
((FUNC)(*(int *)(*(int*)(&a))))();
((FUNC)(*((int*)(*(int*)(&a)) + 1)))();
cout << "-------------------------------------" << endl;
cout << "B::虛表:" << endl;
((FUNC)(*(int *)(*(int*)(&b))))();
((FUNC)(*((int*)(*(int*)(&b)) + 1)))();
((FUNC)(*((int*)(*(int*)(&b)) + 2)))();
cout << "-------------------------------------" << endl;
cout << "C::虛表:" << endl;
((FUNC)(*(int *)(*(int*)(&c))))();
((FUNC)(*((int*)(*(int*)(&c)) + 1)))();
((FUNC)(*((int*)(*(int*)(&c)) + 2)))();
system("pause");
return 0;
}
單繼承情況下:(虛函數(shù)的存放情況----->存放地址)
1:先基類的虛函數(shù);
2:再派生類的虛函數(shù);
3:存在覆蓋的話,派生類的虛函數(shù)占據(jù)了覆蓋的基類的虛函數(shù)位置;
typedef void(*FUNC)();
class A{
public:
virtual void func(){
cout << "A::func" << endl;
}
virtual void funcA(){
cout << "A::funcA" << endl;
}
private:
int a;
};
class B{
public:
virtual void func(){
cout << "B::func" << endl;
}
virtual void funcB(){
cout << "B::funcB" << endl;
}
private:
int b;
};
class C :public A, public B{
public:
virtual void func(){
cout << "C::func" << endl;
}
virtual void funcC(){
cout << "C::funcC" << endl;
}
private:
int c;
};typedef void(*FUNC)();
class A{
public:
virtual void func(){
cout << "A::func" << endl;
}
virtual void funcA(){
cout << "A::funcA" << endl;
}
private:
int a;
};
class B{
public:
virtual void func(){
cout << "B::func" << endl;
}
virtual void funcB(){
cout << "B::funcB" << endl;
}
private:
int b;
};
class C :public A, public B{
public:
virtual void func(){
cout << "C::func" << endl;
}
virtual void funcC(){
cout << "C::funcC" << endl;
}
private:
int c;
};
多繼承條件下的虛函數(shù)表
//多繼承條件下的虛函數(shù)表
void test()
{
C c;
cout << "多繼承條件下的虛函數(shù)表:" << endl;
cout << "------------------------" << endl;
((FUNC)(*((int*)(*(int *)(&c)))))();
((FUNC)(*((int*)(*(int*)(&c)) + 1)))();
((FUNC)(*((int*)(*(int*)(&c)) + 2)))();
cout << "------------------------" << endl;
((FUNC)(*(int*)(*((int*)(&c) + 2))))();
((FUNC)(*((int*)(*((int*)(&c) + 2)) + 1)))();
}
多繼承下的虛函數(shù)表------->有幾個基類就有幾張?zhí)摵瘮?shù)表-----派生類的虛函數(shù)存放在聲明的基類的虛函數(shù)表中。
1:首先要聲明基類的虛函數(shù)表
該類的成員變量(int a);
2:然后再聲明基類虛的函數(shù)表
該類的成員變量(int b);
3:派生類中與基類造成覆蓋的-->分別占據(jù)相應(yīng)的位置-->C::func分別位于二個基類的虛函數(shù)形成覆蓋。
4:派生類中未覆蓋的,放在首先聲明的基類的虛函數(shù)后面----->C::funC。
多繼承條件下,基類指針指向派生類后,基類指針?biāo)茉L問的函數(shù)
//多繼承條件下,基類指針指向派生類后,基類指針?biāo)茉L問的函數(shù)
void test1()
{
C c;
A *pa = &c;
B *pb = &c;
C *pc = &c;
cout << "基類指針pa所能調(diào)用的函數(shù):" << endl;
pa->func();
pa->funcA();
//pa->funcB();error:提示類A沒有成員funcB、funcC ?-->受到類型的限制
//pa->funcC();error
cout << "基類指針pb所能調(diào)用的函數(shù):" << endl;
pb->func();
pb->funcB();
//pb->funcA();error
//pb->funcC();error
cout << "派生類指針pc所能調(diào)用的函數(shù):" << endl;
pc->func();
pc->funcA();
pc->funcB();
pc->funcC();
}
多繼承條件下,基類指針指向派生類對象后,基類指針之間強(qiáng)制類型轉(zhuǎn)化之后,所能訪問的函數(shù)
void test2()
{
C c;
A *pa = &c;
B *pb = &c;
C *pc = &c;
pa = reinterpret_cast<A *>(pb);
pa->func();
pa->funcA();
//pb = reinterpret_cast<B *>(pa);
//pb->func();
//pb->funcB();
//pa = reinterpret_cast<A *>(pc);
//pa->func();
//pa->funcA();
}
如果喜歡,歡迎關(guān)注微信公眾號:神秘編程? ?一起討論學(xué)習(xí)
專注Linux C/C++和算法知識學(xué)習(xí)分享總結(jié)
? ? ? ? ? ? ? ? ? ?歡迎關(guān)注交流共同進(jìn)步