C语言下的封装、继承与多态

本文探讨了如何使用C语言模拟实现面向对象编程的三大特性:封装、继承和多态。通过结构体嵌套、void *指针及可变参数函数展示了C语言在这些方面的灵活性。文章还提供了封装和继承的具体实现示例,并提到了大型开源项目如postgreSQL和GObject如何使用C语言实现面向对象特性。
摘要由CSDN通过智能技术生成

     上次课,钱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》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值