• 纯C语言实现简单封装继承机制


    0 继承是OO设计的基础

    继承是OO设计中的基本部分,也是实现多态的基础,C++,C#,Objective-C。Java。PHP。JavaScript等为OO而设计的语言,其语言本身对实现继承提供了直接支持。而遵循C/Unix设计哲学的语言,从不限定编程风格。并且提供了实现OO的基本支持。以下我们就来看看怎样用C语言实现继承。

    1 内存布局层面上继承的含义

    现在差点儿全部程序猿都知道继承的抽象含义,对于被用烂了的猫狗继承动物的样例也耳熟能详。在此,我们抛开抽象世界,深入到继承的详细实现上。当然不同的语言对继承的实现机制并不全然同样,可是了解当中一种典型的实现细节对于理解继承是很有优点的。这里我们以C++为例进行说明。

    class B
    {
        int x;
        int y;
        int z;
    };
    class C : B
    {
        float f;
        char s[10];
    };

    上述代码表示子类C继承了父类B。以下是类C的一个实例(对象)的内存布局。


    这里写图片描写叙述

    C对象有两部分组成,红色区域是继承自B的部分,蓝色区域是自身特有的。这样一来,红色部分全然能够当成是一个B类对象。

    2 利用结构体实现继承的两种方法

    2.1 父类对象作为子类的成员

    理解了继承的内存布局原理之后,用C来实现继承就很easy了。最easy想到的方法例如以下:

    struct B
    {
        int x;
        int y;
        int z;
    };
    
    struct C
    {
        struct B objB;
        float f;
        char s[10];
    };

    上述代码通过在C中包括一个B类型的成员来实现继承,此方法很直接。但使用起来有一些不太方便。

        struct C objC;
        objC.objB.x = 10;
        ((struct B*)&objC)->x = 10;

    要想訪问父类的成员x,有两种方法。一种是objC.objB.x;还有一种是((struct B*)&objC)->x = 10。

    这两种方式都看起来不够直接。而在子类方法中訪问父类成员是很频繁的。

    void c_member_method(struct C* pObjC)
    {
        pObjC->objB.x = 20; /* 訪问父类成员 */
        pObjC->f = 0.23f; /* 訪问自身成员 */
    }

    第一种方法,感觉更像是OB风格。而不是OO。


    另外一种方法,必须进行强制类型转换,感觉语法上不够美观。

    2.2 子类包括全部的父类成员

    struct C
    {
        int x;
        int y;
        int z;
    
        float f;
        char s[10];
    };

    把全部的父类成员原样作为子类的成员。这样子类对象訪问继承来的成员就很直接了。

    void c_member_method(struct C* pObjC)
    {
        pObjC->x = 20; /* 訪问父类成员 */
        pObjC->f = 0.23f; /* 訪问自身成员 */
    }
    
    void main()
    {
        struct C objC;
        objC.x = 10;
    }

    看起来很好,实际上在project上会存在一个很大的问题:难以维护。比如,每当创建一个子类。必须原样书写全部的父类成员,当父类定义变动时,子类须要做出同样的改动。一旦父类稍具规模,维护这样的继承关系将是一场噩梦。

    那么怎样解决的?
    方法是现成的,那就是利用C语言的预处理宏定义#define. 例如以下所看到的:

    #define B_STRUCT 
        int x; 
        int y; 
        int z
    
    struct B
    {
        B_STRUCT;
    };
    
    struct C
    {
        B_STRUCT;
        float f;
        char s[10];
    };

    当继承层级更深时。比如 C继承B,D继承C,能够照搬此方法。

    #define B_STRUCT 
        int x; 
        int y; 
        int z
    
    struct B
    {
        B_STRUCT;
    };
    
    #define C_STRUCT 
        B_STRUCT; 
        float f; 
        char s[10]
    
    struct C
    {
        C_STRUCT;
    };
    
    #define D_STRUCT 
        C_STRUCT; 
        double d
    
    struct D
    {
        D_STRUCT;
    };

    通过宏定义。能够很easy实现和维护这样的继承关系。

    3 方法(成员函数)的封装与继承

    C语言中没有成员函数的概念,语言本身也不支持。

    使用C语言实现真正的成员函数差点儿是不可能的,除非嵌入汇编语言。与其使用汇编语言,还不如直接使用C++呢。

    所以,我们不追求形式上的成员函数,仅仅实现意义上的成员函数–(对给定类型对象进行操作)的函数,并使用带结构名前缀的函数名加以命名之。

    我们还是以上面的样例进行说明。

    
    typedef struct B B;
    
    static void b_member_function(B* pobjB) /* 类B的成员函数 */
    {
    
    }
    
    typedef struct C C;
    static void c_member_function(C* pobjC) /* 类C的成员函数 */
    {
    
    }

    对成员函数的调用,有两种情形:(1)外部代码调用成员函数;(2)子类成员函数中调用父类的成员函数;

    
    static void c_member_function(C* pobjC)
    {
        b_member_function((B*)pobjC); /* 子类成员函数内部调用父类成员函数 */
    }
    void main()
    {
        C* pObjC = malloc(sizeof(C));
        b_member_function((B*)pObjC);  /* 外部代码调用成员函数 */
    
        free(pObjC);
    }

    这两种情况都须要对实參进行强制类型转换为父类型。C编译器对类型继承关系一无所知,无法从语法上对继承进行自己主动支持,所以仅仅能手动强制类型转换了。

    有些人喜欢更进一步模拟成员函数。把全部成员函数的地址作为指针类型的成员变量存储到结构体内部。

    例如以下:

    #define B_STRUCT 
        int x; 
        int y; 
        int z; 
        void (*pb_member_function1)(B*); 
        void (*pb_member_function2)(B*, int arg)
    
    struct B
    {
        B_STRUCT;   
    };
    
    /* 初始化B对象的同一时候初始化 */
    B* b = malloc(sizeof(B));
    b->pb_member_function1 = b_member_function1;
    b->pb_member_function2 = b_member_function2;
    
    /* 调用 */
    b->b_member_function1(b);

    这样形式上更加接近“成员函数”。但同一时候也带来了额外的内存开销和代码量。

    为了减小内存消耗,有人提出不再在对象中全然存放全部成员函数指针,而是仅仅存放一个指向成员函数地址列表的指针。毕竟,同一类型的全部实例(对象)共享同样的一组成员函数。

    /* B类型的成员方法表 */
    const struct B_MethodTable
    {
        void (*pb_member_function1)(B*); 
        void (*pb_member_function2)(B*, int arg);
    }b_method_table{
        b_member_function1,
        b_member_function2,
    };
    
    #define B_STRUCT 
        int x; 
        int y; 
        int z; 
        struct B_MethodTable * pMethodTable;
    
    struct B
    {
        B_STRUCT;   
    };
    
    /* 初始化B对象的同一时候初始化 */
    B* b = malloc(sizeof(B));
    b->pMethodTable = &b_method_table;
    
    /* 调用 */
    b->pMethodTable->pb_member_function1(b);

    这样在一定程度上减小了内存占用量和代码量,可是队成员函数的调用写法却变得很繁琐不自然。

    4 做到何种程度?

    使用C语言做OO开发时,要掌握好一个度。不要过分追求对OO语言C++的模拟,全然模拟C++的话,还不如干脆直接使用C++。

    • 有些语言特性无法模拟,如C++中private,protected等訪问限定符。成员函数的this指针。更应该注重意义上的模拟。通过一些命名规则和约定来达到OO。

    • 永远不要违背C语言的设计哲学:程序猿控制一切,直接简明。

    这个度的把握须要依据详细的项目规模和需求。是实践中摸索出来的。无法给出理论上的最优值。

  • 相关阅读:
    分支与合并@基础
    创业者那些鲜为人知的事情
    centos7.5+nginx+php急速配置
    通过webhost扩展方式初始化EFCore数据库
    AspNetCore架构图
    Linux就该这样学--之常用linux命令及bash基础
    使用gitlab构建基于docker的持续集成(三)
    使用gitlab构建基于docker的持续集成(二)
    使用gitlab构建基于docker的持续集成(一)
    使用docker-compose配置mysql数据库并且初始化用户
  • 原文地址:https://www.cnblogs.com/gavanwanggw/p/7202170.html
Copyright © 2020-2023  润新知