最近我真烦之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的代码,你会看到更多的宏代码。你也可以通过自己的思考,写出更好的宏。

你可能感兴趣的:(OO)