• C的面向对象编程


    C语言并不支持类这样的概念,但是C仍旧可以使用面向对象的概念。

    C++中的类,关键在于它的虚函数表。因此,我们要模拟一个能够支持虚函数表的类。

    使用C的struct结构,可以模拟类和虚函数。

    比如,我们来模拟一个shape类

    [cpp] view plaincopy
     
    1. //模拟虚函数表  
    2. typedef struct _Shape Shape;  
    3.   
    4. struct ShapeClass {  
    5.    void (*construct)(Shape* self);  
    6.    void (*destroy)(Shape *self);  
    7.    void (*draw)(Shape *self);  
    8. };  
    9.   
    10. struct _Shape {  
    11.     ShapeClass *klass;  //定义class的指针  
    12.     int x, y, width, height;  
    13. };  


    ShapeClass 定义了Shape类的虚函数表,其中construct和destroy分别模拟构造和析构函数,draw则是一个虚函数。Shape模拟数据成员。Shape中的ShapeClass将关联到具体的实现上。

    Shape对象要能够使用,还必须做到以下几点

    1. 实现一个ShapeClass类
    2. 初始化Shape为正确的类

    首先,我们要实现ShapeClass定义的各个成员函数指针

    [cpp] view plaincopy
     
    1. void Shape_construct(Shape* self) {  
    2.     self->x = 0;  
    3.     self->y  = 0;  
    4.     self->width = 100;  
    5.     self->height = 100;   
    6. }  
    7.   
    8. void Shape_destroy(Shape* self)  
    9. {  
    10.    //TODO delete datas  
    11. }  
    12.   
    13. void Shape_draw(Shape* self)  
    14. {  
    15.    //TODO draw ....  
    16. }  
    17.   
    18. ShapeClass _shape_class = {  
    19.     Shape_construct,  
    20.     Shape_destroy,  
    21.     Shape_draw,  
    22. };  
    23.   
    24. Shape *newShape()  
    25. {  
    26.     Shape *shape = (Shape*)malloc(sizeof(Shape);  
    27.     shape->klass = &_shape_class;  
    28.     shape->klass->construct(shape);  
    29.     return shape;  
    30. }  
    31.   
    32. void deleteShape(Shape* shape)  
    33. {  
    34.    shape->klass->destroy(shape);  
    35.    free(shape);  
    36. }  



    当我们调用shape的draw函数时,应该

    [cpp] view plaincopy
     
    1. Shape *shape = newShape();  
    2. ....  
    3. shape->klass->draw(shape);  
    4. ....  
    5. deleteShape(shape);  

    上面的原理容易理解,但是,编写起代码来,着实繁琐且易错, 而且,construct, destory这类方法都是对象最基本的方法,因此,我们抽象出一个Object类来

    [cpp] view plaincopy
     
    1. #define ClassType(className)    className##Class  
    2. #define Class(className)        g_st##className##Cls  
    3.   
    4. typedef struct _mObjectClass mObjectClass;  
    5. typedef struct _mObject mObject;  
    6.   
    7. typedef mObjectClass* (*PClassConstructor)(mObjectClass *);  
    8.   
    9. #define mObjectClassHeader(clss, superCls)   
    10.     PClassConstructor classConstructor;   
    11.     ClassType(superCls) * super;   
    12.     const char* typeName; /* */   
    13.     unsigned int objSize;   
    14.     /* class virtual function */   
    15.     void (*construct)(clss *self, DWORD addData);   
    16.     void (*destroy)(clss *self);   
    17.     DWORD (*hash)(clss *self);   
    18.     const char* (*toString)(clss *self, char* str, int max);  
    19.   
    20. struct _mObjectClass {  
    21.     mObjectClassHeader(mObject, mObject)  
    22. };  
    23.   
    24. extern mObjectClass g_stmObjectCls; //Class(mObject);  
    25.   
    26.   
    27. #define mObjectHeader(clss)   
    28.     ClassType(clss) * _class;  
    29.   
    30. struct _mObject {  
    31.     mObjectHeader(mObject)  
    32. };  


    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函数的实现

    [cpp] view plaincopy
     
    1. mObject * newObject(mObjectClass *_class)  
    2. {  
    3.     mObject * obj;  
    4.   
    5.     if(_class == NULL)  
    6.         return NULL;  
    7.       
    8.     obj = (mObject*)calloc(1, _class->objSize);  
    9.       
    10.     if(!obj)  
    11.         return NULL;  
    12.   
    13.     obj->_class = _class;  
    14.   
    15.     return obj;  
    16. }  
    17.   
    18. void deleteObject(mObject *obj)  
    19. {  
    20.     if(obj == NULL || obj->_class)  
    21.         return;  
    22.   
    23.     _c(obj)->destroy(obj);  
    24.       
    25.     free(obj);  
    26. }  
    27. ......  
    28. static inline mObject * ncsNewObject(mObjectClass *_class,DWORD add_data){  
    29.     mObject * obj = newObject(_class);  
    30.     if(!obj)  
    31. <span style="white-space:pre">      </span>return NULL;  
    32.   
    33.   
    34.     _class->construct(obj, add_data);  
    35.     return obj;  
    36. }  


    newObject负责对对象做最基本的初始化: 调用calloc分配空间,然后将_class赋给对象。而ncsNewObject函数,则调用了construct函数,完成对象的初始化。

    那么,g_stmObjectCls是如何声明和初始化的?请看代码

    [cpp] view plaincopy
     
    1. static void mObject_construct(mObject* self, DWORD addData)  
    2. {  
    3.     //do nothing  
    4.     //to avoid NULL pointer  
    5. }  
    6.   
    7. static void mObject_destroy(mObject* self)  
    8. {  
    9.   
    10. }  
    11.   
    12. static DWORD mObject_hash(mObject *self)  
    13. {  
    14.     return (DWORD)self;  
    15. }  
    16.   
    17. static const char* mObject_toString(mObject *self, char* str, int max)  
    18. {  
    19.     if(!str)  
    20.         return NULL;  
    21.   
    22.     snprintf(str, max, "NCS %s[@%p]", TYPENAME(self),self);  
    23.     return str;  
    24. }  
    25.   
    26. static mObjectClass* mObjectClassConstructor(mObjectClass* _class)  
    27. {  
    28.     _class->super = NULL;   
    29.     _class->typeName = "mObject";  
    30.     _class->objSize = sizeof(mObject);  
    31.   
    32.     CLASS_METHOD_MAP(mObject, construct)  
    33.     CLASS_METHOD_MAP(mObject, destroy)  
    34.     CLASS_METHOD_MAP(mObject, hash)  
    35.     CLASS_METHOD_MAP(mObject, toString)  
    36.     return _class;  
    37. }  
    38.   
    39. mObjectClass Class(mObject) = {  
    40.     (PClassConstructor)mObjectClassConstructor  
    41. };  


    CLASS_METHOD_MAP宏的定义是

    [cpp] view plaincopy
     
    1. #define CLASS_METHOD_MAP(clss, name)   
    2.         _class->name = (typeof(_class->name))(clss##_##name);  

    这里为了方便,要求统一的命名规范。

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

    • 给出类的名字
    • 给出对象的大小
    • 将虚函数表填充完整

    mObject类本身没有任何用处,他只是作为根类存在。我们必须定义其他类,才能起到作用。 那么,如果要实现继承,应该怎么办呢?

    还以Shape为例,基本上应该是这样

    [cpp] view plaincopy
     
    1. typedef struct _mShape mShape;  
    2. typedef struct _mShapeClass mShapeClass;  
    3. struct _mShape {   
    4.        mObject base;   
    5.        int x, y, width, height;  
    6. };  
    7. struct _mShapeClass {   
    8.        mObjectClass base;   
    9.        void (*draw)(mShape* self);  
    10. };  
    
    

    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调用,还必须记住继承的顺序和层次,这基本上是不可能的。

    这是,我们需要通过宏,来实现声明的"继承"

    [cpp] view plaincopy
     
    1. #define mShapeHeader(Cls)   
    2.      mObjectHeader(Cls)   
    3.      int x, y, width, height;  
    4.   
    5. struct  _mShape {  
    6.     mShapeHeader(mShape)  
    7. };  
    8.   
    9. #define mShapeClassHeader(Cls, Super)   
    10.     mObjectClassHeader(Cls, Super)   
    11.     void (*draw)(Cls* self)  
    12.   
    13. struct mShapeClass {  
    14.     mShapeClassHeader(mShape, mObject)  
    15. };  


    <ClassName>Header和<ClassName>ClassHeader宏很好的解决了这个问题。mObject的所有声明都将在mShape和mShapeClass中在声明一遍,而且,Class的名字,也从mObject替换为了mShape了。这样一来,当我们使用mShape类型的变量时,所有的虚函数都可以被直接调用,不需要任何的转换。

    mShape和mObject之间,仍旧保持了那种内存上的一致性。

    当mShape作为基类时,他的派生类可以使用mShapeHeader和mShapeClassHeader来生成新的类。

    下面,我们讨论下,mShapeClass的初始化问题。

    虚函数表虽然定义了结构,却没有定义变量,需要定义:

    [cpp] view plaincopy
     
    1. extern mShapeClass g_stmShapeCls;  


    然后,在再shape.c中,声明和填充g_stmShapeCls。

    g_stmShapeCls的实现和g_stmShapeCls是一样的,也需要定义一个classConstructor函数,然后在这个函数中初始化类的名字、mShape的大小以及draw函数指针的初始化。但是,这样写非常繁琐,因此,我们通过一个宏来定义

    [cpp] view plaincopy
     
    1.   #define BEGIN_MINI_CLASS(clss, superCls)   
    2. static ClassType(clss) * clss##ClassConstructor(ClassType(clss)* _class);   
    3. 2 ClassType(clss) Class(clss) = { (PClassConstructor)clss##ClassConstructor };   
    4. static const char* clss##_type_name = #clss;   
    5. static ClassType(clss) * clss##ClassConstructor(ClassType(clss)* _class) {   
    6. 5   _class = (ClassType(clss)*)((PClassConstructor)(Class(superCls).classConstructor))((mObjectClass*)_class);   
    7. 6   _class->super = &Class(superCls);   
    8. 7   _class->typeName = clss##_type_name;   
    9. 8   _class->objSize = sizeof(clss);  
    10.   
    11.   #define END_MINI_CLASS return _class; }  
    12.   
    13.   #define CLASS_METHOD_MAP(clss, name)   
    14.         _class->name = (typeof(_class->name))(clss##_##name);  


    我们把ClassConstructor函数的声明拆成了3部分:初始化定义、结束定义和方法填充。重点解释的是初始化定义:

    BEGIN_MINI_CLASS :

    • 行1: 前置声明ClassConstructor函数,使用类名以区分不同类的classConstructor函数
    • 行2: 声明了g_stmShapeCls变量,并将ClassConstructor赋值给它。这是非常重要的,如果没有这一步骤,那么,虚函数表就无法被初始化;
    • 行3:声明一个类的名字的字符串数组
    • 行4:定义了ClassConstructor函数的实现部分
    • 行5:首先调用超类的ClassConstructor,让超类先初始化一遍,这样如果子类不覆盖超类的函数,那么,我们将继续使用超类的函数,这是多态的“继承”特性
    • 行6:设置超类指针
    • 行7:设置类名
    • 行8:得到成员变量的大小
    使用的时候,非常简单
    [html] view plaincopy
     
    1. BEGIN_MINI_CLASS(mShape, mObject)  
    2.    CLASS_METHOD_MAP(mShape, draw)  
    3. END_MINI_CLASS  

    这样做不仅避免了大量字符输入,更重要的是:1)避免错误;2)避免开发者学习和记住这些通用性很强的内容。
     
     
    当然,这种情况下,类还是不能直接使用的,要使用,必须调用一次g_stmShapeCls.classConstructor类,真正完成类的初始化。为了简便,提供一个宏来简化这个过程:
    [cpp] view plaincopy
     
    1. #define MGNCS_WIDGET_REGISTER(className)   
    2.     Class(className).classConstructor((mObjectClass*)(void*)(&(Class(className))))  

    在初始化时
    [html] view plaincopy
     
    1. void init()  
    2. {  
    3.    ...  
    4.    MGNCS_WIDGET_REGISTER(mShape);  
    5.    ...  
    6. }  


    用C模拟类,还能够得到C++的RTTI的一些效果,例如,模拟java的instanceof关键字
    [html] view plaincopy
     
    1. BOOL ncsInstanceOf(mObject *object, mObjectClass* clss)  
    2. {  
    3.     mObjectClass* objClss;  
    4.     if(object == NULL || clss == NULL)  
    5.         return FALSE;  
    6.   
    7.     objClss = _c(object);  
    8.       
    9.     while(objClss && clss != objClss){  
    10.         objClss = objClss->super;  
    11.     }  
    12.   
    13.     return objClss != NULL;  
    14. }  
    15. #define INSTANCEOF(obj, clss)  ncsInstanceOf((mObject*)(obj), (mObjectClass*)(void*)(&Class(clss)))  
    我们可以直接去判断,如  INSTANCEOF(rectange, mShape)。这个消耗是很少的,因为,继承层次超过5层的已经非常少了,基本上,继承层次在5层以内就能做出足够的抽象。
  • 相关阅读:
    SpringBoot整合jsp
    SpringBoot常用application.properties配置
    SpringBoot入门
    vue cli创建vue项目
    vue 指令
    vue hello
    pytest doc
    atom
    java csvutil
    Django uuidfield 实现自动生成唯一列,并设置为主键
  • 原文地址:https://www.cnblogs.com/Bonker/p/3547675.html
Copyright © 2020-2023  润新知