C語言下的封裝、繼承與多態

     上次課,錢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》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章