OO是一种编程范型,而不只是特定语言的特定支持,所以用C来实现也是可行的。最近碰到的一部分代码都是用C实现的面向对象风格,可能是参考了Python里面的实现,Python内部实现的基本对象这块也全是这样的代码。在这里做一个小小的总结。
C语言里面没有语言层面的面向对象支持,那OO中的三个基本要素封装、继承、多态如何实现?C里面最强大的东西是指针,指针中最神奇的是void指针,这是一切的基本。首先来看封装,如何通过实例来调用方法,而对内部数据进行隐藏。完全可以写一些struct,然后写对应的函数来针对这个struct来操作,我们需要更进一步,把数据和方法绑定起来。这样写初看起来并没什么好处,后面会发现,通过函数指针去找对应的函数是多态的关键。
//object.h
typedef struct _obj{
char name[MAXLEN];
int ref_cnt;
int value;
void (*destructor) (void* thiz);
void (*print) (const void* thiz);
int (*equal) (const void* thiz, const void* other);
}Obj;
//object.c destruct,print,equal定义为static
Obj* Obj_new(int value)
{
Obj* o = malloc(sizeof(Obj));
strcpy(o->name,"baseobj");
o->ref_cnt = 1;
o->value = value;
o->destructor = &destruct;
o->print = &print;
o->equal = &equal;
assert(o);
return o;
}
//使用方法
{
Obj* first = Obj_new(1);
Obj* other = Obj_new(2);
first->print(first);
other->print(other);
assert(!first->equal(first,other));
Obj_drop(first);
Obj_drop(other);
return 0;
}
对于继承C当然也没原生的支持,可以在子类的定义中写入父类中的成员变量和成员函数,这里如果父类中定义的时候就是宏,直接拿过来就是。所以把父类的定义重新改写一下,分为DATA类型和TYPE类型,在Python里面就是这样,PyObject和PyVarObject是一切其他对象都包含有的。下面是一个例子People继承Object,Student继承People。
#define PEOPLE_DATA \
OBJ_DATA \
int age;\
char fullname[100];
//OBJ_DATA必须放在子类新的数据成员前面,只有这样才能把子类的指针强制转换成父类指针 或者转化为Object指针
#define PEOPLE_TYPE \
OBJ_TYPE \
void (*sleep)(void* thiz);
typedef struct _People_Type{
PEOPLE_TYPE
}People_Type;
extern const Object_Type Object_type;
extern const People_Type People_type;
typedef struct _People{
const People_Type* methods;
PEOPLE_DATA
}People;
这里sleep为新增加的子类方法,fullname为新增加的成员变量。注释部分为特别注意的,只有在保证内存的里面数据的分布前面部分都是一样的(一个methods指针和obj_data部分)才能进行指针之间的强制转换时候不出问题。例子里面的Student类也是类似的继承People类,这里可以看到sleep这个方法不好弄,因为在People那里申明为static了,这里想复用就麻烦,所以只有再自己写一个(即使实现是一样的),这也是C++内部帮用户做好的。可以看到通过type里面的函数指针的不同,不同对象相同的方法实现就不同了,因此实现了多态。
最后我们可以写一个基于计数的指针管理,在持有一个指针的时候调用Obj_pick,用完以后执行Obj_drop。
void Obj_pick(const void* thiz)
{
assert(thiz);
Object* o = (Object*)thiz;
o->ref_cnt++;
}
void Obj_drop(const void* thiz)
{
Object* o = (Object*)thiz;
const Object_Type* p;
if( --o->ref_cnt <= 0){
for( p = o->methods; p; p=p->father){
if(p->destructor)
p->destructor(o);
}
}
free(o);
}
按照这种OO的风格的C代码感觉要清晰一些,至少我习惯了。不过还是看个人品位吧,这样的代码风格是我另外一个同事所鄙视的。关于用C实现OO风格,还有一本比较好的书叫做
Object-oriented Programming with ANSI-C,感兴趣可以看看。
上面的代码在这里下载:
https://github.com/chenyukang/ooc。