標籤:
很多開發工程師都是從學習C語言的"Hello world!"開始的,都知道C語言中的指標是一把利劍,一不留意就傷了自個。但其C語言絕對是一個宗師級語言,這是不可否認的。
由於我們開發的需要在多個平台上運行且需要物件導向的一些特性、所以特寫此文章。權當拋磚引玉。
一、概述
C語言是一種面向過程的程式設計語言、而C++在語言層級上添加了很多新機制(繼承,多態等)
因此這裡說說使用C語言實現封裝,繼承和多態的方法。
二、基本知識
1、結構體
在C語言中,常把一個對象用結構體進行封裝,這樣便於對對象進行操作,比如:1
strcut Point{
int x;
int y;
};
結構體可以嵌套。因而可以把一個結構體當成另一個結構體的成員:
struct Circle {
struct Point point_;
int radius;
};
該結構體與以下定義完全一樣(包括記憶體布置都一樣):
struct Circle {
int x;
int y;
int radius;
};
2、函數指標
函數指標是指標的一種,它指向函數的首地址(函數的函數名即為函數的首地址),可以通過函數指標來調用函數。
如函數:
int func(int a[], int n);
可以這樣聲明函數指標:
int (*pFunc)(int a[], int n);
這樣使用:
pFunc = func;
(*pFunc)(a, n);【或者PFunc(a, n)】
可以用typedef定義一個函數指標類型,如:
typdef int (*FUNC)(int a[], int n)
可以這樣使用:
int cal_a(FUNC fptr, int a[], int n)
{
//實現體...
}
3、extern與static
extern和static是C語言中的兩個修飾符,extern可用於修飾函數或者變數,表示該變數或者函數在其他檔案中進行了定義;
static也可用於修飾函數或者變數,表示該函數或者變數只能在該檔案中使用。可利用它們對資料或者函數進行隱藏或者限制存取權限。
三、封裝
在C語言中,可以用結構+函數指標來類比類的實現,而用這種結構定義的變數就是對象。
封裝的主要含義是隱藏內部的行為和資訊,使用者只用看到對外提供的介面和公開的資訊。
有兩種方法實現封裝:
1、利用C語言文法。在標頭檔中聲明,在C檔案中真正定義它。
這樣可以隱藏內部資訊,因為外部不知道對象所佔記憶體的大小,所以不能靜態建立該類的對象,只能調用類提供的建立函數才能建立。這種方法的缺陷是不支援繼承,因為子類中得不到任何關於父類的資訊。如:
/**
* 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);
struct Point {
const void * base; //繼承Base類,基類指標,放在第一個位置,const是防止修改
int x, y; //座標
};
#define point_x(p)(((const struct Point *)(p)) -> x)
#define point_y(p)(((const struct Point *)(p)) -> y)
#endif
//Point的源檔案Point.c
#include <stdio.h>
#include "Point.h"
#include "cnew.h"
#include "Base.h"
/**********Point類自己的建構函式***********/
static void * Point_New(void * _self, va_list * app) {
struct Point * self = _self;
self->x = va_arg(*app, int);
self->y = va_arg(*app, int);
printf("Point_New self = %p \n", self);
return self;
}
/**********Point類自己的解構函式***********/
static void* Point_Delete(void * _self) {
printf("call Point_Delete self =%p \n", _self);
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \n\n");
return NULL;
}
/**********Point類自己的繪圖函數***********/
static void Point_Draw(const void * _self) {
const struct Point * self = _self;
printf("Point_Draw at %d,%d \n", self->x, self->y);
}
void move(void * _self, int dx, int dy) {
struct Point * self = _self;
printf("call move self =%p \n", _self);
self->x += dx;
self->y += dy;
}
static const struct Base _Point = { sizeof(struct Point), Point_New, Point_Delete, Point_Draw };
const void * Point = &_Point;
四、繼承
在C語言中,可以利用“結構在記憶體中的布局與結構的聲明具有一致的順序”這一事實實現繼承。
比如我們要設計一個作圖工具,其中可能涉及到的對象有Point(點),Circle(圓),由於圓是由點組成的,所有可以看成Circle繼承自Point。另外,Point和Circle都需要空間申請,空間釋放等操作,所有他們有共同的基類Base。
#ifndef C_NEW_H
#define C_NEW_H
/**
* 記憶體管理類標頭檔cnew.h(對外提供介面)
*/
void * cnew(const void * base, ...);
void cdelete(void * item);
void draw(const void * self);
#endif /* C_NEW_H */
/**
* 記憶體管理類的源檔案:cnew.c
*/
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdarg.h>
#include "cnew.h"
#include "Base.h"
void * cnew(const void * _class, ...) {
const struct Base * base = _class;
void * p = calloc(1, base->size);
assert(p);
*(const struct Base **) p = base;
if (base->constructor) {
va_list ap;
va_start(ap, _class);
p = base->constructor(p, &ap);
va_end(ap);
}
return p;
}
void cdelete(void * self) {
const struct Base ** cp = self;
if (self && *cp && (*cp)->destroy)
self = (*cp)->destroy(self);
free(self);
}
void draw(const void * self) {
const struct Base * const * cp = self;
assert(self && *cp && (*cp)->draw);
(*cp)->draw(self);
}
/**
* 基類Base的內部標頭檔Base.r,對外隱藏
*/
#ifndef BASE_R
#define BASE_R
#include <stdarg.h>
struct Base {
size_t size;
void * (*constructor)(void * self, va_list * app); //建構函式
void * (*destroy)(void * self); //解構函式
void (*draw)(const void * self);//作圖函數
};
#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);
struct Point {
const void * base; //繼承Base類,基類指標,放在第一個位置,const是防止修改
int x, y; //座標
};
#define point_x(p)(((const struct Point *)(p)) -> x)
#define point_y(p)(((const struct Point *)(p)) -> y)
#endif
//Point的源檔案Point.c
#include <stdio.h>
#include "Point.h"
#include "cnew.h"
#include "Base.h"
/**********Point類自己的建構函式***********/
static void * Point_New(void * _self, va_list * app) {
struct Point * self = _self;
self->x = va_arg(*app, int);
self->y = va_arg(*app, int);
printf("Point_New self = %p \n", self);
return self;
}
/**********Point類自己的解構函式***********/
static void* Point_Delete(void * _self) {
printf("call Point_Delete self =%p \n", _self);
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \n\n");
return NULL;
}
/**********Point類自己的繪圖函數***********/
static void Point_Draw(const void * _self) {
const struct Point * self = _self;
printf("Point_Draw at %d,%d \n", self->x, self->y);
}
void move(void * _self, int dx, int dy) {
struct Point * self = _self;
printf("call move self =%p \n", _self);
self->x += dx;
self->y += dy;
}
static const struct Base _Point = { sizeof(struct Point), Point_New, Point_Delete, Point_Draw };
const void * Point = &_Point;
/**
* Circle的標頭檔Circle.h(對外提供介面)
*/
#ifndef CIRCLE_H
#define CIRCLE_H
#include "Point.h"
extern const void * Circle; /* new(Circle, x, y, rad) */
struct Circle {
const struct Point pbase; //繼承Point類,需放在第一位
int radius;
int (*area)(void *self);// 面積,擴充方法
};
#define circle_area(p) (((const struct Circle *)(p)) -> area(p))
#endif
/**
* Circle的源檔案Circle.c
*/
#include <stdio.h>
#include "Circle.h"
#include "cnew.h"
#include "Base.h"
/**********Circle類自己的擴充函數***********/
static int Circle_Area(void * _self) {
const struct Circle * self = _self;
printf("call Circle_Area self =%p \n", _self);
return self->radius * self->radius;
}
/**********Circle類自己的建構函式***********/
static void * Circle_New(void * _self, va_list * app) {
struct Circle * self = ((const struct Base *) Point)->constructor(_self, app);
self->radius = va_arg(*app, int);
self->area = Circle_Area;
printf("call Circle_New self =%p \n", _self);
return self;
}
/**********Circle類自己的建構函式***********/
static void* Circle_Delete(void * _self) {
printf("call Circle_Delete self =%p \n", _self);
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \n\n");
return NULL;
}
/**********Circle類自己的繪圖函數***********/
static void Circle_Draw(const void * _self) {
const struct Circle * self = _self;
int x = point_x(self);
int y = point_y(self);
printf("Circle_Draw at %d,%d rad %d \n", x, y, self->radius);
}
static const struct Base _Circle = { sizeof(struct Circle), Circle_New, Circle_Delete, Circle_Draw };
const void * Circle = &_Circle;
/**
* 測試函數
*/
#include "Circle.h"
#include "cnew.h"
int oo_main(int argc, char ** argv) {
void * p;
int i;
for (i = 0; i < 2; i++) {
if (i == 0) {
p = cnew(Circle, 1, 2, 3);
circle_area(p);
} else {
p = cnew(Point, 1, 2);
}
draw(p);
move(p, 10, 20);
draw(p);
cdelete(p);
}
return 0;
}
/***********************************
* 測試結果:
*
* Point_New self = 0x50a1d8
* call Circle_New self =0x50a1d8
* Circle_Draw at 1,2 rad 3
* call move self =0x50a1d8
* Circle_Draw at 11,22 rad 3
* call Circle_Delete self =0x50a1d8
*
* Point_New self = 0x5096a0
* Point_Draw at 1,2
* call move self =0x5096a0
* Point_Draw at 11,22
* call Point_Delete self =0x5096a0
*
************************************/
五、多態
可以是用C語言中的萬能指標void* 實現多態,接上面的例子:
//測試main.c
int oo_main(int argc, char ** argv) {
void * p;
int i;
for (i = 0; i < 2; i++) {
if (i == 0) {
p = cnew(Circle, 1, 2, 3);
circle_area(p);
} else {
p = cnew(Point, 1, 2);
}
draw(p);
move(p, 10, 20);
draw(p);
cdelete(p);
}
return 0;
}
六、總結
C語言能夠類比實現物件導向語言具有的特性,包括:多態,繼承,封裝等,現在很多開源軟體都了用C語言實現了這幾個特性,包括大型開來源資料庫系統postgreSQL,可移植的C語言物件導向架構GObject,無線二進位運行環境BREW。採用C語言實現多態,繼承,封裝,能夠讓軟體有更好的可讀性,可擴充性。
全部的測試代碼:
http://download.csdn.net/detail/andyhuabing/8475335
用C實現物件導向