首頁 後端開發 C#.Net教程 必學! C++實作多型機制滿足的基本條件條件

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

Jul 24, 2018 pm 02:11 PM

如何實現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

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

C語言各種符號的使用方法 C語言各種符號的使用方法 Apr 03, 2025 pm 04:48 PM

C 語言中符號的使用方法涵蓋算術、賦值、條件、邏輯、位運算符等。算術運算符用於基本數學運算,賦值運算符用於賦值和加減乘除賦值,條件運算符用於根據條件執行不同操作,邏輯運算符用於邏輯操作,位運算符用於位級操作,特殊常量用於表示空指針、文件結束標記和非數字值。

char在C語言字符串中的作用是什麼 char在C語言字符串中的作用是什麼 Apr 03, 2025 pm 03:15 PM

在 C 語言中,char 類型在字符串中用於:1. 存儲單個字符;2. 使用數組表示字符串並以 null 終止符結束;3. 通過字符串操作函數進行操作;4. 從鍵盤讀取或輸出字符串。

char在C語言中如何處理特殊字符 char在C語言中如何處理特殊字符 Apr 03, 2025 pm 03:18 PM

C語言中通過轉義序列處理特殊字符,如:\n表示換行符。 \t表示製表符。使用轉義序列或字符常量表示特殊字符,如char c = '\n'。注意,反斜杠需要轉義兩次。不同平台和編譯器可能有不同的轉義序列,請查閱文檔。

c#多線程和異步的區別 c#多線程和異步的區別 Apr 03, 2025 pm 02:57 PM

多線程和異步的區別在於,多線程同時執行多個線程,而異步在不阻塞當前線程的情況下執行操作。多線程用於計算密集型任務,而異步用於用戶交互操作。多線程的優勢是提高計算性能,異步的優勢是不阻塞 UI 線程。選擇多線程還是異步取決於任務性質:計算密集型任務使用多線程,與外部資源交互且需要保持 UI 響應的任務使用異步。

char與wchar_t在C語言中的區別 char與wchar_t在C語言中的區別 Apr 03, 2025 pm 03:09 PM

在 C 語言中,char 和 wchar_t 的主要區別在於字符編碼:char 使用 ASCII 或擴展 ASCII,wchar_t 使用 Unicode;char 佔用 1-2 個字節,wchar_t 佔用 2-4 個字節;char 適用於英語文本,wchar_t 適用於多語言文本;char 廣泛支持,wchar_t 依賴於編譯器和操作系統是否支持 Unicode;char 的字符範圍受限,wchar_t 的字符範圍更大,並使用專門的函數進行算術運算。

char在C語言中如何進行類型轉換 char在C語言中如何進行類型轉換 Apr 03, 2025 pm 03:21 PM

在 C 語言中,char 類型轉換可以通過:強制類型轉換:使用強制類型轉換符將一種類型的數據直接轉換為另一種類型。自動類型轉換:當一種類型的數據可以容納另一種類型的值時,編譯器自動進行轉換。

char和unsigned char的區別是什麼 char和unsigned char的區別是什麼 Apr 03, 2025 pm 03:36 PM

char 和 unsigned char 是存儲字符數據的兩種數據類型,主要區別在於處理負數和正數的方式:值範圍:char 有符號 (-128 到 127),unsigned char 無符號 (0 到 255)。負數處理:char 可以存儲負數,unsigned char 不能。位模式:char 最高位表示符號,unsigned char 無符號位。算術運算:char 和 unsigned char 作為有符號和無符號類型,其算術運算方式不同。兼容性:char 和 unsigned char

char數組在C語言中如何使用 char數組在C語言中如何使用 Apr 03, 2025 pm 03:24 PM

char 數組在 C 語言中存儲字符序列,聲明為 char array_name[size]。訪問元素通過下標運算符,元素以空終止符 '\0' 結尾,用於表示字符串終點。 C 語言提供多種字符串操作函數,如 strlen()、strcpy()、strcat() 和 strcmp()。

See all articles