首頁  >  文章  >  後端開發  >  必學! C++實作多型機制滿足的基本條件條件

必學! C++實作多型機制滿足的基本條件條件

php是最好的语言
php是最好的语言原創
2018-07-24 14:11:022109瀏覽

如何實現C 的多態機制,實現運行多態的機制是在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將根據對象的實際類型來調用對應的函數。

C 多態的實作及原理

C 的多型用一句話概括就是:在基底類別的函數前加上virtual關鍵字,在衍生類別中重寫函數,運行時會根據物件的實際類型來呼叫對應的函數。如果物件類型是衍生類,就呼叫衍生類別的函數;如果物件類型是基底類別,就呼叫基底類別的函數

1:用virtual關鍵字申明的函數叫做虛函數,虛函數肯定是類別的成員函數。

2:存在虛函數的類別都有一個一維的虛函數表叫做虛表,類別的物件有一個指向虛表開始的虛指標。虛表是和類別對應的,虛表指標是和物件對應的。

3:多態性是一個介面多種實現,是物件導向的核心,分為類別的多態性和函數的多態性。

4:多型用虛函數來實現,結合動態綁定.

5:純虛函數是虛函數再加上= 0;

6:抽象類別是指包括至少一個純虛函數的類別。

純虛函數:virtual void fun()=0;即抽象類別!必須在子類別實作這個函數,也就是先有名稱,沒有內容,在衍生類別實作內容。

我們先來看個例子

#include <iostream> #include <stdlib.h>using namespace std; 

class Father
{public:    void Face()
    {        cout << "Father&#39;s face" << endl;
    }    void Say()
    {        cout << "Father say hello" << endl;
    }
};class Son:public Father
{public:     
    void Say()
    {        cout << "Son say hello" << endl;
    }
};int main()
{
    Son son;
    Father *pFather=&son; // 隐式类型转换
    pFather->Say();    return 0;
}

輸出的結果為:

Father say hello

我們在main()函數中先定義了一個Son類別的物件son,接著定義了一個指向Father類別的指標變數pFather,然後利用該變數呼叫pFather->Say().估計很多人往往將這種情況和c 的多態性搞混淆,認為son實際上是Son類別的對象,應該是呼叫Son類別的Say,輸出”Son say hello”,然而結果卻不是.

  從編譯的角度來看:

  c 編譯器在編譯的時候,要確定每個物件呼叫的函數(非虛函數)的位址,稱為早期綁定,當我們將Son類別的物件son的位址賦給pFather時,c 編譯器進行了類型轉換,此時c 編譯器認為變數pFather保存的就是Father物件的位址,當在main函數中執行pFather->Say(),呼叫的當然就是Father物件的Say函數

 從記憶體角度看

必學! C++實作多型機制滿足的基本條件條件

#Son類別物件的記憶體模型如上圖

我們建構Son類別的物件時,首先要呼叫Father類別的建構子去建構Father類別的對象,然後才呼叫Son類別的建構函數完成自身部分的構造,從而拼接出一個完整的Son類別物件。當我們將Son類別物件轉換為Father類型時,該物件就被認為是原始物件整個記憶體模型的上半部分,也就是上圖中“Father的物件所佔記憶體”,那麼當我們利用型別轉換後的當物件指標去呼叫它的方法時,當然也就是呼叫它所在的記憶體中的方法,因此,輸出“Father Say hello”,也就順理成章了。

  正如許多人那麼認為,在上面的程式碼中,我們知道pFather實際上指向的是Son類別的對象,我們希望輸出的結果是son類別的Say方法,那麼想到達到這種結果,就要用到虛函數了。

  前面輸出的結果是因為編譯器在編譯的時候,就已經確定了物件呼叫的函數的位址,要解決這個問題就要使用晚綁定,當編譯器使用晚綁定時候,就會在運行時再去確定物件的類型以及正確的呼叫函數,而要讓編譯器採用晚綁定,就要在基類中聲明函數時使用virtual關鍵字,這樣的函數我們就稱之為虛函數,一旦某個函數在基底類別中宣告為virtual,那麼在所有的衍生類別中該函數都是virtual,而不需要再明確地宣告為virtual。

  程式碼稍微改動一下,看一下運行結果

#include <iostream> #include <stdlib.h>using namespace std; 

class Father
{public:    void Face()
    {        cout << "Father&#39;s face" << endl;
    }    virtual void Say()
    {        cout << "Father say hello" << endl;
    }
};class Son:public Father
{public:     
    void Say()
    {        cout << "Son say hello" << endl;
    }
};int main()
{
    Son son;
    Father *pFather=&son; // 隐式类型转换
    pFather->Say();    return 0;
}

運行結果:

Son say hello

我們發現結果是」Son say hello」也就是根據物件的類型呼叫了正確的函數,那麼當我們將Say()宣告為virtual時,背後發生了什麼事。

  編譯器在編譯的時候,發現Father類別中有虛函數,此時編譯器會為每個包含虛函數的類別建立一個虛表(即vtable),該表是一個一維數組,在這個陣列中存放每個虛擬函數的位址,

必學! C++實作多型機制滿足的基本條件條件

那么如何定位虚表呢?编译器另外还为每个对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表,在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向了所属类的虚表,从而在调用虚函数的时候,能够找到正确的函数,对于第二段代码程序,由于pFather实际指向的对象类型是Son,因此vptr指向的Son类的vtable,当调用pFather->Son()时,根据虚表中的函数地址找到的就是Son类的Say()函数.

  正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的,换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数,那么虚表指针是在什么时候,或者什么地方初始化呢?

  答案是在构造函数中进行虚表的创建和虚表指针的初始化,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表,当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。

  总结(基类有虚函数的):

  1:每一个类都有虚表

  2:虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现,如果基类有3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现,如果派生类有自己的虚函数,那么虚表中就会添加该项。

  3:派生类的虚表中虚地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。

  这就是c++中的多态性,当c++编译器在编译的时候,发现Father类的Say()函数是虚函数,这个时候c++就会采用晚绑定技术,也就是编译时并不确定具体调用的函数,而是在运行时,依据对象的类型来确认调用的是哪一个函数,这种能力就叫做c++的多态性,我们没有在Say()函数前加virtual关键字时,c++编译器就确定了哪个函数被调用,这叫做早期绑定。

  c++的多态性就是通过晚绑定技术来实现的。

  c++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数。

  虚函数是在基类中定义的,目的是不确定它的派生类的具体行为,例如:

  定义一个基类:class Animal //动物,它的函数为breathe()

  再定义一个类class Fish //鱼。它的函数也为breathe()

  再定义一个类class Sheep //羊,它的函数也为breathe()

将Fish,Sheep定义成Animal的派生类,然而Fish与Sheep的breathe不一样,一个是在水中通过水来呼吸,一个是直接呼吸,所以基类不能确定该如何定义breathe,所以在基类中只定义了一个virtual breathe,它是一个空的虚函数,具体的函数在子类中分别定义,程序一般运行时,找到类,如果它有基类,再找到它的基类,最后运行的是基类中的函数,这时,它在基类中找到的是virtual标识的函数,它就会再回到子类中找同名函数,派生类也叫子类,基类也叫父类,这就是虚函数的产生,和类的多态性的体现。

  这里的多态性是指类的多态性。

  函数的多态性是指一个函数被定义成多个不同参数的函数。当你调用这个函数时,就会调用不同的同名函数。

一般情况下(不涉及虚函数),当我们用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。

当设计到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数

现在我们看一个体现c++多态性的例子,看看输出结果:

#include <iostream> #include <stdlib.h>using namespace std; 

class CA 
{ 
public: 
    void f() 
    { 
        cout << "CA f()" << endl; 
    } 
    virtual void ff() 
    { 
        cout << "CA ff()" << endl; 
        f(); 
    } 
}; 

class CB : public CA 
{ 
public : 
    virtual void f() 
    { 
        cout << "CB f()" << endl; 
    } 
    void ff() 
    { 
        cout << "CB ff()" << endl; 
        f(); 
        CA::ff(); 
    } 
}; 
class CC : public CB 
{ 
public: 
    virtual void f() 
    { 
        cout << "C f()" << endl; 
    } 
}; 

int main() 
{ 
    CB b; 
    CA *ap = &b; 
    CC c; 
    CB &br = c; 
    CB *bp = &c; 

    ap->f(); 
    cout << endl;

    b.f(); 
    cout << endl;

    br.f(); 
    cout << endl;

    bp->f(); 
    cout << endl;

    ap->ff(); 
    cout << endl;

    bp->ff(); 
    cout << endl;    return 0; 
}

输出结果:

CA f()CB f()C f()C f()CB ff()CB f()CA ff()CA f()CB ff()C f()CA ff()CA f()

 相关推荐:

【c#教程】C# 多态性       

C# 多态性

C 影片教學_免費C 教學線上學習

#

以上是必學! C++實作多型機制滿足的基本條件條件的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn