CatCoding

C 的面向对象风格代码

2011-08-16

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

公号同步更新,欢迎关注👻