天天看點

C的面向對象程式設計

C語言并不支援類這樣的概念,但是C仍舊可以使用面向對象的概念。

C++中的類,關鍵在于它的虛函數表。是以,我們要模拟一個能夠支援虛函數表的類。

使用C的struct結構,可以模拟類和虛函數。

比如,我們來模拟一個shape類

//模拟虛函數表  

typedef struct _Shape Shape;  

struct ShapeClass {  

   void (*construct)(Shape* self);  

   void (*destroy)(Shape *self);  

   void (*draw)(Shape *self);  

};  

struct _Shape {  

    ShapeClass *klass;  //定義class的指針  

    int x, y, width, height;  

ShapeClass 定義了Shape類的虛函數表,其中construct和destroy分别模拟構造和析構函數,draw則是一個虛函數。Shape模拟資料成員。Shape中的ShapeClass将關聯到具體的實作上。

Shape對象要能夠使用,還必須做到以下幾點

實作一個ShapeClass類

初始化Shape為正确的類

首先,我們要實作ShapeClass定義的各個成員函數指針

void Shape_construct(Shape* self) {  

    self->x = 0;  

    self->y  = 0;  

    self->width = 100;  

    self->height = 100;   

}  

void Shape_destroy(Shape* self)  

{  

   //TODO delete datas  

void Shape_draw(Shape* self)  

   //TODO draw ....  

ShapeClass _shape_class = {  

    Shape_construct,  

    Shape_destroy,  

    Shape_draw,  

Shape *newShape()  

    Shape *shape = (Shape*)malloc(sizeof(Shape);  

    shape->klass = &_shape_class;  

    shape->klass->construct(shape);  

    return shape;  

void deleteShape(Shape* shape)  

   shape->klass->destroy(shape);  

   free(shape);  

當我們調用shape的draw函數時,應該

Shape *shape = newShape();  

....  

shape->klass->draw(shape);  

deleteShape(shape);  

上面的原理容易了解,但是,編寫起代碼來,着實繁瑣且易錯, 而且,construct, destory這類方法都是對象最基本的方法,是以,我們抽象出一個Object類來

#define ClassType(className)    className##Class  

#define Class(className)        g_st##className##Cls  

typedef struct _mObjectClass mObjectClass;  

typedef struct _mObject mObject;  

typedef mObjectClass* (*PClassConstructor)(mObjectClass *);  

#define mObjectClassHeader(clss, superCls) \  

    PClassConstructor classConstructor; \  

    ClassType(superCls) * super; \  

    const char* typeName; /* */ \  

    unsigned int objSize; \  

    /* class virtual function */ \  

    void (*construct)(clss *self, DWORD addData); \  

    void (*destroy)(clss *self); \  

    DWORD (*hash)(clss *self); \  

    const char* (*toString)(clss *self, char* str, int max);  

struct _mObjectClass {  

    mObjectClassHeader(mObject, mObject)  

extern mObjectClass g_stmObjectCls; //Class(mObject);  

#define mObjectHeader(clss) \  

    ClassType(clss) * _class;  

struct _mObject {  

    mObjectHeader(mObject)  

mObject和mObjectClass是所有類的基礎類。

這裡,我們使用了一個技巧,及通過定義mObjectClassHeader和mObjectHeader兩個宏,讓Object的繼承類能夠“繼承”Object的定義。這一點在後文講述。

mObject的定義很簡單的,就定義了一個mObjectClass *_class類(mObjectHeader宏的展開)。

mObjectClass的定義,稍微複雜一些,每個成員描述如下:

classConstructor : 這是類本身的初始化。他的作用是,将類的虛函數表填充完整。之是以用一個函數來填充虛函數表,是為了能夠讓派生類和基類的類類型都能夠得到正确的初始化。

super : 這是超類,是為繼承做準備的

typeName: 存儲類的名稱

objSize: 定義了類本身的大小,這樣在malloc的時候,不需要知道具體的類類型,就可以配置設定足夠的空間

construct, destory: 構造和析構

hash: hash函數,用在hash表中

toString:調試時生成描述資訊

我們通過extern聲明了g_stmObjectCls變量。這個變量是mObjectClass的變量,包含的都是類的虛函數表和最基本的資訊。當我們建立類的時候,就需要這個函數了。

下面看看new和delete函數的實作

mObject * newObject(mObjectClass *_class)  

    mObject * obj;  

    if(_class == NULL)  

        return NULL;  

    obj = (mObject*)calloc(1, _class->objSize);  

    if(!obj)  

    obj->_class = _class;  

    return obj;  

void deleteObject(mObject *obj)  

    if(obj == NULL || obj->_class)  

        return;  

    _c(obj)->destroy(obj);  

    free(obj);  

......  

static inline mObject * ncsNewObject(mObjectClass *_class,DWORD add_data){  

    mObject * obj = newObject(_class);  

<span style="white-space:pre">      </span>return NULL;  

    _class->construct(obj, add_data);  

newObject負責對對象做最基本的初始化: 調用calloc配置設定空間,然後将_class賦給對象。而ncsNewObject函數,則調用了construct函數,完成對象的初始化。

那麼,g_stmObjectCls是如何聲明和初始化的?請看代碼

static void mObject_construct(mObject* self, DWORD addData)  

    //do nothing  

    //to avoid NULL pointer  

static void mObject_destroy(mObject* self)  

static DWORD mObject_hash(mObject *self)  

    return (DWORD)self;  

static const char* mObject_toString(mObject *self, char* str, int max)  

    if(!str)  

    snprintf(str, max, "NCS %s[@%p]", TYPENAME(self),self);  

    return str;  

static mObjectClass* mObjectClassConstructor(mObjectClass* _class)  

    _class->super = NULL;   

    _class->typeName = "mObject";  

    _class->objSize = sizeof(mObject);  

    CLASS_METHOD_MAP(mObject, construct)  

    CLASS_METHOD_MAP(mObject, destroy)  

    CLASS_METHOD_MAP(mObject, hash)  

    CLASS_METHOD_MAP(mObject, toString)  

    return _class;  

mObjectClass Class(mObject) = {  

    (PClassConstructor)mObjectClassConstructor  

CLASS_METHOD_MAP宏的定義是

#define CLASS_METHOD_MAP(clss, name) \  

        _class->name = (typeof(_class->name))(clss##_##name);  

這裡為了友善,要求統一的命名規範。

注意到mObjectClassConstructor,他就是mObjectClass中的classConstructor的實作。看所做的工作:

給出類的名字

給出對象的大小

将虛函數表填充完整

mObject類本身沒有任何用處,他隻是作為根類存在。我們必須定義其他類,才能起到作用。 那麼,如果要實作繼承,應該怎麼辦呢?

還以Shape為例,基本上應該是這樣

typedef struct _mShape mShape;  

typedef struct _mShapeClass mShapeClass;  

struct _mShape {   

       mObject base;   

       int x, y, width, height;  

struct _mShapeClass {   

       mObjectClass base;   

       void (*draw)(mShape* self);  

mShape和mShapeClass都将mObject和mObjectClass放在最上面,這樣,C編譯器就會保證mShape和mObject的記憶體結構,在前半部分都是一緻的。是以,當我使用 mObject *obj = (mObject*)shape這樣的代碼時,不會發生任何意外。通過這個方法,就能實作C++的多态。

但,這裡有兩個問題:

如果我們想通路父類的方法,就必須通過 shape->base.XXX來通路,如果通路方法,就必須shape->base._class->construct

必須進行強制轉換:

如果我們通路父類的虛函數,則必須把子類轉換為父類,如 shape->base._class->toString((mObject*)shape);

如果我們要通路自己的虛函數,則必須把父類的虛函數表,轉換為自己的,如  ((mShapeClass*)(shape->base._class))->draw(shape);

這不僅僅是寫法上繁瑣這麼簡單。當繼承層次很多時,既要寫一長串的base調用,還必須記住繼承的順序和層次,這基本上是不可能的。

這是,我們需要通過宏,來實作聲明的"繼承"

#define mShapeHeader(Cls) \  

     mObjectHeader(Cls) \  

     int x, y, width, height;  

struct  _mShape {  

    mShapeHeader(mShape)  

#define mShapeClassHeader(Cls, Super) \  

    mObjectClassHeader(Cls, Super) \  

    void (*draw)(Cls* self)  

struct mShapeClass {  

    mShapeClassHeader(mShape, mObject)  

<ClassName>Header和<ClassName>ClassHeader宏很好的解決了這個問題。mObject的所有聲明都将在mShape和mShapeClass中在聲明一遍,而且,Class的名字,也從mObject替換為了mShape了。這樣一來,當我們使用mShape類型的變量時,所有的虛函數都可以被直接調用,不需要任何的轉換。

mShape和mObject之間,仍舊保持了那種記憶體上的一緻性。

當mShape作為基類時,他的派生類可以使用mShapeHeader和mShapeClassHeader來生成新的類。

下面,我們讨論下,mShapeClass的初始化問題。

虛函數表雖然定義了結構,卻沒有定義變量,需要定義:

extern mShapeClass g_stmShapeCls;  

然後,在再shape.c中,聲明和填充g_stmShapeCls。

g_stmShapeCls的實作和g_stmShapeCls是一樣的,也需要定義一個classConstructor函數,然後在這個函數中初始化類的名字、mShape的大小以及draw函數指針的初始化。但是,這樣寫非常繁瑣,是以,我們通過一個宏來定義

  #define BEGIN_MINI_CLASS(clss, superCls) \  

1 static ClassType(clss) * clss##ClassConstructor(ClassType(clss)* _class); \  

2 ClassType(clss) Class(clss) = { (PClassConstructor)clss##ClassConstructor }; \  

3 static const char* clss##_type_name = #clss; \  

4 static ClassType(clss) * clss##ClassConstructor(ClassType(clss)* _class) { \  

5   _class = (ClassType(clss)*)((PClassConstructor)(Class(superCls).classConstructor))((mObjectClass*)_class); \  

6   _class->super = &Class(superCls); \  

7   _class->typeName = clss##_type_name; \  

8   _class->objSize = sizeof(clss);  

  #define END_MINI_CLASS return _class; }  

  #define CLASS_METHOD_MAP(clss, name) \  

我們把ClassConstructor函數的聲明拆成了3部分:初始化定義、結束定義和方法填充。重點解釋的是初始化定義:

BEGIN_MINI_CLASS :

行1: 前置聲明ClassConstructor函數,使用類名以區分不同類的classConstructor函數

行2: 聲明了g_stmShapeCls變量,并将ClassConstructor指派給它。這是非常重要的,如果沒有這一步驟,那麼,虛函數表就無法被初始化;

行3:聲明一個類的名字的字元串數組

行4:定義了ClassConstructor函數的實作部分

行5:首先調用超類的ClassConstructor,讓超類先初始化一遍,這樣如果子類不覆寫超類的函數,那麼,我們将繼續使用超類的函數,這是多态的“繼承”特性

行6:設定超類指針

行7:設定類名

行8:得到成員變量的大小

使用的時候,非常簡單

BEGIN_MINI_CLASS(mShape, mObject)  

   CLASS_METHOD_MAP(mShape, draw)  

END_MINI_CLASS  

這樣做不僅避免了大量字元輸入,更重要的是:1)避免錯誤;2)避免開發者學習和記住這些通用性很強的内容。

當然,這種情況下,類還是不能直接使用的,要使用,必須調用一次g_stmShapeCls.classConstructor類,真正完成類的初始化。為了簡便,提供一個宏來簡化這個過程:

#define MGNCS_WIDGET_REGISTER(className) \  

    Class(className).classConstructor((mObjectClass*)(void*)(&(Class(className))))  

在初始化時

void init()  

   ...  

   MGNCS_WIDGET_REGISTER(mShape);  

用C模拟類,還能夠得到C++的RTTI的一些效果,例如,模拟java的instanceof關鍵字

BOOL ncsInstanceOf(mObject *object, mObjectClass* clss)  

    mObjectClass* objClss;  

    if(object == NULL || clss == NULL)  

        return FALSE;  

    objClss = _c(object);  

    while(objClss && clss != objClss){  

        objClss = objClss->super;  

    }  

    return objClss != NULL;  

#define INSTANCEOF(obj, clss)  ncsInstanceOf((mObject*)(obj), (mObjectClass*)(void*)(&Class(clss)))  

我們可以直接去判斷,如  INSTANCEOF(rectange, mShape)。這個消耗是很少的,因為,繼承層次超過5層的已經非常少了,基本上,繼承層次在5層以内就能做出足夠的抽象。

QQ:519841366

本頁版權歸作者和部落格園所有,歡迎轉載,但未經作者同意必須保留此段聲明,

且在文章頁面明顯位置給出原文連結,否則保留追究法律責任的權利

繼續閱讀