C++ 之 多態(很是很是重要,重點在後面)

編譯環境:WIN10 VS2017 
這篇博客有點長,但都是滿滿的乾貨,必定要看到最後,那纔是重點。 
什麼是多態? 
顧名思義就是同一個事物在不一樣場景下的多種形態。 
 
下面會具體的詳細的介紹。html

靜態多態
咱們之前說過的函數重載就是一個簡單的靜態多態面試

int Add(int left, int right)
{
    return left + right;
}
double Add(double left, int right)
{
    return left + right;
}shell

int main()
{
    Add(10, 20);
    //Add(10.0, 20.0);  //這是一個問題代碼
    Add(10.0,20);  //正常代碼
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16函數

能夠看出來,靜態多態是編譯器在編譯期間完成的,編譯器會根據實參類型來選擇調用合適的函數,若是有合適的函數能夠調用就調,沒有的話就會發出警告或者報錯。。。比較簡單,不作多介紹。 this


動態多態
什麼是動態多態呢? 
動態多態: 顯然這和靜態多態是一組反義詞,它是在程序運行時根據基類的引用(指針)指向的對象來肯定本身具體該調用哪個類的虛函數。 
我在西安臨潼上學,我就以這邊的公交車舉個栗子啊:.net

class TakeBus
{
public:
    void TakeBusToSubway()
    {
        cout << "go to Subway--->please take bus of 318" << endl;
    }
    void TakeBusToStation()
    {
        cout << "go to Station--->pelase Take Bus of 306 or 915" << endl;
    }
};
//知道了去哪要作什麼車可不行,咱們還得知道有沒有這個車
class Bus
{
public:
    virtual void TakeBusToSomewhere(TakeBus& tb) = 0;  //???爲何要等於0
};指針

class Subway:public Bus
{
public:
    virtual void TakeBusToSomewhere(TakeBus& tb)
    {
        tb.TakeBusToSubway();
    }
};
class Station :public Bus
{
public:
    virtual void TakeBusToSomewhere(TakeBus& tb)
    {
        tb.TakeBusToStation();
    }
};htm

int main()
{
    TakeBus tb;
    Bus* b = NULL;
    //假設有十輛公交車,若是是奇數就是去地鐵口的,反之就是去火車站的
    for (int i = 1; i <= 10; ++i)
    {
        if ((rand() % i) & 1)
            b = new Subway;
        else
            b = new Station;
    }
    b->TakeBusToSomewhere(tb);
    delete b;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
這就是一個簡單的動態多態的例子,它是在程序運行時根據條件去選擇調用哪個函數。 
並且,從上面的例子咱們還發現了我在每個函數前都加了virtual這個虛擬關鍵字,想一想爲何?若是不加會不會構成多態呢? 
幹想不如上機實踐:對象

class Base
{
public:
    virtual void Funtest1(int i)
    {
        cout << "Base::Funtest1()" << endl;
    }
    void Funtest2(int i)
    {
        cout << "Base::Funtest2()" << endl;
    }
};
class Drived :public Base
{
    virtual void Funtest1(int i)
    {
        cout << "Drived::Fubtest1()" << endl;
    }
    virtual void Funtest2(int i)
    {
        cout << "Drived::Fubtest2()" << endl;
    }
    void Funtest2(int i)
    {
        cout << "Drived::Fubtest2()" << endl;
    }
};
void TestVirtual(Base& b)
{
    b.Funtest1(1);
    b.Funtest2(2);
}
int main()
{
    Base b;
    Drived d;
    TestVirtual(b);
    TestVirtual(d);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 
在調用FuncTest2的時候咱們看出來他並無給咱們調用派生類的函數,所以咱們能夠對動態多態的實現作個總結。blog

動態多態的條件: 
●基類中必須包含虛函數,而且派生類中必定要對基類中的虛函數進行重寫。 
●經過基類對象的指針或者引用調用虛函數。

重寫 : 
(a)基類中將被重寫的函數必須爲虛函數(上面的檢測用例已經證明過了) 
(b)基類和派生類中虛函數的原型必須保持一致(返回值類型,函數名稱以及參數列表),協變和析構函數(基類和派生類的析構函數是不同的)除外 
(c)訪問限定符能夠不一樣 
那麼問題又來了,什麼是協變? 
協變:基類(或者派生類)的虛函數返回基類(派生類)的指針(引用) 
總結一道面試題:那些函數不能定義爲虛函數? 
經檢驗下面的幾個函數都不能定義爲虛函數: 
1)友元函數,它不是類的成員函數 
2)全局函數 
3)靜態成員函數,它沒有this指針 
3)構造函數,拷貝構造函數,以及賦值運算符重載(能夠可是通常不建議做爲虛函數)

抽象類: 
在前面公交車的例子上我提了一個問題:

class Bus
{
public:
    virtual void TakeBusToSomewhere(TakeBus& tb) = 0;  //???爲何要等於0
};
1
2
3
4
5
在成員函數(必須爲虛函數)的形參列表後面寫上=0,則成員函數爲純虛函數。包含純虛函數的類叫作抽象類(也叫接口類),抽象類不能實例化出對象。純虛函數在派生類中從新定義之後,派生類才能實例化出對象。純虛函數是必定要被繼承的,不然它存在沒有任何意義。

多態調用原理
class Base
{
public:
    virtual void Funtest1(int i)
    {
        cout << "Base::Funtest1()" << endl;
    }
    virtual void Funtest2(int i)
    {
        cout << "Base::Funtest2()" << endl;
    }
    int _data;
};

int main()
{
    cout << sizeof(Base) << endl;
    Base b;
    b._data = 10;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
8?不知道你們有沒有問題,反正我是有疑惑了。之前在對象模型(https://blog.csdn.net/qq_39412582/article/details/80808754)時我提到過怎麼來求一個類的大小。按照那個方法,這裏應該是4纔對啊,爲何會是8呢?

經過觀察。咱們發現這個例子裏面和之前不同,類成員函數變成了虛函數,這是否是引發類大小變化的緣由呢? 
咱們假設就是這樣,而後看看內存裏是怎麼存儲的呢? 
 
能夠看到它在內存裏多了四個字節,那這四個字節的內容究竟是什麼呢?

是否是有點看不懂,咱們假設它是一個地址去看地址裏存的東西的時候發現它存的是兩個地址。 
我假設它是虛函數的地址,咱們來驗證一下:

typedef void (__stdcall *PVFT)();  //函數指針
int main()
{
    cout << sizeof(Base) << endl;
    Base b;
    b._data = 10;
    PVFT* pVFT = (PVFT*)(*((int*)&b));
    while (*pVFT)
    {
        (*pVFT)();
        pVFT+=1;
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
結果好像和咱們的猜測同樣,是一件開心的事。而後我給一張圖總結一下: 

在反彙編中咱們還能夠看到,若是含有虛函數的類中沒有定義構造函數,編譯器會自動合成一個構造函數 


對於派生類的東西我給個連接仔細看,人家總結的超級贊,我偷個懶就不寫了,老鐵們包容下啊。

派生類虛表:  1.先將基類的虛表中的內容拷貝一份  2.若是派生類對基類中的虛函數進行重寫,使用派生類的虛函數替換相同偏移量位置的基類虛函數  3.若是派生類中新增長本身的虛函數,按照其在派生類中的聲明次序,放在上述虛函數以後  https://coolshell.cn/articles/12176.html

相關文章
相關標籤/搜索