1. 程式人生 > >淺談C++多型&多型物件模型

淺談C++多型&多型物件模型

多型

所謂多型,其實就是“多種形態”,C++的多型分為靜態多型和動態多型。
1. 靜態多型就是過載,因為是在編譯期決議確定,所以稱為靜態多型。
2. 動態多型就是通過繼承重寫基類的虛擬函式實現的多型,因為是在運⾏時決議確定,所以稱為動態多型。

C++中虛擬函式的主要作⽤——就是實現多型。簡單說⽗類的指標/引⽤調⽤重寫的虛擬函式,當⽗類指標/引⽤指向⽗類物件時調⽤的是⽗類的虛擬函式,指向⼦類物件時調⽤的是⼦類的虛擬函式。
ps:父類即基類,子類即派生類。

虛擬函式
虛擬函式的定義在父類中進行,它是在父類中需要定義為虛擬函式的成員函式的宣告中冠以關鍵字 virtual ,從而提供一種介面介面。並且虛擬函式可以在一個或者多個子類

中重新定義。定義時,其函式名、返回型別、引數個數、引數型別的順序,都必須和父類中原型完全相同。舉例如下:
virtual 返回型別 函式名(形參表)
{
函式體
}

#include<iostream>
using namespace std;
class Grandma
{
public:
    virtual void introduce_self()
    {
        cout << "I am Grandma" << endl; 
    }
};
class Mother:public
Grandma { public: void introduce_self() { cout << "I am Mother" << endl; } }; class Daughter :public Mother { public: void introduce_self() { cout << "I am Daughter" << endl; } }; int main() { Grandma* ptr; Grandma g; Mother m; Daughter d; ptr = &g; ptr->introduce_self(); ptr = &m; ptr->introduce_self(); ptr = &d; ptr->introduce_self(); system("pause"
); return 0; }

結果如下:
這裡寫圖片描述
程式只有Grandma中顯式定義了introduce_self()是虛擬函式。C++中規定,若沒有virtual顯式宣告,將依據以下條件判斷是否是虛擬函式。1、該函式與虛擬函式有相同名稱。2、該函式與虛擬函式有相同引數個數及相同對應引數型別3、該函式與虛擬函式有相同返回值型別或者滿足賦值相容規則的指標、引用型別的返回值。

賦值相容規則
1. ⼦類物件可以賦值給⽗類物件(切割/切⽚)
2. ⽗類物件不能賦值給⼦類物件
3. ⽗類的指標/引⽤可以指向⼦類物件
4. ⼦類的指標/引⽤不能指向⽗類物件(可以通過強制型別轉換完成)
ps:純虛擬函式—— 宣告純虛擬函式一般形式如下:
virtual 函式型別 函式名(引數表)=0;
純虛擬函式不具備函式的功能,不能被呼叫。因為包含至少一個純虛擬函式的抽象類不能例項化出物件。

虛擬函式表
虛擬函式表是通過⼀塊連續記憶體來儲存虛擬函式的地址。這張表解決了繼承、虛擬函式(重寫)的問題。在有虛擬函式的物件例項中都存在⼀張虛擬函式表,虛擬函式表就像⼀張地圖,指明瞭實際應該調⽤的虛擬函式。虛擬函式表的地址可以通過記憶體視窗看到。

以單繼承虛表舉例:

#include<iostream>
using namespace std;

class Base
{
public:
    virtual void func1()
    {
        cout << "Base::func1" << endl;
    }
    virtual void func2()
    {
        cout << "Base::func2" << endl;
    }
private:
    int a;
};
class Derive :public Base
{
public:
    virtual void func1()
    {
        cout << "Derive::func1" << endl;
    }
    virtual void func3()
    {
        cout << "Derive::func3" << endl;
    }
    virtual void func4()
    {
        cout << "Derive::func4" << endl;
    }
private:
    int b;
};
typedef void(*FUNC) ();

void PrintVTable(int* VTable)
{
    cout << "虛表地址>" << VTable << endl;
    for (int i = 0; VTable[i] != 0; ++i)
    {
        printf(" 第%d個虛擬函式地址 :0X%x,->", i, VTable[i]);
        FUNC f = (FUNC)VTable[i];
        f();
    }
    cout << endl;
}
void Test1()
{
    Base b1;
    Derive d1;
    int* VTable1 = (int*)(*(int*)&b1);
    int* VTable2 = (int*)(*(int*)&d1);
    PrintVTable(VTable1);
    PrintVTable(VTable2);
}
int main()
{
    Test1();
    system("pause");
    return 0;
}

可以看到派⽣類Derive::func1重寫基類Base::func1,覆蓋了相應虛表位置上的函式。
ps:可以看到這⾥沒有看到派⽣類Derive中的func3和func4,這兩個函式就放在func2的後⾯,這⾥沒有顯⽰是VS的問題(bug)。
下面的圖有助於更好理解。

單繼承模型

那麼多繼承在記憶體中佈局是怎樣的?下面例子告訴你 !

#include<iostream>
using namespace std;

class Base1
{
public:
    virtual void func1()
    {
        cout << "Base::func1()" << endl;
    }
    virtual void func2()
    {
        cout << "Base::func2()" << endl;
    }
public:
    int b1;
};
class Base2
{
public:
    virtual void func1()
    {
        cout << "Base::func1()" << endl;
    }
    virtual void func3()
    {
        cout << "Base::func3()" << endl;
    }
public:
    int b2;

};
class Derive:public Base1,public Base2
{
public:
    virtual void func1()
    {
        cout << "Derive::func1()" << endl;
    }
    virtual void func4()
    {
        cout << "Derive::func4()" << endl;
    }
public:
    int d1;
};

typedef void(*FUNC) ();

void PrintVTable(int* Vtable)
{
    cout << "虛表地址>" << Vtable << endl;
    int ** ppvtable = (int**)Vtable;
    for (int i = 0; ppvtable[i] != 0; ++i)
    {
        printf(" 第%d個虛擬函式地址 :0X%x,->", i, ppvtable[i]);
        FUNC f = (FUNC)ppvtable[i];
        f();
    }
    cout << endl;
}
void Test1()
{
    Derive  d;
    d.b1 = 1;
    d.b2 = 2;
    d.d1 = 3;

    PrintVTable(*((int**)(&d)));

    // Base2虛擬函式表在物件Base1後⾯
    PrintVTable(*((int**)((char*)&d + sizeof(Base1))));
}
int main()
{
    Test1();
    system("pause");
    return 0;
}

多繼承物件模型

在多繼承模型中我們在列印虛擬函式表時應注意型別的轉換和不同虛表在記憶體中的佈局關係。在之後的菱形繼承模型中,我們會用到虛繼承來解決二義性和資料冗餘問題,對記憶體佈局加以更深瞭解。

相關推薦

C++&物件模型

多型 所謂多型,其實就是“多種形態”,C++的多型分為靜態多型和動態多型。 1. 靜態多型就是過載,因為是在編譯期決議確定,所以稱為靜態多型。 2. 動態多型就是通過繼承重寫基類的虛擬函式實現的多型,因為是在運⾏時決議確定,所以稱為動態多型。 C++中虛

C++

面向物件的程式設計都有封裝,繼承和多型三大特性,前兩個特性在之前的部落格中已經提到,這篇主要講關於C++多型問題。 何為多型? 簡單的說,就是一句話:允許將子類型別的指標(引用)賦值給父類型別的指標(引用)。 舉個例子,當我們在定義兩個類,一個Person類作為父類,在

C++實現原理(虛繼承的奧祕)

大夥都知道,如果要實現C++的多型,那麼,基類中相應的函式必須被宣告為虛擬函式(或純虛擬函式)。舉個例子: class Point { public: Point(float x = 0.0, float y = 0.0) : _x(x), _y(y) { } virtual fl

C#中的值類和引用類

title log 創建 編譯 設計 編寫 通過 發布 構造 在C#中,值類型和引用類型是相當重要的兩個概念,必須在設計類型的時候就決定類型實例的行為。如果在編寫代碼時不能理解引用類型和值類型的區別,那麽將會給代碼帶來不必要的異常。很多人就是因為沒有弄清楚這兩個概念從而在編

【.Net】C#中的值類和引用類

rem 理解 amp div net 親情 實例 函數 大小 在C#中,值類型和引用類型是相當重要的兩個概念,必須在設計類型的時候就決定類型實例的行為。如果在編寫代碼時不能理解引用類型和值類型的區別,那麽將會給代碼帶來不必要的異常。很多人就是因為沒有弄清楚這兩個概念從而在編

C#語言中的各種數據類,與數據類之間的轉換

優化配置 line com 歸類 浮點 初學者 結構 ali 順序 什麽是數據類型? 數據類型,百度百科是這樣解釋的:數據類型在數據結構中的定義是一個值的集合以及定義在這個值集上的一組操作。這樣的解釋對於一個初學者來說未必太過於深奧。 簡單點說,數據類型就是不同長度的數據的

C++態性

tle string 意思 ptr 多態 其中 param 語言 希望 C++編程語言是一款應用廣泛,支持多種程序設計的計算機編程語言。我們今天就會為大家詳細介紹其中C++多態性的一些基本知識,以方便大家在學習過程中對此能夠有一個充分的掌握。   多態性可

C++中的四種類轉換

轉換的含義是通過改變一個變數的型別為別的型別從而改變該變數的表示方式。為了型別轉換一個簡單物件為另一個物件你會使用傳統的型別轉換操作符。 型轉換有c風格的,當然還有c++風格的。c風格的轉換的格式很簡單(TYPE)EXPRESSION;例如將為了轉換一個型別為doubole

C#泛

一.為什麼要提出泛型的概念 我們在宣告物件或者方法中,物件中成員變數的定義或者函式引數都傳遞都要指定具體的物件型別,但是有的時候引數的型別是變化的,但是實現的功能卻又差不多,這個時候我們就想,是否存在一種東西可以將引數的位置“佔住”,當傳遞具體的物件型別是再用這個型別取替換被佔住的位

js數據類識別方法

簡單 string name bject 識別方法 ber true logs 對象 js有5種基本數據類型 Undefined , Null , Boolean , Number , String 和一種引用類型Object,下面我們就來一一看穿,哦不,識別他們。

c#語言的類,事件,物件,方法和函式的區別

在討論這些關係之前讓我們先來了解一下面向過程和麵向物件的區別: 所謂面向過程就是把一切事物或者需要解決的問題都當做是一個過程來處理,而面向物件則是把一切事物當成一個一個的物件來處理,這兩者有什麼區別呢,舉一個簡單的例子: 編寫一個駕駛汽車的程式: (1)面向過程的程式設計:

網路I/O路複用模型 select & poll & epoll

我們首先需要知道select,poll,epoll都是IO多路複用的機制。I/O多路複用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,

Java中的泛

編寫 eric ria getc variable ner strong 集合類 類繼承 泛型是Java自JDK5開始支持的新特性,主要用來保證類型安全。另外泛型也讓代碼含義更加明確清晰,增加了代碼的可讀性。 泛型的聲明和使用   在類聲明時在類名後面聲明泛型,比如My

javaScript數據類、變量、內存之間的關系,文末有圖解

賦值 特殊 資源釋放 都是 給他 存儲 情況 數據 引用類型 一、變量是沒有類型的 在JavaScript中,定義變量的方法是“var 變量名=變量值”,無論這個變量要給他賦值為一個數字、字符串還是數組,他的類型都不需要聲明。也就是說如果我

C#解析網頁

多說 .text 了解 light path text load 所有 web 最近做了一個項目,要求獲取各大主流網頁上的關鍵信息,本人以前了解過網頁爬蟲的知識,所以想到了網頁爬蟲了實現功能 第一次嘗試: 采用webclient獲取遠程網頁的內容,然後采用正則表達式進行過濾

C#實現Web代理服務器的幾大步驟

tag 代碼 sco hostent 網絡代理 cat accept 中間 recv  代理服務程序是一種廣泛使用的網絡應用程序。代理程序的種類非常多,根據協議不同可以分成HTTP代理服務程序、FTP代理服務程序等,而運行代理服務程序的服務器也就相應稱為HTTP代理服務器和

c++中結構體和共用體的區別

ont 基本類型 erl list 變量名 ext 使用結構體 oot 數據格式 在c++中,結構體(struct)和共用體(union)是兩種很相似的復合數據類型,都可以用來存儲多種數據類型,但是兩者還有很大的區別。 結構體(struct) 結構是用戶自定

從拳皇97中C#委托與事件

n) virtual 所有 [] 輸出 rri strac 理解 簡單 先看看下列代碼: protected void Page_Load(object sender, EventArgs e){ } protected void btnSearch_Click(objec

C語言字符串結束符'

單獨 lan 數組 targe 情況 包括 span htm ext 如果你希望你的字符串以’\0‘結束,那麽你可以這樣做: 1 char str[]={"hello"};//①字符串賦值 2 char str[]={‘h‘,‘e‘,‘l‘,

編程之美,讓美國人科技高速發展,C語言帶給美國的變化

c99 開發者 追蹤 n) 再次 故障排查 最新 gen 內部數據   我去年7月份有幸應美國朋友的邀約,在美國眾多正在飛速發展中的高科技型企業暢遊了一番。本來我以為,美國只有Google公司,蘋果公司,FaceBook,IBM,微軟,思科這些巨型的高新技術企業在世界的新技