所谓类:是对特定数据的特定操作的集合体。所以说类包含了两个范畴:数据和操作。而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
#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;
}