当对C与C++有一定的理解之后,很容易明白:C++是一门面向对象的语言,而C语言则是面向过程的语言。那什么是面向对象、什么是面向过程?其实面向对象与面向过程,只是一种将要模拟的事件抽象出来进行编程的思想。面向过程,主要关注的是事件如何发展;面向对象,主要关注事件中存在的事物以及如何操作这些事物来模拟事件。

       在我看来,经常所说的C++是一门面向对象的语言,指的是C++在语法上为面向对象编程提供了便利。C语言虽然没有定义面向对象的语法,但使用C语言也是可以实现面向对象的编程思想。

       首先通过如下的C++代码,可以很直观的看出C++中如何进行构造类与对象。构造基类fruit,apple类继承了fruit类。在产生对象ap时,可以通过打印的log,明白创建一个子对象时,同时也会创建一个父类对象。即,一个ap代表一个苹果,同时也代表一个水果(这也很否和我们对现实中一个苹果的理解)。

#include <iostream>
using namespace std;
class fruit {
public:
        fruit();
        ~fruit();
};
fruit::fruit()
{
        cout << "create  fruit" << endl;
}
fruit::~fruit()
{
        cout << "release furit" << endl;
}

class apple : public fruit {
public:
        apple();
        ~apple();
};
apple::apple()
{
        cout << "create  apple" << endl;
}
apple::~apple()
{
        cout << "release apple" << endl;
}

int main()
{
        apple *ap = new apple();
        delete(ap);
        return 0;
}

       那在C语言中如何实现类与对象呢?在C语言中的结构体可以与C++中的类很相似。但我之前一直没有想到一个很好的方法表示类之间的继承关系。原因是忽略了一个C语言中很重要的特性:指针。当我看到内核中的kobject,才恍然大悟,可以通过指针表示继承关系。因此可以通过如下方式实现fruit类和apple类。

       首先定义一个结构体struct object,每一个类包含一个object对象obj,用于表示其继承关系。若无父类则obj.parent指向NULL。并且我们可以通过类中的obj.parent指针,得到其父类指针(借用内核代码中的container_of实现)。

#include <stdio.h>
#include <malloc.h>

/*
 *linux/kernel.h
 *
 **/

#define offsetof(TYPE, MEMBER) \
            ((size_t) &((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) \
                ({const typeof(((type *)0)->member) *__ptr = (ptr);          \
                           (type *)((char *) __ptr - offsetof(type, member));})



/*
 *object用于表示类的父类
 *
 *parent指向父类,只支持单继承。
 *若需要多继承,可考虑在object中添加struct list_head,用于表示其父类们。
 *
 * */
struct object {
        struct object *parent;
};


struct fruit {
        const char *name;
        struct object obj;
};

struct fruit * create_fruit()
{
        struct fruit *f = (struct fruit *)malloc(sizeof(struct fruit));
        if (f != NULL) {
                f->name = "fruit";
                f->obj.parent = NULL;
        }

        return f;
}
void release_fruit(struct fruit *f)
{
        if (f != NULL) {
                free(f);
        }
}



struct apple {
        const char *name;
        struct object obj;

        int color;
        float weight;
};

struct apple * create_apple()
{
        struct fruit *f;
        struct apple *ap;
        f = create_fruit();
        if (f == NULL) {
                goto create_fruit_error;
        }

        ap = (struct apple *)malloc(sizeof(struct apple));
        if (ap == NULL) {
                goto create_apple_error;
        }
        
        ap->obj.parent = &(f->obj);
        ap->name = "apple";

        return ap;
create_apple_error:
        release_fruit(f);
create_fruit_error:
        return NULL;
}

void release_apple(struct apple *ap)
{
        struct fruit *f;
        if (ap == NULL) {
                return;
        }
        f = container_of(ap->obj.parent, struct fruit, obj);
        free(ap);
        release_fruit(f);
}


int main()
{
        struct apple *ap = create_apple();
        if (ap != NULL) {
                struct fruit *f = container_of(ap->obj.parent, struct fruit, obj);
                printf("ap is %s\n", ap->name);
                printf("ap is %s\n", f->name);
        }
        release_apple(ap);
        return 0;
}

       由于C语言没有定义面向对象的语法,因此我们可以通过任意的方式进行实现。当然也可以在struct apple中定义一些函数指针,类似C++中类的方法。

struct apple {
        const char *name;
        struct object obj;    
        int color;
        float weight;
        
        void (*set_apple_color)(struct apple *, int);
        int (*get_apple_color)(struct apple *);
        void (*set_apple_weight)(struct apple *, float);
        float (*get_apple_weight)(struct apple *); 
        
};

       当然本文只是阐述个人的一些观点,目的是为了更好的理解内核中如何实现设备与模块。在Linux内核中,kobject的设计就是面向对象的思想,我们可以在实际的编程中可以借鉴这种思想。另外,在我印象中,有一本描述用C语言实现面向对象的书籍,还有一本关于面向对象骗局的书籍(在大二时与松哥讨论C语言实现面向对象时,了解到的,但我暂时还没看过这两本书)。