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();
other->print();
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。