前言
学过C++的同学知道封装、继承和多态是C++的三个特性。其实这些特性并不是C++独有的,只是它特意用这些专有词汇来描述本身的语言特色。早在C++面世之前,其实C语言已经支持封装、单继承及多态(函数指针)。下面,我会分别分享这几个特性的C语言实现,这些知识来自于《架构整洁之道》的面向对象编程,这里是一个简约的总结,以及给出AWTK中的实际应用例子。
一、封装
封装指的是对数据和函数的封装。通过封装特性,我们可以把一组相关的数据和函数圈起来,使圈外面的代码只能看见部分函数,数据则完全不可见。例如,在实际应用中,类的公共函数是可见的,类的私有成员变量是不可见的。
以下是书上的一个C语言封装的例子:
point.h
struct Point;
struct Point* makePoint(double x, double y);
double distance(struct Point *p1, struct Point *p2);
point.c
struct Point {
double x;
double y;
};
struct Point* makepoint(double x, double y) {
struct Point* p = malloc(sizeof(struct Point));
p->x = x;
p->y = y;
return p;
}
double distance(struct Point* p1, struct Point* P2) {
double dx = p1->x - p2->x;
double dy = p1->y - p2->y;
return sqrt(dx*dx + dy*dy);
}
二、继承
继承的主要作用是让我们可以在某个作用域内对外部定义的一组变量与函数进行覆盖。在C++之前,C语言就已经这样用了。C语言的继承写法比较巧妙,这也是C++单继承实现的方法。
以下是书上的一个C语言单继承的一个例子:
namedPoint.h
struct NamedPoint;
struct NamedPoint* makeNamePoint(double x, double y, char* name);
void setName(struct NamedPoint* np, char* name);
char* getName(struct NamedPoint* np);
namedPoint.c
struct NamedPoint {
double x;
double y;
char* name;
};
struct NamedPoint* makeNamePoint(double x, double y, char* name) {
struct NamedPoint* p = malloc(sizeof(struct NamedPoint));
p->x = x;
p->y = y;
p->name = name;
return p;
}
void setName(struct NamedPoint* np, char* name) {
np->name = name;
}
char* getName(struct NamedPoint* np) {
return np->name;
}
main.c
int main(int ac, char** av) {
struct NamedPoint* origin = makeNamedPoint(0, 0, "origin");
struct NamedPoint* upperRight = makeNamedPoint(1, 1, "upperRight ");
distance((struct Point*)origin, (struct Point*)upperRight);
}
三、多态
同样的调用语句在实际运行时有多种不同的表现形态,这就是多态。在C++中,类的每一个虚函数的地址都被记录在一个虚函数表(vtable)中,类的构造函数负责把具体的函数地址填到这个虚函数表中,我们对虚函数的每一次调用都要先查询这个表,然后调用响应的函数。归根结底,多态只不过是函数指针的一种应用。
用函数指针显式实现多态的问题在于函数指针的危险性。毕竟,函数指针的调用依赖于一系列需要人为的约定。程序员必须严格按照固定约定来初始化函数指针,并同样严格的按照约定来调用这些指针。只要有一个程序员没有遵守这些约定,整个程序就会产生极其难以跟踪和消除的bug.
面向对象编程语言为我们消除了人工遵守这些约定的必要,也就等于消除了这方面的危险性。采用面向对象编程语言让多态实现变得非常简单。面向对象编程其实是对程序间接控制权的转移进行了约束。
widget.h
struct _widget_t {
xy_t x;
xy_t y;
wh_t w;
wh_t h;
.......
/**
* 虚函数表。
*/
const widget_vtable_t* vt;
/*private*/
assets_manager_t* assets_manager;
};
虚函数表定义:
struct _widget_vtable_t {
...
widget_create_t create;
widget_get_prop_t get_prop;
widget_set_prop_t set_prop;
....
};
window子类在创建对象的时候初始化虚函数表:
static ret_t window_set_prop(widget_t* widget, const char* name, const value_t* v) {
if (tk_str_eq(name, WIDGET_PROP_FULLSCREEN)) {
window_set_fullscreen(widget, value_bool(v));
return RET_OK;
}
return window_base_set_prop(widget, name, v);
}
static ret_t window_get_prop(widget_t* widget, const char* name, value_t* v) {
window_t* window = WINDOW(widget);
return_value_if_fail(window != NULL, RET_BAD_PARAMS);
if (tk_str_eq(name, WIDGET_PROP_FULLSCREEN)) {
value_set_bool(v, window->fullscreen);
return RET_OK;
}
return window_base_get_prop(widget, name, v);
}
TK_DECL_VTABLE(window) = {.......
.create = window_create,
.set_prop = window_set_prop,
.get_prop = window_get_prop,
.......};
widget_t* window_create(widget_t* parent, xy_t x, xy_t y, wh_t w, wh_t h) {
return window_base_create(parent, TK_REF_VTABLE(window), x, y, w, h);
}
main.c
int main(int ac, char** av) {
widget* window = window_create(NULL, 0, 0, 10, 10);
window->vt->set_prop(window, WIDGET_PROP_FULLSCREEN, TRUE);
}
我们直接通过widget指针调用set_prop函数时,由于这个对象的虚函数表中存的函数指针是window类的set_prop指针,所以,执行这个函数调用的时候,将会执行window类的set_prop函数,这就是虚函数的实现原理,本质是函数指针的应用。