剖析C++多型:用C實現簡單多型

life4711發表於2016-03-12

怎樣用C語言實現多型,首先想到的應該是去模擬C++的虛擬函式。

在C++中,每一個含有虛擬函式的類,都有一個虛擬函式表。對於類的每一個物件,都有一個指向虛擬函式表的指標。

用C語言模擬這個過程。先考慮最簡單的情況,只含有一個虛擬函式的類,並且只有建構函式,沒有虛解構函式。 
假設這個類Base有一個int型別的成員變數val,虛擬函式run(int num)能夠輸出(val+num)的值。它的派生類Derived的結構與它類似,但是函式run(int num)輸出(val*num)的值。 
C語言中並沒有類,所以我們用結構體來代替類,用含有指向類的指標的函式來表示類的成員函式。

程式碼如下:

#include <stdio.h>
#include <stdlib.h>

typedef int (*PFUNC)(void*, int); // 函式指標
/*這個宣告引入了 PFUNC 型別作為函式指標的同義字,該函式有個 void* 和 int 型別的引數以及一個 int 型別的返回值。 */
/*基類*/
typedef struct BASE {
    PFUNC VIRTUAL;
    int val;
} Base;

int BASE_RUN(void* self, int num) {
    return ((Base*)self)->val + num;
}

Base* CONS_BASE(int val) {
    Base* p = malloc(sizeof(Base));
    p->VIRTUAL = BASE_RUN;
    p->val = val;
    return p;
}

/*派生類*/
typedef struct DERIVED {
    PFUNC VIRTUAL;
    int val;
} Derived;

int DERIVED_RUN(void* self, int num) {
    return ((Derived*)self)->val * num;
}

Derived* CONS_DERIVED(int val) {
    Derived* p = malloc(sizeof(Derived));
    p->VIRTUAL = DERIVED_RUN;
    p->val = val;
    return p;
}

/*虛擬函式*/
int run(Base* p, int num) {
    return p->VIRTUAL((void*)p, num);
}

int main()
{
    Base* base = CONS_BASE(10);
    Base* derived = (Base*)CONS_DERIVED(10);
    int val_base = run(base, 10);
    int val_derived = run(derived, 10);
    printf("Base:%d\nDerived:%d\n", val_base, val_derived);
    return 0;
}


函式CONS_BASE, CONS_DERIVED返回物件指標,可以看作類的建構函式。函式run可以看作是Base類的成員函式。 

通過結構體的定義可以看出,結構體中有一個指向函式的指標VIRTUAL,他的作用類似於虛擬函式表,只不過因為表中只有一個函式,因此省略了表的結構。 
在建構函式中,除了初始化val的值,還將函式BASE_RUN、DERIVED_RUN賦值給了函式指標VIRTUAL。當呼叫run時,通過self指標找到函式指標VIRTUAL。雖然self指標是指向Base的指標,但是由於Base與Derived的前4個位元組(32位)都是VIRTUAL指標,所以當基類的指標指向派生類時,VIRTUAL指標指向的也是派生類的成員函式DERIVED_RUN。這樣就實現了簡單的多型。 
執行結果如下:

Base:20 
Derived:100

成功。

複雜版本的思路: 
首先結構體裡指向函式的指標要改成(void**)型別,指向一個虛擬函式表。而虛擬函式表要通過另外的一個函式初始化。各個函式在表中的位置要手動的指定,比如run函式在表中第一位,go函式在表中第二位。當呼叫函式時,首先找到虛擬函式表,再根據不同的編號去找到表中對應位的函式進行呼叫。

可以發現虛擬函式的呼叫比普通的函式呼叫多了一個查表的過程,降低了一點效率。

相關文章