zoukankan      html  css  js  c++  java
  • 最近我真烦之OOC!

    Vrix随笔之第七回合--面向对象的C!(C语言高级技巧)
    http://asp.7i24.com/ecrazyc/vrixpworld/file/vnote7.htm
          (04.13.2004)早在去年年底,我就考虑使用seal一样的体系{我指ooc的编程模式}。接下来,我将我的粗浅认识写将出来,以便大家在对seal进行分析的时候可以考虑以下。因为我发现这方面的资料实在太少。
          先来看以下代码:
          typedef struct jobj{int class_id;}Jobj;
          /*
          我也想过加入更多的元素在基类,可是那是空间的浪费。也许初学C的朋友会纳闷儿,明明是结构体,怎么叫类呢?往下看吧,你会知道的。
    透露一个小知识给大家,结构和类在本质上没有区别。一个整形变量,大家知道应当占用sizeof(int)个字节。那么Jobj这个结构占用多大的内存的,答案也是sizeof(int)个字节,这当然是明摆着的事情,但是当:
          Jobj obj,*pobj;
          int *pint;
          obj.class_id=10;
          pobj=&obj;//以上天经地义的操作
          pint=(int *)pobj;//注意这里的赋值操作{可能需要一个void*指针做中介}
          *pint=0;//这会导致什么呢??就是class_id=0;
          这一切的发生并不是偶然,因为所有指针都占用同等大小的空间。没有int指针会比char指针大这种事情发生,但是相对于不同的指针,它所操作的范围确实指定的。它只能访问原始类型所定义的空间。那就是说pobj只能在sizeof(Jobj)范围内瞎搞。
          以上的问题可以被更广泛的应用在结构之间。
          */
          typedef struct test
          {
             Jobj parent;//基类成员
             int m_var1;//派生类成员变量
          }Jtest;
          Jtest testobj,*ptest=&testobj;
          Jobj *pobj=(Jobj *)ptest;
          //现在看看pobj吧,它现在指向了一个派生类实例。
          pobj->class_id=20;//现在变的很顺应天理了。
          /*
          但这pobj指向的是Jtest类的对象呀,pobj->m_var1=20      ;这个操作可以吗?答案是不可以。原因其实不复杂,&我说过了,指针只能访问原始类型大小的空间。
          理论上来说,派生类总是比基类占用更多的空间,也就是说它总比基类大。但当一个基类指针指向这个派生类实例时,这个实例将被切割,如果用面向对象的语言来描述,就是object slicing.一个大小为sizeof(int)*2的Jtest类对象被切开。用pobj只能访问前面的部分。也就是我们写在派生类开始的Jobj类的实例。因为pobj指向的正是那个Jobj元素。
          另外我将为这个类构造一个宏来处理基类指针,即:#define JTEST(o) (Jtest *)o
          我们也可以定义:#define new(o) (o *)malloc(sizeof(o))这个宏来在C语言里使用new这个本该在C++里出现的东西。当然与它相对的是#define delete(o) free(o) 这个宏。但是我们用它是要给指针用。所以定义一个Jobj类的实例就可以写成 Jobj *obj=new(Jobj);这样不是很好吗,看起来有点C++的意思。如果我们有typedef Jobj* PJobj 那么我们可以写出PJobj obj=new(Jobj)这样的句子,如果宏new被定义成#define new(o) (o)malloc(sizeof(o)) 那么PJobj obj=new(PJobj);这样的句子也是可以的。总之,只要你愿意,C语言是可以这么做的。
          */
          以上的特性也许有些人熟悉之后,并不明白它将用在哪里。那么我就借下面的例子来让大家知道它的实用性。{如果大家对gui感兴趣的话,这将是必须被理解的东西。因为在设计framework时,大量的此类技术被应用。}
          在现实生活中,我门会遇到不同形状的物体,有长方形,椭圆形,正方形,正圆型等等。加入每个形状都可以被display,我们暂且让display来显示它本身的名字吧。那么当我们有多个不同形状的物体时,我们如何能用如下代码来方便的实现对每个物体的display();
          物体数组[物体个数];
          while(物体个数--) 物体数组[物体个数].display();
          那怎么可能呢?除非数组可以放不同类型的元素。这种数组确实是有的,但那是在perl语言里的哈西数组。C语言没有这样的数组。但是有了上面的我提到的继承特性。我们可以实现我们想要的。由于每个物体都有自己的形状,我们可以让所有的物体都从形状类派生。然后用形状类的指针就可以得到指向不同物体的指针。当然我们必须知道那是那个类。那么基类的class_id就派上了用场。
          看以下的代码,你可以得到一些启发:
          void jshape_display(){printf("Jshape\n");}
          #define Jobj Jshape
          typedef rect
          {
          Jshape parent;
          void (*display)();
          }Jrect;
          void jrect_display(){printf("Jrectangle\n");}
          typedef circle
          {
          Jshape parent;
          void (*display)();
          }Jcirlce;
          void jcircle_display(){printf("Jcircle\n");}
          void jshape_display(Jshape *pshape)
          {
          switch(pshape->class_id)
          {
          case 1:
           (Jrect *)pshape->display();
          case 2:
           (Jcircle *)pshape->dispaly();
          }
          }
          int main()
          {
          Jrect rect,*prect=▭
          Jcircle circle,*pcircle=&circle;
          prect->display=jrect_display;
          pcirlce->display=jcircle_display;
          Jshape *pshape[2];
          pshape[0]=(Jshape *)prect;
          pshape[0]->class_id=1;
          pshape[1]=(Jshape *)pcircle;
          pshape[1]->class_id=2;
          //终于可以用数组了
          int i=2;
          while(i--)
          {jshape_display(pshape[i-1]);}
          return 0;
          }//end of main
          终于整个世界清净了。你看到这个继承的妙用了吧。什么?{读者:用C++也可以实现}我当然知道用C++的类实现这些是多么的容易。但是,你考虑过一个想看懂seal的人该如何吗,不是要得到有人用C++把seal重写吧。
          其实C++和C是一样的。C++ 能写的东西,C也能。
          回到GUI的framework来吧。以上的技术在GUI中的应用将更加普遍。因为所有的控件都连接在一起。一个MEMO控件和个TOOLBAR甚至是一个BUTTON控件,是如何能被串到一个树中的呢。{关于GUI的体系结构之2叉树理论,请参看我的其他文章}。对了,一个基类指针构成的树,当然可以指向不同的控件,只要不同的控件都派生自基类即可。
          但是,大家应当注意到一个问题,那就是在以上的描述中提到的是结构的单继承。它要求派生类必须有一个基类的变量出现在派生类的开头,否则就不好办了,得到的是什么也无法确定。除非你自己知道该如何让指针正确的工作。
          {2004.03.21 13:35}以下将是对多继承的描述,以及一些巨集的妙用,我将把从MFC里学来的伎俩展示给大家。{我想大家该知道MFC是什么吧}
          首先来说说多重继承:{基于上面的部分代码}
          当你有以下类:
          struct multiderived
          {
             Jrect parenta;//基类成员
             Jcircle parentb;//基类成员
             int m_var1;//派生类成员变量
          }Jmultiderived;
          Jmultiderived jmd_obj,*pjmd_test=&jmd_obj;
          Jrect *p_jrect=(Jrect *)pjmd_test;
          //以上这一句给大家带来的自然是切割好的基类的实例{参看前边的object slicing}。
          Jcircle *p_jcircle=(Jcircle *)(p_jrect+1);
          /*
          p_jrect+1的效果就是从对象jmd_obj的开始处跳过第一个Jrect类的实例,以便得到第二个基类的开始地址。然后把这个地址强制转换成相应类型就可以使用了。这只是个简单的理解过程而已,如此一来,大家可以构造多于2个基类的派生类。当然,如果你可以有足够的脑力来记忆他们之间的派生关系,那就是最好的。否则,请只使用单继承。而单继承,我们可以构造一个宏来处理。方法见本文单继承论点。
          */
          接下来我们说说宏这个东西。前面我们在讨论单继承的时候,已经用到了很巧妙的new和delete宏。但是那仍然是不够的。
          (2004.05.30 8:50am)通常情况下,宏用来封装我们的语法最合适,也就是构造小型的伪语言。这种事情,就如同我们提到的new和delete。不过做的要更多。看看下面的实例:
    #define CLASS(derived,base) struct derived { struct base instance;
    #define PUBLIC }; #if 0
    #define END #endif 
    #define CMF(ret,class,method,para) ret class_method para
    
    CLASS(jmyclass,jobj)
        int index;
        char name[10];
    PUBLIC
        void  jmyclass();
        void  _jmyclass();
        void  set_index(int value);
        int   get_index();
        void  set_name(char *value);
        char* get_name();
    END
    
    CMF(void,jmyclass,(jmyclass *this))
    {
        this->index=0;
        strcpy(this->name,"");
    }
    CMF(void,_jmyclass,(jmyclass *this))
    {
        this->index=0;
        strcpy(this->name,"");
    }
    CMF(void,set_index,(jmyclass *this,int value))
    {
        this->index=value;
    }
    CMF(int,get_index,(jmyclass *this))
    {
        return this->index;
    }
    CMF(void,set_name,(jmyclass *this,char* value))
    {
        strcpy(this->name,value);
    }
    CMF(char*,get_name,(jmyclass *this))
    {
        return this->name;
    }
    
          以上给出的只是简单的C语言的例子,这种做法在很多的地方都得到普遍的应用,如果你看过MFC的代码,你会看到更多的宏代码。你也可以通过自己的思考,写出更好的宏。
  • 相关阅读:
    Oracle分析函数大全
    Docker容器与容器云之Docker单机集群部署案例
    hive中的几个参数:元数据配置、仓库位置、打印表字段相关参数
    启用hive hwi方法
    hive进行词频统计
    Docker在centos上的安装
    Hive日志(Hive Logging)--hive GettingStarted翻译
    【RMAN】RMAN-05001: auxiliary filename conflicts with the target database
    简单示例用例(Simple Example Use Cases)--hive GettingStarted用例翻译
    hive分析nginx日志之UDF清洗数据
  • 原文地址:https://www.cnblogs.com/huqingyu/p/118129.html
Copyright © 2011-2022 走看看