前言
?多態(tài),顧名思義,就是一件事物具備不同的形態(tài)
,是繼承之后,面向?qū)ο蟮牡谌筇匦?/code>,可以這樣說:
有了繼承才有了類的多態(tài),而類的多態(tài)是為了更好的實現(xiàn)繼承。
?多態(tài)的列車即將起航,不知你準(zhǔn)備好了嗎?
一、概念
?繼承與多態(tài)相輔相成
。
舉個例子:
?我們都是人(具備人都有的信息——性別,年齡等),在社會上我們又會具備不同的身份——老師,學(xué)生,工人等,那這時放假回家,要去買火車票,那這時如果你是學(xué)生火車票五折
。如果是老師火車票七五折
。
? 老師和學(xué)生都是人,人又具有不同的身份,而不同身份面臨的同一件事情表現(xiàn)的具體形態(tài)不同。
因此:老師和學(xué)生繼承人的信息,從而使不同的事物(具有相同特征)對同一件事表現(xiàn)出不同的形態(tài),這樣就是多態(tài)。
補充:
網(wǎng)友比較形象的解釋:
英雄聯(lián)盟里的每個英雄都是一個對象,其父類(基類)有個方法:按Q、W、E、R會產(chǎn)生傷害、消耗藍量、并在屏幕上顯示攻擊效果,每個對象(英雄)繼承這個方法,并具有具體的屬性,每個對象的傷害量不同、消耗藍量不同,產(chǎn)生的效果不同,而這個方法具有多態(tài),在編寫代碼和編譯的時候并不確定,而在運行的時候,依據(jù)你選則了哪個英雄(對象),在調(diào)用父類的這個方法的時候,會依據(jù)具體對象執(zhí)行能產(chǎn)生不同狀態(tài)的方法,也就是說這個父類的方法具有了多種狀態(tài),想要其產(chǎn)生新的狀態(tài),只需基于父類再編寫新的對象
?那語法層面上是如何實現(xiàn)多態(tài)呢?
1.分類
?多態(tài),我們其實已經(jīng)接觸過一種,叫函數(shù)重載
,根據(jù)傳參不同而調(diào)用同一函數(shù)的不同形態(tài)。這叫做靜態(tài)的多態(tài),也叫靜態(tài)綁定
,而我們今天講的主要是在繼承之后延伸出的類的多態(tài),叫動態(tài)的多態(tài),也叫動態(tài)綁定
。
這里根據(jù)上面的例子,列出一段代碼:
#include<iostream>
using namespace std;
class Person
{
public:
virtual void BuyTickets()
{
cout << "全價" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTickets()
{
cout << "學(xué)生->半價" << endl;
}
};
class Teacher : public Person
{
public:
virtual void BuyTickets()
{
cout << "老師->七五折" << endl;
}
};
void BuyTicket(Person & per)
{
per.BuyTickets();
}
int main()
{
Student stu;
Teacher tea;
BuyTicket(stu);
BuyTicket(tea);
return 0;
}
運行結(jié)果如下:
初學(xué)者看懂個大概即可,語法和原理下面會細(xì)講。
2.實現(xiàn)條件
主要實現(xiàn)方法有兩個。
①重寫虛函數(shù)
- 所謂虛函數(shù),就是在函數(shù)的前面加上
virtual
。
那重點就在于重寫,也叫覆蓋。
如何才能構(gòu)成重寫呢?或者什么叫做重寫呢?
- 首先父類至少得有虛函數(shù)。
- 一般來說,子類虛函數(shù)得跟父類的虛函數(shù)的函數(shù)名,參數(shù)類型,返回類型相同,簡稱三同。
細(xì)節(jié):
- 子類的函數(shù)前可不加virtual。
-
返回類型可以不同,但是必須是父子類的指針或者引用,且父類只能是父類的指針或者引用,子類必須是子類的指針或者引用。 ——
協(xié)變
- 重寫的是實現(xiàn)。
?到這重寫的條件就講清了,至于什么叫重寫,其實很簡單就是:在達成重寫的條件下,子類的虛函數(shù)替換掉父類的虛函數(shù),從而達成用指向子類的父類指針,在調(diào)用此虛函數(shù)時,會調(diào)用子類的虛函數(shù),而不是父類的虛函數(shù)。
列一段代碼,看結(jié)果便可明了。
#include<iostream>
using namespace std;
class Person
{
public:
//其它類的引用和指針也行,但必須是父是父的,子是子的!
virtual Person* BuyTickets(int val1 = 1)
{
cout << "全價" << endl;
cout << val1 << endl;
return nullptr;
}
};
class Student : public Person
{
public:
virtual Student* BuyTickets(int val2 = 0)
{
cout << "學(xué)生->半價" << endl;
cout << val2 << endl;
return nullptr;
}
};
class Teacher : public Person
{
public:
virtual Teacher* BuyTickets(int val3 = - 1)
{
cout << "老師->七五折" << endl;
cout << val3 << endl;
return nullptr;
}
};
void BuyTicket(Person & per)
{
per.BuyTickets();
}
int main()
{
Student stu;
Teacher tea;
BuyTicket(stu);
BuyTicket(tea);
return 0;
}
運行結(jié)果如下:
- 解釋細(xì)節(jié)3:殼還是套的父類,僅僅改變了作用域和實現(xiàn), 如果便于理解,你也可以認(rèn)為參數(shù)名也改了——但實際上
底層用的是地址和寄存器,根本不關(guān)心參數(shù)名
,這也是細(xì)節(jié)1的原因。
1.1總結(jié)三重
1.2 final與override
- override——對虛函數(shù)的重寫進行檢查。
class Student : public Person
{
public:
Student* BuyTickets (int val2 = 0) override
{
cout << "學(xué)生->半價" << endl;
cout << val2 << endl;
return nullptr;
}
//void fun1() override
//{
//}
//
//此注釋代碼不符合重寫條件,故報錯。
};
- final
- 禁止派生類重寫此虛函數(shù)
class Person
{
public:
virtual void BuyTickets()final
{
cout << "全價" << endl;
}
};
class Student : public Person
{
public:
//因為此函數(shù)構(gòu)成重寫,派生類會重寫虛函數(shù),因此會報錯。
void BuyTickets ()
{
cout << "學(xué)生->半價" << endl;
}
};
- 禁止此類被繼承(語法層面) —— C++11
class A final
{};
//因為B繼承A,所以會報錯。
class B : public A
{};
C++98采用構(gòu)造函數(shù)/析構(gòu)函數(shù)私有來進行實現(xiàn)不可被繼承(應(yīng)用層面)。
- 構(gòu)造私有
class A
{
public:
static A* CreatObj()
{
return new A;
}
private:
A()
{}
};
class B : public A
{
//原理為父類的私有成員在派生類中不可見。
};
int main()
{
A* a = A::CreatObj();
//B b;報錯
return 0;
}
- 析構(gòu)私有
class A
{
public:
void Destory()
{
A::~A();
}
private:
~A()
{}
};
class B : public A
{
public:
//原理為父類的私有成員在派生類中不可見。
};
int main()
{
A* ptra = new A;
ptra->Destory();
operator delete (ptra);
//B b;
//報錯
return 0;
}
②父類的指針或者引用
為啥必須是父類的指針和引用呢?
從概念上理解,是人具有多種形態(tài),而不是老師具有多種形態(tài),因為人是比較抽象的,賦予了某種身份才具象化。
再換一個例子,是植物具有多種形態(tài),還是玫瑰花具有多種形態(tài)?其原因還是一樣的,植物沒有賦予固定的形態(tài),是比較抽象的,而給植物賦予玫瑰花的身份是具象的。
- 所以父類這種比較抽象的狀態(tài)是符合多態(tài)的。 當(dāng)然從C++語法上來說父類也可以是具體的。 但都是
為了改變父類的指向從而調(diào)用父類的虛函數(shù)
。
補充:
子類也可以有多種形態(tài),這是把子類當(dāng)做父類來看的,就比如動物里面有貓,而貓分為很多種,比如波斯貓,布偶貓等。
細(xì)節(jié): 指定作用域,可破壞多態(tài)的條件:
void BuyTicket(Person & per)
{
per.Person::BuyTickets();
}
-
為啥不能是子類的指針或者引用?
-
為啥不是是父類對象?(涉及原理之后再講)
2.1普通調(diào)用VS多態(tài)調(diào)用
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
int _a = 0;
};
class B
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
};
class C : public A, public B
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
};
int main()
{
C c;
//普通調(diào)用
c.fun1();
//多態(tài)調(diào)用
B* b = &c;
b->fun1();
//看匯編代碼之后,想一下為什么,不構(gòu)成多態(tài)也去虛函數(shù)里面找,再進行調(diào)用。
C* ptrc = &c;
return 0;
}
匯編圖解:
3.抽象類
3.1. 純虛函數(shù)
- 在虛函數(shù)后面加上 “= 0” 即為純虛函數(shù),切記語法規(guī)定只能是0。
class A
{
public:
virtual void fun1() = 0;
};
純虛函數(shù)所在類是不能實例化的。
補充:空函數(shù)——實現(xiàn)啥也沒有
如:void func() {}
class B : public A
{}
這里B繼承了A,純虛函數(shù)也被繼承了,因此B也無法進行實例化。
像這樣,有純虛函數(shù)的類,就是抽象類。
那如何使用呢?很簡單子類將純虛函數(shù)進行重寫,不就能使用了么。
- 因此抽象類,會強制子類重寫虛函數(shù)(應(yīng)用)。
class B : public A
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
};
B進行重寫后,就不含純虛函數(shù),也就不是抽象類了。
如果你執(zhí)意要調(diào)用,也是可以的,不過會報錯:
int main()
{
B b;
A* a = &b;
a->fun1();
return 0;
}
3.2. 接口繼承和實現(xiàn)繼承
- 普通的繼承,直接對成員函數(shù)進行復(fù)用,俗稱接口繼承。
- 多態(tài)的繼承,對虛函數(shù)進行重寫,重寫的是實現(xiàn)。俗稱實現(xiàn)繼承。
二、原理及使用
1.虛函數(shù)表 —— 虛表
引入:
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1()
{}
private:
char _a = 1;
};
int main()
{
A a;
cout << sizeof(A) << endl;
return 0;
}
運行結(jié)果:
為啥不是4(8字節(jié)對齊)呢?難道多了什么嗎?
查看監(jiān)視窗口:
調(diào)用構(gòu)造函數(shù)前~
調(diào)用構(gòu)造函數(shù)后~
- 首先在構(gòu)造函數(shù)調(diào)用前,比我們預(yù)想多了一個指針,是void** 類型的,并且沒有被初始化。這就足以證明,8字節(jié)是咋來的了。
- 在調(diào)用構(gòu)造函數(shù)后,可以看到_vftptr的指針的具體信息,并被初始化了,大概是_vftptr指針指向的是一個數(shù)組,數(shù)組有兩個元素,元素所存的元素的類型為void (*) ()的虛函數(shù)指針,也就是一張存放函數(shù)指針的表。且最后一個位置存放的應(yīng)該是虛函數(shù)表的結(jié)束位置。
之后證明
。
那這張存放虛函數(shù)的表,我們稱之為虛函數(shù)表,簡稱虛表。
那虛表是用來干啥呢?當(dāng)然是肯定是用來實現(xiàn)多態(tài)的了,再說細(xì)點就是為了實現(xiàn)重寫。
既然是這樣,那我們對以下代碼進行調(diào)試。
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1()
{}
virtual void fun2()
{}
private:
char _a = 1;
};
class B : public A
{
public:
virtual void fun1()
{}
};
int main()
{
A a;
B b;
return 0;
}
調(diào)試結(jié)果:
- 很顯然,不同類的虛表是不同的。根據(jù)虛表指針存的值即可看出 。
- 當(dāng)子類生成虛表時,把子類的虛表拷貝下來,然后對構(gòu)成重寫的虛函數(shù)進行覆寫,這里是對原來的位置進行覆蓋實現(xiàn)。而子類的不構(gòu)成重寫的虛函數(shù)則繼續(xù)在虛表的后面進行排列。
- 至于這里監(jiān)視窗口為啥看不到fun3,我的理解是監(jiān)視窗口是站在父類的角度進行查看的,當(dāng)然只能看到父類重寫的虛函數(shù)和父類沒有被重寫的虛函數(shù),如果能看到子類的虛函數(shù)不就怪了嗎?
至于如何驗證第三個位置是fun3,給出如下代碼。
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << " A :: fun1()" << endl;
}
virtual void fun2()
{
cout << " A :: fun2()" << endl;
}
private:
int _a = 1;
};
class B : public A
{
public:
virtual void fun1()
{
cout << " B :: fun1()" << endl;
}
virtual void fun3()
{
cout << " B :: fun3()" << endl;
}
int _b = 1;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
for (size_t i = 0; arr[i] != nullptr; i++)
{
printf("%p->", arr[i]);
FUN_PTR ptr = arr[i];
ptr();
}
}
int main()
{
B b;
int ptr = *(int*)(&b);
Print((FUN_PTR*)ptr);
return 0;
}
運行結(jié)果:
這是虛表的內(nèi)存地址(小端):
總結(jié)一下:
- 不同類的虛表指針的值是不同的。相同類的虛表指針的值是相同的。(不再證明,有興趣自己看監(jiān)視窗口)。
- 監(jiān)視窗口看不到fun3, 是因為站的視角為父類,而fun3是子類的。
- vs2019虛表的結(jié)束位置為 0
- 子類虛表先放父類(虛函數(shù)),再重寫,再放子類(從上往下)。
- 虛表指針隨著構(gòu)造函數(shù)的調(diào)用而初始化,且虛表指針在對象模型的第一個位置。
下面我們繼續(xù)討論遺留下來的問題:
- 為啥不是是父類對象?
class A
{
public:
virtual void fun1()
{}
};
class B : public A
{
public:
virtual void fun1()
{}
};
int main()
{
A a;
B b;
a = b;
//拷貝不拷貝虛表?
return 0;
}
我們只看賦值之后的監(jiān)視窗口:
- 可見,虛表是不會被拷貝過去的,因此,無法完成指向父類調(diào)子類的情況(多態(tài)),并且如果拷貝過去子類能調(diào)用父類的虛函數(shù),就亂套了!因此是不拷貝虛表的。
- 為啥不是是父類對象?
那虛表存在哪呢?給出如下代碼進行驗證。
#include<iostream>
class A
{
public:
virtual void fun1()
{}
};
int main()
{
A a;
//虛表的地址
void** ptr = (void**)(*(int*)&a);
//棧區(qū)的地址
int _a = 0;
//靜態(tài)區(qū)的地址
static int b = 0;
//常量區(qū)地址
const char* str = "abc";
//堆的地址
int* ptr1 = new int;
printf("虛表地址->%p\n", ptr);
printf("棧區(qū)地址->%p\n", &_a);
printf("堆區(qū)地址->%p\n", ptr1);
printf("靜態(tài)區(qū)地址->%p\n", &b);
printf("常量區(qū)地址->%p\n", str);
return 0;
}
運行結(jié)果:
- 可見虛表地址與常量區(qū)地址僅僅差8個字節(jié)。
- 因此:虛表至少在VS2019下是存在常量區(qū)的。
2.默認(rèn)成員函數(shù)
2.1構(gòu)造函數(shù)
- 語法上,不允許在構(gòu)造函數(shù)前加virtual。
那為什么呢?
利用之前得到的結(jié)論,虛表指針是在構(gòu)造函數(shù)調(diào)用時才被初始化的! 如果構(gòu)造函數(shù)是虛函數(shù),那虛表指針都沒有初始化,如何調(diào)用虛函數(shù)呢?典型的先有虛函數(shù)指針 還是 先調(diào)用構(gòu)造函數(shù)的問題。因此語法上禁掉了。
除此之外,構(gòu)造(包括拷貝構(gòu)造)函數(shù)里面調(diào)用成員函數(shù),不會從虛表進行調(diào)用,而是直接進行調(diào)用!
2.2析構(gòu)函數(shù)
- 語法上,允許在析構(gòu)函數(shù)前加virtual。
這是為啥呢?
舉一段錯誤代碼,一看便知:
#include<iostream>
using namespace std;
class A
{
public:
~A()
{
cout << "A::~A()" << endl;
}
int _a = 0;
};
class B : public A
{
public:
~B()
{
cout << "B::~B()" << endl;
}
int _b = 1;
};
int main()
{
B* b = new B;
A* a = b;
delete a;
return 0;
}
運行結(jié)果 :
- 很明顯的問題出來了,竟然沒有調(diào)用子類的析構(gòu)函數(shù)。這是由于向上轉(zhuǎn)換發(fā)生的切割現(xiàn)象。會導(dǎo)致內(nèi)存泄漏的問題。
如何解決?
- 析構(gòu)函數(shù)前加virtual,那假設(shè)上面的代碼加上virtual,那delete 子類指針會形成多態(tài),指向父類調(diào)用父類的析構(gòu)函數(shù),指向子類調(diào)用子類的析構(gòu)函數(shù)。
3. 多繼承
3.1普通的多繼承 + 虛函數(shù)
舉出如下代碼進行實驗:
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
virtual void fun2()
{
cout << "A::fun2()" << endl;
}
int _a = 0;
};
class B
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
virtual void fun2()
{
cout << "B::fun2()" << endl;
}
int _b = 0;
};
class C : public A ,public B
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
virtual void fun3()
{
cout << "C::fun3()" << endl;
}
int _c = 0;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
for (size_t i = 0; arr[i] != nullptr; i++)
{
printf("%p->", arr[i]);
FUN_PTR ptr = arr[i];
ptr();
}
cout << endl;
}
int main()
{
C c;
void** vftptr1 = (void **)(*(int*)(&c));
B* ptr = &c;
void** vftptr2 = (void **)(*(int*)(ptr));
Print((FUN_PTR*)vftptr1);
Print((FUN_PTR*)vftptr2);
return 0;
}
首先我們要看初始化之后的類C的對象模型
:
- 細(xì)節(jié):可以根據(jù)_vfptr的的信息看出有幾個元素。這里A的有四個,B的有三個。
根據(jù)此畫出對象模型:
運行結(jié)果: - 總結(jié):繼承的子類的不構(gòu)成重寫虛函數(shù)不會再生成虛表(除非沒有),
而是將虛函數(shù)放在第一個父類的虛表中
;多繼承父類不共享虛表,而是各用個的。
除此之外,這里還會衍生出一個問題:運行結(jié)果C::fun1()的地址竟然不同,這是為什么呢?
將上述代碼進行簡化:
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
int _a = 0;
};
class B
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
};
class C : public A, public B
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
};
int main()
{
C c;
B* b = &c;
A* a = &c;
a->fun1();
b->fun1();
return 0;
}
調(diào)用函數(shù)的反匯編流程圖:
- 可見,b對象在調(diào)用真正的fun1時拐了一個彎,然后再調(diào)用fun1。
為啥要這樣這樣做呢?
- 看關(guān)鍵動作——對ecx減8,ecx存放的是this指針,對this指針減8,到C對象的this指針位置,通過C的this指針再進行調(diào)用fun1。為啥要這樣做呢?因為fun1的作用域是C的類域,直接用B的this指針顯然不合理。
- 因此:調(diào)整B的this指針是為了類域的獨立性,那A對象咋不用呢?因為A的this指針本就可以當(dāng)做D的this指針進行使用,沒必要再偏。
3.2菱形繼承 + 虛函數(shù)
同樣給出一段代碼實驗:
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
int _a = 0;
};
class B : public A
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
virtual void fun2()
{
cout << "B::fun2()" << endl;
}
int _b = 0;
};
class C : public A
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
virtual void fun2()
{
cout << "C::fun2()" << endl;
}
int _c = 0;
};
class D : public B , public C
{
public:
virtual void fun2()
{
cout << "D::fun2()" << endl;
}
virtual void fun3()
{
cout << "D::fun3()" << endl;
}
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
for (size_t i = 0; arr[i] != nullptr; i++)
{
printf("%p->", arr[i]);
FUN_PTR ptr = arr[i];
ptr();
}
cout << endl;
}
int main()
{
D d;
void** vftptr1 = (void**)(*((int*)(&d)));
C* b = &d;
void** vftptr2 = (void**)(*((int*)(b)));
Print((FUN_PTR*)vftptr1);
Print((FUN_PTR*)vftptr2);
return 0;
}
初始化對象后的D類的監(jiān)視窗口:
運行結(jié)果:
據(jù)此畫出D類對象的對象模型:
- 其實跟多繼承差不多,也就多套了一層,這里解釋一下,B類的fun1重寫A類的fun1,D類的fun2重寫B(tài)類的fun2,D類的fun3放在第一個父類對象的虛表中。C類同理這里就不多說了。
3.3菱形虛擬繼承 + 虛函數(shù)
貼出一段代碼進行實驗:
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
int _a = 0;
};
class B : virtual public A
{
public:
virtual void fun2()
{
cout << "B::fun2()" << endl;
}
int _b = 0;
};
class C : virtual public A
{
public:
virtual void fun2()
{
cout << "C::fun2()" << endl;
}
int _c = 0;
};
class D : public B , public C
{
public:
virtual void fun3()
{
cout << "D::fun3()" << endl;
}
virtual void fun4()
{
cout << "D::fun4()" << endl;
}
int _d = 0;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
for (size_t i = 0; arr[i] != nullptr; i++)
{
printf("%p->", arr[i]);
FUN_PTR ptr = arr[i];
ptr();
}
cout << endl;
}
int main()
{
D d;
void** vftptr1 = (void**)(*((int*)(&d)));
C* b = &d;
void** vftptr2 = (void**)(*((int*)(b)));
A* a = &d;
void** vftptr3 = (void**)(*((int*)(a)));
Print((FUN_PTR*)vftptr1);
Print((FUN_PTR*)vftptr2);
Print((FUN_PTR*)vftptr3);
return 0;
}
d初始化后的監(jiān)視窗口:
運行結(jié)果:
結(jié)合內(nèi)存畫出對象模型:
- D單獨的虛函數(shù)放在第一張?zhí)摫碇?,如果除A的虛表外,沒有虛表可以放,那就放自己的虛表中。
- B,C的各自的虛函數(shù),分別存在兩張?zhí)摫碇小?/li>
- A,存放一張?zhí)摫怼?/li>
-
類對象第一個位置存放的是虛表指針,而不是虛基表指針。 因此虛基表指針指向的第一個位置變成了
ff ff ff fc
,轉(zhuǎn)換成int
也就是- 4
,這是虛表指針的地址相對類的this指針偏移量(this指針 - 虛表指針的地址)。
第二個位置存的是虛表指針的地址相對于A的偏移量
。第三個位置存的是0,個人理解:表示終止位置
。
此外,我們還需注意避免不同子類重寫基類的問題:
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1()
{}
};
class B : virtual public A
{
public:
virtual void fun1()
{}
};
class C : virtual public A
{
public:
virtual void fun1()
{}
};
class D : public B, public C
{
public:
virtual void fun2()
{}
};
編譯結(jié)果:
畫個圖理解一下:
- 問題:二義性,A不知該繼承誰的。
- 解決方法:交給孫子類決定。
代碼如下:
class A
{
public:
virtual void fun1()
{}
int _a = 0;
};
class B : virtual public A
{
public:
virtual void fun1()
{}
int _b = 0;
};
class C : virtual public A
{
public:
virtual void fun1()
{}
int _c = 0;
};
class D : public B, public C
{
public:
virtual void fun1()
{}
virtual void fun2()
{}
int _d;
};
4.inline與static
4.1inline
補充知識:
1.內(nèi)聯(lián)函數(shù),可以說不是函數(shù),是一段代碼。
2.類里面的成員函數(shù),默認(rèn)inline修飾。
3. inline修飾函數(shù),不一定是內(nèi)聯(lián)函數(shù),取決于函數(shù)的實現(xiàn)是否復(fù)雜,最終還是要編譯器決定的。但內(nèi)聯(lián)函數(shù)一定是被inline修飾的!文章來源:http://www.zghlxwxcb.cn/news/detail-618784.html
- 虛函數(shù)前是可以加inline的,但其不是內(nèi)聯(lián)函數(shù),原因是因為虛函數(shù)是需要被放在虛表中的。
4.2 static
- static是不能修飾虛函數(shù)的,其原因在于
虛函數(shù)是為了實現(xiàn)多態(tài)的
,而多態(tài)的條件是父類的指針或者引用,其本質(zhì)上都是傳了子類的this指針,但static修飾的函數(shù)是沒有this指針的
,無法實現(xiàn)多態(tài),因此不能用static修飾虛函數(shù)。
總結(jié)
?今天的分享就到這里了,如果覺得文章不錯,點個贊鼓勵一下吧!我們下篇文章再見
!文章來源地址http://www.zghlxwxcb.cn/news/detail-618784.html
到了這里,關(guān)于【C++進階之路】多態(tài)篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!