当对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语言实现面向对象时,了解到的,但我暂时还没看过这两本书)。