• [转]C语言中的类模拟(C++编程思想)


    所谓类:是对特定数据的特定操作的集合体。所以说类包含了两个范畴:数据和操作。而C语言中的struct仅仅是数据的集合。
    1.实例:下面先从一个小例子看起
    #ifndef C_Class
    #define C_Class struct
    #endif
    C_Class A {
    C_Class A *A_this;
    void (*Foo)(C_Class A *A_this);
    int a;
    int b;
    };
    C_Class B{ //B继承了A
    C_Class B *B_this; //顺序很重要
    void (*Foo)(C_Class B *Bthis); //虚函数
    int a;
    int b;
    int c;
    };
    void B_F2(C_Class B *Bthis)
    {
    printf("It is B_Fun\n");
    }
    void A_Foo(C_Class A *Athis)
    {
    printf("It is A.a=%d\n",Athis->a);//或者这里
    // exit(1);
    // printf("纯虚 不允许执行\n");//或者这里
    }
    void B_Foo(C_Class B *Bthis)
    {
    printf("It is B.c=%d\n",Bthis->c);
    }
    void A_Creat(struct A* p)
    {
    p->Foo=A_Foo;
    p->a=1;
    p->b=2;
    p->A_this=p;
    }
    void B_Creat(struct B* p)
    {
    p->Foo=B_Foo;
    p->a=11;
    p->b=12;
    p->c=13;
    p->B_this=p;
    }
    int main(int argc, char* argv[])
    {
    C_Class A *ma,a;
    C_Class B *mb,b;
    A_Creat(&a);//实例化
    B_Creat(&b);
    mb=&b;
    ma=&a;
    ma=(C_Class A*)mb;//引入多态指针
    printf("%d\n",ma->a);//可惜的就是 函数变量没有private
    ma->Foo(ma);//多态
    a.Foo(&a);//不是多态了
    B_F2(&b);//成员函数,因为效率问题不使用函数指针
    return 0;
    }
    输出结果:
    11
    It is B.c=13
    It is A.a=1
    It is B_Fun
    2.类模拟解说:
    我在网上看见过一篇文章讲述了类似的思想(据说C++编程思想上有更加详细的解说,
    可惜我没空看这个了,如果有知道的人说一说吧)。但是就象C++之父说的:“C++和C是两种
    语言”。所以不要被他们在语法上的类似就混淆使用,那样有可能会导致一些不可预料的事情
    发生。
    其实我很同意这样的观点,本文的目的也不是想用C模拟C++,用一个语言去模拟另外
    一个语言是完全没有意义的。我的目的是想解决C语言中,整体框架结构过于分散、以及数据
    和函数脱节的问题。
    C语言的一大问题是结构松散,虽然现在好的大型程序都基本上按照一个功能一个文件的
    设计方式,但是无法做到更小的颗粒化――原因就在于它的数据和函数的脱节。类和普通的
    函数集合的最大区别就在于这里。类可以实例化,这样相同的函数就可以对应不同的实例化
    类的变量。
    自然语言的一个特点是概括:比如说表。可以说手表,钟表,秒表等等,这样的描述用
    面向对象的语言可以说是抽象(继承和多态)。但是我们更要注意到,即使对应于手表这个
    种类,还是有表链的长度,表盘的颜色等等细节属性,这样细微的属性如果还用抽象,就无
    法避免类膨胀的问题。所以说类用成员变量来描述这样的属性。这样实例并初始化不同的
    类,就描述了不同属性的对象。
    但是在C语言中,这样做是不可能的(至少语言本身不提供这样的功能)。C语言中,如
    果各个函数要共享一个变量,必须使用全局变量(一个文件内)。但是全局变量不能再次实
    例化了。所以通常的办法是定义一个数组。以往C语言在处理这样的问题的时候通常的办法就
    是这样,比如说socket的号,handel等等其实都是数组的下标。(不同的连接对应不同的
    号,不同的窗口对应不同的handel,其实这和不同的类有不同的成员变量是一个意思)
    个人认为:两种形式(数组和模拟类)并无本质的区别(如果不考虑虚函数的应用的
    话),它们的唯一区别是:数组的办法将空间申请放在了“模块”内,而类模拟的办法将空间
    申请留给了外部,可以说就这一点上,类模拟更加灵活。
    3.其他的话:
    我的上述思想还是很不成熟的,我的目的是想让C语言编程者能够享受面向对象编程的更
    多乐趣。我们仅仅面对的是浩瀚的“黑箱”,我们的工作是堆砌代码,而且如果要更改代码功
    能的时候,仅仅换一个黑箱就可以了。
    而更大的目的是促使这样的黑箱的产生。或许有一天,一种效率很好,结构很好的语言
    将会出现。那个时候编程是不是就会象说话一样容易了呢?
    相信很多人都看过设计模式方面的书,大家有什么体会呢?Bridge,Proxy,Factory这些设
    计模式都是基于抽象类的。使用抽象对象是这里的一个核心。
    其实我觉得框架化编程的一个核心问题是抽象,用抽象的对象构建程序的主体框架,这
    是面向对象编程的普遍思想。用抽象构建骨架,再加上多态就形成了一个完整的程序。由于C
    ++语言本身实现了继承和多态,使用这样的编程理念(理念啥意思?跟个风,嘿嘿)在C+
    +中是十分普遍的现象,可以说Virtual(多态)是VC的灵魂。
    但是,使用C语言的我们都快把这个多态忘光光了。我常听见前辈说,类?多态?我们用
    的是C,把这些忘了吧。很不幸的是,我是一个固执的人。这么好的东西,为啥不用呢。很高
    兴的,在最近的一些纯C代码中,我看见了C中的多态!下面且听我慢慢道来。
    1. VC中的Interface是什么
    Interface:中文解释是接口,其实它表示的是一个纯虚类。不过我所要说的是,在VC中
    的Interface其实就是struct,查找Interface的定义,你可以发现有这样的宏定义:
    #Ifndef Interface
    #define Interface struct
    #endif
    而且,实际上在VC中,如果一个类有Virtual的函数,则类里面会有vtable,它实际上是一个
    虚函数列表。实际上C++是从C发展而来的,它不过是在语言级别上支持了很多新功能,在C
    语言中,我们也可以使用这样的功能,前提是我们不得不自己实现。
    2.C中如何实现纯虚类(我称它为纯虚结构)
    比较前面,相信大家已经豁然开朗了。使用struct组合函数指针就可以实现纯虚类。
    例子: typedef struct {
    void (*Foo1)();
    char (*Foo2)();
    char* (*Foo3)(char* st);
    }MyVirtualInterface;
    这样假设我们在主体框架中要使用桥模式。(我们的主类是DoMyAct,接口具体实现类是
    Act1,Act2)下面我将依次介绍这些“类”。(C中的“类”在前面有说明,这里换了一个,是使
    用早期的数组的办法)
    主类DoMyAct: 主类中含有MyVirtualInterface* m_pInterface; 主类有下函数:
    DoMyAct_SetInterface(MyVirtualInterface* pInterface)
    {
    m_pInterface= pInterface;
    }
    DoMyAct_Do()
    {
    if(m_pInterface==NULL) return;
    m_pInterface->Foo1();
    c=m_pInterface->Foo2();
    }
    子类Act1:实现虚结构,含有MyVirtualInterface st[MAX]; 有以下函数:
    MyVirtualInterface* Act1_CreatInterface()
    {
    index=FindValid() //对象池或者使用Malloc !应该留在外面申请,实
    例化
    if(index==-1) return NULL;
    St[index].Foo1=Act1_Foo1; // Act1_Foo1要在下面具体实现
    St[index].Foo2=Act1_Foo2;
    St[index].Foo3=Act1_Foo3;
    Return &st [index];
    }
    子类Act2同上。
    在main中,假设有一个对象List。List中存贮的是MyVirtualInterface指针,则有:
    if( (p= Act1_CreatInterface()) != NULL)
    List_AddObject(&List, p); //Add All
    While(p=List_GetObject()){
    DoMyAct_SetInterface(p);//使用Interface代替了原来大篇幅的Switch Case
    DoMyAct_Do();//不要理会具体的什么样的动作,just do it
    }
    FREE ALL。
    在微系统里面,比如嵌入式,通常使用对象池的技术,这个时候可以不用考虑释放的问
    题(对象池预先没有空间,使用Attach,在某个函数中申请一个数组并临时为对象池分配空
    间,这样函数结束,对象池就释放了)
    但是在Pc环境下,由于程序规模比较大,更重要的是一些特殊的要求,使得对象的生命
    周期必须延续到申请的那个函数体以外,就不得不使用malloc,实际上即使在C++中,new
    对象的自动释放始终是一个令人头疼的问题,新的标准引入了智能指针。但是就我个人而
    言,我觉得将内存释放的问题完全的交给机器是不可信任的,它只能达到准最佳。
    你知道设计Java的垃圾回收算法有多困难吗?现实世界是错综复杂的,在没有先验条件
    下,要想得到精确的结果及其困难。所以我说程序员要时刻将free记在心上,有关程序的健
    壮性和自我防御将在另外一篇文章中讲述。
    3.纯虚结构的退化
    下面我们来看看如果struct里面仅仅有一个函数是什么? 这个时候如果我们不使用
    struct,仅仅使用函数指针又是什么? 我们发现,这样就退化为普通的函数指针的使用
    了。
    所以说,有的时候我觉得面向对象仅仅是一种形式,而不是一种技术。是一种观点,而
    不是一种算法。但是,正如炭,石墨和钻石的关系一样,虽然分子式都是C,但是组成方法不
    一样,表现就完全不一样了!
    有的时候,我们经常被编程中琐碎的事情所烦恼,而偏离了重心,其实程序可进化的特
    性是很重要的。有可能,第一次是不成功的,但是只要可进化,就可以发展。
    4.进阶――类结构树,父类不是纯虚类的类
    前面仅仅讲的是父类是纯虚结构的情况 (面向对象建议的是所有类的基类都是从纯虚类
    开始的), 那么当类层次比较多的情况下,出现父类不是纯虚结构怎么办呢。嘿嘿,其实在C
    中的实现比C++要简单多了。因为C中各个函数是分散的。
    在这里使用宏定义是一个很好的办法:比如两个类Act1,ActByOther1“继承”Act1:
    MyVirtualInterface* ActByOther1_CreatInterface()
    {
    index=FindValid() //对象池或者使用Malloc
    if(index==-1) return NULL;
    St[index].Foo1= ActByOther1_Foo1; // Act1_Foo1要在下面具体实现
    St[index].Foo2= ActByOther1_Foo2;
    St[index].Foo3= ActByOther1_Foo3;
    Return &st [index];
    }
    #define ActByOther1_Foo1 Act1_Foo1 //这就是继承 嘿嘿
    ActByOther1_Foo2(){} // 可以修改其实现
    ActByOther1_DoByOther() {} //当然就可以添加新的实现咯

    5.实例――可以参见H264的源码,其中NalTool就是这样的一个纯虚结构。
    类模拟中使用了大量的函数指针,结构体等等,有必须对此进行性能分析,以便观察这样的
    结构对程序的整体性能有什么程度的影响。
    1.函数调用的开销
    #define COUNTER XX
    void testfunc()
    {
    int i,k=0;
    for(i=0;i<YY;i++){k++;}
    }
    在测试程序里面,我们使用的是一个测试函数,函数体内部可以通过改变YY的值来改变
    函数的耗时。测试对比是 循环调用XX次函数,和循环XX次函数内部的YY循环。
    结果发现,在YY足够小,X足够大的情况下,函数调用耗时成为了主要原因。所以当一个
    “简单”功能需要“反复”调用的时候,将它编写为函数将会对性能有影响。这个时候可以使用
    宏,或者inline关键字。
    但是,实际上我设置XX=10000000(1千万)的时候,才出现ms级别的耗时,对于非实时
    操作(UI等等),即使是很慢的cpu(嵌入式10M级别的),也只会在XX=10万的时候出现短
    暂的函数调用耗时,所以实际上这个是可以忽略的。
    2.普通函数调用和函数指针调用的开销
    void (*tf)();
    tf=testfunc;
    测试程序修改为一个使用函数调用,一个使用函数指针调用。测试发现对时间基本没有
    什么影响。(在第一次编写的时候,发现在函数调用出现耗时的情况下(XX=1亿),函数指
    针的调用要慢(release版本),调用耗时350:500。后来才发现这个影响是由于将变量申请
    为全局的原因,全局变量的访问要比局部变量慢很多)。
    3.函数指针和指针结构访问的开销
    struct a {
    void (*tf)();
    };
    测试程序修改为使用结构的函数指针,测试发现对时间基本没有什么影响。其实使用结
    构并不会产生影响,因为结构的访问是固定偏移量的。所以结构变量的访问和普通变量的访
    问对于机器码来说是一样的。
    测试结论:使用类模拟的办法对性能不会产生太大的影响。

    #include 
    #include 
    #define VIRTUAL
    struct vtable /*虚函数表*/

        
    int (*p_geti)(void *); /*虚函数指针*/ 
        
    void (*p_print)(); /*虚函数指针*/
    }
    ;

    struct shape /*类shape*/
    {
        
    void *vptr; /*虚表指针 - 指向vtable*/ 
        
    int i;
    }
    ;

    struct circle /*类circle*/

        
    void *vptr; /*虚表指针 - 指向vtable*/ 
        
    int i;
    }
    ;
    struct rectangle /*类rectangle*/

        
    void *vptr; /*虚表指针 - 指向vtable*/ 
        
    int i;
    }
    ;
    //------------------------------------
    //print() - 虚函数 
    /*真正调用的函数,在其内部实现调用的多态*/
    VIRTUAL 
    void print(void *self) /*参数是对象指针*/

        
    const struct vtable * const *cp = self; 
        (
    *cp)->p_print();
    }

    void shape_print()

        printf(
    "this is a shape!\n");
    }

    void circle_print()
        printf(
    "this is a circle!\n");
    }

    void rectangle_print() 

        printf(
    "this is a rectangle!\n");
    }

    //------------------------------------------------------
    //geti() - 虚函数 
    VIRTUAL int geti(void *self)

        
    const struct vtable * const *cp = self; 
        
    return (*cp)->p_geti(self); /*这一行出问题*/
    }

    int shape_geti(struct shape *self) /*具体函数实现时,参数还要是其类型指针*/

        
    return self->i;
    }

    int circle_geti(struct circle *self) /*具体函数实现时,参数还要是其类型指针*/
    {
        
    return self->i;
    }

    int rectangle_geti(struct rectangle *self) /*具体函数实现时,参数还要是其类型指针*/

        
    return self->i;
    }

    int main(int argc, char *argv[])

        
    struct shape _shape; /*shape的对象_shape*/ 
        
    struct circle _circle; /*circle的对象_circle*/ 
        
    struct rectangle _rectangle; /*rectangle的对象_rect*/ /*声名虚表*/ 
        
    struct vtable shape_vtable; /*shape对象的vtable*/ 
        
    struct vtable circle_vtable; /*circle对象的vtable*/ 
        
    struct vtable rectangle_vtable; /*rectangle的虚表*/ /*给类分配虚表*/ 
        _shape.vptr 
    = &shape_vtable; /*将虚表挂上*/ 
        _circle.vptr 
    = &circle_vtable; /*将虚表挂上*/
        _rectangle.vptr 
    = &rectangle_vtable; /*将虚表挂上*/ 
        
    /*给虚表对应相应的函数*/ 
        shape_vtable.p_print 
    = shape_print;     /*赋值相应的函数*/ 
        circle_vtable.p_print 
    = circle_print; /*赋值相应的函数*/ 
        rectangle_vtable.p_print 
    = rectangle_print; /*赋值相应的函数*/ 
        
    /*给虚表对应相应的函数*/ 
        shape_vtable.p_geti 
    = shape_geti;
        circle_vtable.p_geti 
    = circle_geti; 
        rectangle_vtable.p_geti 
    = rectangle_geti; /*动态联编实现多态*/ 
        
    /*因类型的不同而作出不同的反映*/ 
        print(
    &_shape); print(&_circle); 
        print(
    &_rectangle); 
        _shape.i 
    = 5
        _circle.i 
    = 19
        _rectangle.i 
    = 1
        
    /*动态联编实现多态*/ 
        
    /*因类型的不同而作出不同的反映*/ 
        printf(
    "_shape's i is : %d\n", geti(&_shape)); 
        printf(
    "_circle's i is : %d\n", geti(&_circle)); 
        printf(
    "_rectangle's i is : %d\n", geti(&_rectangle)); 
        system(
    "PAUSE");
        
    return 0;
    }
  • 相关阅读:
    Github开始强制使用PAT(Personal Access Token)了
    STM32F401的外部中断EXTI
    STM32F401的PWM输出
    STM32F103和STM32F401的ADC多通道采集DMA输出
    nRF24L01无线模块笔记
    51单片机(STC89C52)在Ubuntu下的开发
    51单片机(STC89C52)的中断和定时器
    51单片机的软件和硬件PCA/PWM输出
    Zadig 云原生持续交付 面向开发者设计的开源、高可用 CI/CD
    人生 乐观 悲观 英雄
  • 原文地址:https://www.cnblogs.com/WuCountry/p/907018.html
Copyright © 2020-2023  润新知