上次课,钱SIR提到,Liux下面也有很多用C实现的面向对象的结构。比较感觉兴趣,就在网上查了一些资料,原来C语言模拟实现面向对象语言所具有的特性:多态,继承,封装,也是一件很简单的事儿。并且现在很多开源软件都了用C语言实现了这几个特性,包括大型开源数据库系统postgreSQL,可移植的C语言面向对象框架GObject。
在自己机器上实践了下,感叹C语言的灵活与强大!总结一下,以便交流:
一、基础知识
(1)结构体
结构体可以嵌套,因而可以把一个结构体当成另一个结构体的成员,如:
struct Point{
int x;
int y;
};
struct Circle {
struct Point point_;
int radius;
};
该结构体与以下定义完全一样(包括内存布置都一样
struct Circle {
int x;
int y;
int radius;
};
(2)void *
指针是整个 C 语言的精髓所在。而你也一直敬畏着指针,又爱又恨地使用着它。许多教材都告诉你,int *叫做指向整型的指针,而 char *是指向字符型的指针,等等等等不一而足。然而这里有一个另类的指针家族成员——void *。不要按照通常的命名方式叫它做指向void 类型的指针,它的正式的名字叫做:可以指向任意类型的指针。
(3)C中的参数个数可变函数
可变参数函数的原型声明:
type VAFunction(type arg1, type arg2, … );
参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明时用"..."表示。固定参数和可选参数公同构成一个函数的参数列表。
标准C/C++包含头文件stdarg.h,该头文件中定义了操作不定变量的相关宏:
void va_start ( va_list arg_ptr, prev_param ); /* ANSI version */
type va_arg ( va_list arg_ptr, type );
void va_end ( va_list arg_ptr );
在这些宏中,va就是variable argument(可变参数)的意思;
arg_ptr 是指向可变参数表的指针;
prev_param 指可变参数表的前一个固定参数;
type 为可变参数的类型。
va_list 也是一个宏,其定义为typedef char * va_list,实质上是一char型指针。
二、封装
封装的主要含义是隐藏内部的行为和信息,使用者只用看到对外提供的接口和公开的信息。
在C语言中的实现方法:把私有数据信息放在一个不透明的priv变量或者结构体中,只有类的实现代码才知道priv或者结构体的真正定义。
例如:
//========头文件:Point.h文件========
#ifndef POINT_H
#define POINT_H
typedef struct Point point;
typedef struct pointPrivate pointPrivate;
struct Point
{
struct pointPrivate *pp;};
int get_x(point *point_);
int get_y(point *point_);
point * new_point(int x,int y);
}
#endif
源文件
//=======C文件:Point.c文件========
#include "Point.h"
#include<stdlib.h>
struct pointPrivate;
int x;
int y;
};
int get_x(point *point_){
return point_->pp->x;
}
int get_y(point *point_){
return point_->pp->y;
}
point* new_point(int x,int y){
point* p=(point*)malloc(sizeof(point));
p->pp=(pointPrivate*)malloc(sizeof(pointPrivate));
p->pp->x=x;
p->pp->y=y;
return p;
}
测试文件:
int main()
{
point* p = new_point(1,2);
//printf("x:%d,y:%d\n",p->pp->x,p->pp->y);
printf("x:%d,y:%d\n",get_x(p),get_y(p));
}
在测试代码中,注释掉的一部分是编译不过的,因为我们已经把pointPrivate结构体的定义隐藏了。而且必须使用new_point来创建point结构对象,否则无法初始化point结构体中的pp成员变量。
有意思的是:这段代码生成的exe文件可能会被360误认为病毒。
三、继承
在C语言中,可以利用“结构在内存中的布局与结构的声明具有一致的顺序”这一事实实现继承。 比如我们要设计一个作图工具,其中可能涉及到的对象有Point(点),Circle(圆),由于圆是由点组成的,所有可以看成Circle继承自Point。另外,Point和Circle都需要空间申请,空间释放等操作,所有他们有共同的基类Base。
//基类Base的内部头文件Base.r,对外隐藏
#ifndef BASE_R
#define BASE_R
#include
struct Base {
size_t size;
void * (* ctor) (void * self, va_list * app);//构造函数
void * (* dtor) (void * self); //析构函数
void (* draw) (const void * self);//作图函数
};
#endif
//Point的内部头文件Point.r,对外隐藏
#ifndef POINT_R
#define POINT_R
struct Point {
const void * base; //继承Base类,基类指针,放在第一个位置,const是防止修改
int x, y; //坐标
};
#define x(p) (((const struct Point *)(p)) -> x)
#define y(p) (((const struct Point *)(p)) -> y)
#endif
//Point的头文件Point.h(对外提供接口)
#ifndef POINT_H
#define POINT_H
extern const void * Point; /* new(Point, x, y); */
void move (void * point, int dx, int dy);
#endif
//Point的源文件Point.c
#include
#include "Point.h"
#include "Point.r"
#include "new.h"
#include "Base.r"
/**********Point类自己的构造函数***********/
static void * Point_ctor (void * _self, va_list * app){
struct Point * self = _self;
self -> x = va_arg(* app, int);
self -> y = va_arg(* app, int);
return self;
}
/**********Point类自己的绘图函数***********/
static void Point_draw (const void * _self){
const struct Point * self = _self;
printf("Point at %d,%d\n", self -> x, self -> y);
}
static const struct Base _Point = {
sizeof(struct Point), Point_ctor, 0, Point_draw
};
const void * Point = & _Point;
void move (void * _self, int dx, int dy){
struct Point * self = _self;
self -> x += dx, self -> y += dy;
}
//Circle内部头文件Circle.r,对外隐藏
#ifndef CIRCLE_R
#define CIRCLE_R
#include "Point.r"
struct Circle {
const struct Point _; //继承Point类,需放在第一位
int rad;
};
#endif
//Circle的头文件Circle.h(对外提供接口)
#ifndef CIRCLE_H
#define CIRCLE_H
#include "Point.h"
extern const void * Circle; /* new(Circle, x, y, rad) */
#endif
//Circle的源文件Circle.c
#include
#include "Circle.h"
#include "Circle.r"
#include "new.h"
#include "Base.r"
/**********Circle类自己的构造函数***********/
static void * Circle_ctor (void * _self, va_list * app){
struct Circle * self = ((const struct Base *) Point) -> ctor(_self, app);
self -> rad = va_arg(* app, int);
return self;
}
/**********Circle类自己的绘图函数***********/
static void Circle_draw (const void * _self){
const struct Circle * self = _self;
printf("circle at %d,%d rad %d\n",x(self), y(self), self -> rad);
}
static const struct Base _Circle = {
sizeof(struct Circle), Circle_ctor, 0, Circle_draw
};
const void * Circle = & _Circle;
//内存管理类头文件new.h(对外提供接口)
#ifndef NEW_H
#define NEW_H
void * new (const void * base, ...);
void delete (void * item);
void draw (const void * self);
#endif
//内存管理类的源文件:new.c
#include
#include
#include
#include "Base.r"
void * new (const void * _class, ...){
const struct Base * base = _class;
void * p = calloc(1, base -> size);
assert(p);
* (const struct Base **) p = base;
if (base -> ctor){
va_list ap;
va_start(ap, _class);
p = base -> ctor(p, & ap);
va_end(ap);
}
return p;
}
void delete (void * self){
const struct Base ** cp = self;
if (self && * cp && (* cp) -> dtor)
self = (* cp) -> dtor(self);
free(self);
}
void draw (const void * self){
const struct Base * const * cp = self;
assert(self && * cp && (* cp) -> draw);
(* cp) -> draw(self);
}
四、多态可以是用C语言中的万能指针void* 实现多态,接上面的例子:
#include "Circle.h"
#include "new.h"
int main (int argc, char ** argv)
{
void * p;
int i;
for(i=0; i<2; i++)
{
if(i==0)
p = new(Circle, 1, 2, 3);
else
p = new(Point, 1, 2);
draw(p);
move(p, 10, 20);
draw(p);
delete(p);
}
return 0;
}
输出结果:
circle at 1,2 rad 3
circle at 11,22 rad 3
Point at 1,2
Point at 11,22
面向对象是一种程序设计思想,而 C 语言则是一种编程语言。也许它并不是专门为了面向对象编程而设计,但是这绝不意味着它不能实现面向对象的程序设计。当然以上所展示的这几个操作,如果是用别的编程语言,可能只要寥寥几行就可以完成,但是 C 语言想告诉我们的是:也许我不擅长,但是并不意味着我做不到。
参考书籍《Object-Oriented Programming With ANSI-C》