多態(tài)性:一個(gè)接口,多種方法.程序在運(yùn)行時(shí)才確定調(diào)用的函數(shù),是 oop 的核心概念.
- 多態(tài)性通過虛函數(shù)來(lái)實(shí)現(xiàn),子類可以重新定義父類(重寫:override).
重寫有兩種,一種是重寫虛函數(shù)(體現(xiàn)多態(tài)),另一種就是重寫成員函數(shù)(并沒有體現(xiàn))
和重寫相對(duì)的另一個(gè)概念是重載(overloading),指的是多個(gè)重名的函數(shù)他們的參數(shù)列表不同(個(gè)數(shù),類型),編譯器通過函數(shù)的調(diào)用參數(shù)列表來(lái)決定調(diào)用的
- 多態(tài)和非多態(tài)的本質(zhì)區(qū)別在于:早綁定和晚綁定.函數(shù)調(diào)用的地址是編譯期間就能確定的,那就是非多態(tài);需要在運(yùn)行的時(shí)候才確定,那就是多態(tài)
- 面向?qū)ο笕筇匦?br>
1. 封裝:模塊化代碼,實(shí)現(xiàn)代碼重用
2. 繼承擴(kuò)展已經(jīng)實(shí)現(xiàn)的代碼,實(shí)現(xiàn)代碼重用
3. 多態(tài): 實(shí)現(xiàn)接口重用,同一個(gè)接口可以自適應(yīng)到各自對(duì)象的實(shí)現(xiàn)方法上面去. - 實(shí)現(xiàn)多態(tài)的方法
1. 聲明基類指針,指向一個(gè)子類對(duì)象
2. 調(diào)用虛函數(shù)
3. 如果沒有多態(tài)性,則調(diào)用的函數(shù)將一直是基類的相應(yīng)函數(shù),即,函數(shù)調(diào)用的地址是固定的,不能實(shí)現(xiàn)一個(gè)接口,多種方法.
#include<iostream>
using namespace std;
class A
{
public:
void foo()
{
printf("1\n");
}
virtual void fun()
{
printf("2\n");
}
};
class B : public A
{
public:
void foo()
{
printf("3\n");
}
void fun()
{
printf("4\n");
}
};
int main(void)
{
A a;
B b;
A *p = &a;
p->foo();
p->fun();
p = &b;
p->foo();
p->fun();
return 0;
}
輸出為
1 2 1 4
對(duì)于輸出1 2是沒有問題的,在第三和第四個(gè)輸出的時(shí)候,因?yàn)榛愔羔樦赶蛄俗宇?而foo()函數(shù)沒有被虛擬化,所以,這是一個(gè)早綁定,只能調(diào)用基類的同名函數(shù),fun()是一個(gè)基類中的虛函數(shù),所以可以被晚綁定,調(diào)用子類中的函數(shù),從而實(shí)現(xiàn)了一個(gè)借口,多個(gè)函數(shù)的多態(tài)性.
- 小結(jié)
1. 如果有 virtual 才有多態(tài)(覆蓋基類函數(shù))
2. 沒有多態(tài),按照原類型調(diào)用(隱藏)
純虛函數(shù):在基類中定義的虛函數(shù),沒有定義,派生類需要定義自己的實(shí)現(xiàn)方法.
virtual void function() = 0;
派生類中必須進(jìn)行重寫以實(shí)現(xiàn)多態(tài)性. 含有純虛函數(shù)的類成為抽象類,不能生成對(duì)象.
- 編譯多態(tài)性: 通過重載實(shí)現(xiàn)
- 運(yùn)行多態(tài)性: 通過虛函數(shù)實(shí)現(xiàn)(覆蓋)
虛函數(shù)表(vtable): 虛函數(shù)是通過虛函數(shù)表來(lái)實(shí)現(xiàn)的,這個(gè)表主要是一個(gè)類虛函數(shù)的地址表,這張表解決了繼承和多態(tài)的問題,保證了真實(shí)反映和使用實(shí)際的函數(shù).這個(gè)表在一個(gè)對(duì)象實(shí)例的最前面,我們可以通過變量虛函數(shù)表的函數(shù)指針,調(diào)用相應(yīng)的函數(shù).
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
Base b;
這個(gè)對(duì)象實(shí)例b的結(jié)構(gòu)如下:

-
一對(duì)一繼承(子類沒有覆蓋重寫虛函數(shù),這樣在實(shí)際中沒有意義,僅為對(duì)比)
如果子類也有自己的虛函數(shù),并繼承父類的虛函數(shù)表,則其虛函數(shù)表的結(jié)構(gòu)為:
圖片.png
1)虛函數(shù)按照聲明順序放在表中
2)父類的虛函數(shù)放在子類的前面 -
一對(duì)一繼承(有虛函數(shù)被覆蓋)
父子兩個(gè)對(duì)象的表分別如下:
圖片.png
則最后子類的虛函數(shù)表將為:

1)覆蓋的虛函數(shù),父類位置被子類頂替
2)其余順序不變
因此,如果有
Base *b = new Derive();
b->f();
該父類對(duì)象指向的地址是子類的地址,對(duì)象b將調(diào)用覆蓋后的 Derive::f().
這就是多態(tài)實(shí)現(xiàn)的原理.
-
多重繼承(無(wú)覆蓋)
如果子類對(duì)父類的虛函數(shù)沒有覆蓋:
圖片.png
那么子類實(shí)例中的虛函數(shù)表是這樣子的:
圖片.png
1)每個(gè)父類有自己的虛表
2)子類的虛函數(shù)在第一個(gè)父類之后
3)父類順序是按照聲明順序來(lái)的
當(dāng)每次設(shè)計(jì)到這些虛函數(shù)的時(shí)候,需要對(duì)應(yīng)到相應(yīng)的虛函數(shù)表(根據(jù)基類的定義),比如:
Base2 *ptr = new d();
ptr->f() //調(diào)用第二個(gè)表的第一個(gè)函數(shù)(Base2 的 f())
-
多重繼承(有虛函數(shù)覆蓋)
圖片.png
所有相應(yīng)的同名虛函數(shù)都要被覆蓋:
圖片.png
安全性
- 子類中沒有重載的虛函數(shù)雖然出現(xiàn)在第一個(gè)虛函數(shù)表中,但是相應(yīng)的父類指針并不能訪問這個(gè)函數(shù).程序?qū)?bào)錯(cuò).但是仍然可以通過地址訪問的方式調(diào)用.
- 對(duì)于父類中
private的方法,如果是虛函數(shù),并被子類通過共有繼承,那么這個(gè)函數(shù)將出現(xiàn)在子類的虛函數(shù)表中,并可以通過地址訪問的方式被調(diào)用,這是很危險(xiǎn)的.





