在C裡面做到部分OOP(主要是多型)的功能

事情的起頭是我某天在公司看vender的code,乍看之下發現他們一個種類的item就分成一組header file和source file,而且struct的宣告有著類似的layout,initialize的時候使用了一樣的function table structure,只是存入了各自的function。

稍微一想,欸嘿,這不是OOP嗎? 於是就有了以下的測試

#include <stdio.h> //printf
typedef struct
{
    void (*f)(void);
}base;
//作為base class(?)的struct

typedef struct
{
    void (*af)(void);
}A;
//當作繼承base的derived class A

typedef struct
{
    void (*bf)(void);
}B;
//當作繼承base的derived class B

void printa()
{
    printf("A\n");
}
//給class A的function

void printb()
{
    printf("B\n");
}
//給class B的function

void print(void* ptr)
{
    ((base*)ptr)->f();
}
//傳入參數的type是void*是為了能夠同時接受A, B的pointer
//這裡同時也是"類似"OOP的地方

int main()
{
    A a;
    a.af = printa;
    B b;
    b.bf = printb;
    print(&a); //A
    print(&b); //B
    return 0;
}

上面的範例是簡化過的,複雜一點的可以再自訂一個struct專門放function pointer,例如

typedef struct 
{
    void(*add)(void*,void*);
    void(*minus)(void*,void*);
}baseMethod;
typedef struct
{
    baseMethod method;
}base;
typedef struct
{
    baseMethod method;
    int val;
}A;

typedef struct
{
    baseMethod method;
    double val;
}B;

上面的範例是提供一個自訂int/double加減法的struct,接著需要initialize A和B的function table(baseMethod)。

void a_plus(void* ptr1, void* ptr2)
{
    ((A*)ptr1)->val = ((A*)ptr1)->val + ((A*)ptr2)->val;
}
void a_minus(void* ptr1, void* ptr2)
{
    ((A*)ptr1)->val = ((A*)ptr1)->val - ((A*)ptr2)->val;
}
void b_plus(voi
d* ptr1, void* ptr2)
{
    ((B*)ptr1)->val = ((B*)ptr1)->val + ((B*)ptr2)->val;
}
void b_minus(void* ptr1, void* ptr2)
{
    ((B*)ptr1)->val = ((B*)ptr1)->val - ((B*)ptr2)->val;
}
void addition(void* ptr1, void* ptr2)
{
    ((base*)ptr1)->method.add(ptr1,ptr2);
}
int main()
{
    A a1,a2;
    a1.method.add = a_plus;
    a1.method.minus = a_minus;
    a1.val = 1;
    a2.val = 2; 
    //偷個懶,不init a2的method,比較好的做法應該是把宣告一個global static的method的變數
    //struct裡只存放(baseMethod*)指到那個變數
    B b1,b2;
    b1.method.add = b_plus;
    b2.method.minus = b_minus;
    b1.val = 11;
    b2.val = 12;
    addition(&a1,&a2);
    addition(&b1,&b2);
    printf("%d\n",a1.val); //3
    printf("%lf\n",b1.val); //23.0
    return 0;
}

讓addition去call 各自的add,可以發現它會正確的call到各自的function。

這種做法最重要的是,Base的function要和其他struct對應的function有相同的memory offset,最簡單的就是都把它們宣告在struct的最一開始,所以你會看到上面的baseMethod都宣告在A跟B的最上面。

如同標題所說,這篇文章主要在講的是polymorphism(多形),inheritance只能靠programmer自己動手刻,encapsulation在C也能勉強(?)做到,就沒什麼好提的了。

說到底其實都是寫C很常玩的pointer XD

發表留言