指針是C的靈魂,正是指針使得C存在了這么多年,而且將長期存在下去。事實上,我自己不用C語言寫程序已經有一年了,工作中接觸到的只有java,python和javascript。最近用C完成了一下類似于OO中的封裝(即"類")的概念,順便把指針復習了下,感覺有必要記一下。
本文中的例子有這樣兩個概念:任務(Task),執(zhí)行器(Executor)。任務有名稱(taskName),并且可以執(zhí)行(execute)。 而執(zhí)行器與具體任務所執(zhí)行的內容無關,只是回調(callback)任務的執(zhí)行方法,這樣我們的執(zhí)行器就可以做的比較通用。而任務接口只需要實現一個execute方法即可,這樣我們的任務就可以是多種多樣的,可以通過統(tǒng)一的接口set給執(zhí)行器執(zhí)行。這是面向對象中基本的思想,也是比較常用的抽象方式。下面我們具體看下例子。
可以想象,main函數大概是這個樣子:
int main(int argc, char** argv) {
Task *t1 = TaskConstruction("Task1", run);//此處的run是一個函數指針 Executor *exe = ExecutorConstruction(); exe->setTask(t1); exe->begin(); exe->cancel();
Task *t2 = TaskConstruction("Task2", run2);//此處的run2也是一個函數指針,用于構造一個Task. exe->setTask(t2); exe->begin(); exe->cancel(); return (EXIT_SUCCESS); } |
運行結果為:
task : [Task1] is ready to run [a = 1.200000, b = 2.300000] [(a + b) * (a - b) = -3.850000] cancel is invoked here task : [Task2] is ready to run another type of execute,just print out some information cancel is invoked here |
好了,下面詳細看看實現:
定義接口
首先,定義Task和Executor兩個實體的接口:
Task接口,注意其中的_this字段,這個指針在后邊有很重要的作用,用于hold整個Task的實例。然后是一個taskName的字符串,和一個函數指針,這個指針在初始化(構造)Task時傳入。這個execute()函數比較有意思,它不在內部使用,而是讓執(zhí)行器回調執(zhí)行的。
#ifndef _ITASK_H #define _ITASK_H
typedef struct Task{ struct Task *_this; char *taskName; void (*execute)(); }Task;
void execute(); #endif /* _ITASK_H */ |
執(zhí)行器接口比Task接口復雜一些,其中包含_this指針,包含一個對Task的引用,然后是對外的接口begin(), cancel().對接口的使用者來說,他們只需要調用接口實例上的setTask(),將任務傳遞給執(zhí)行器,然后在適當時期調用begin(),等待任務正常結束或者調用cancel()將其取消掉。
#include "ITask.h"
#ifndef _IEXECUTOR_H #define _IEXECUTOR_H
typedef struct Executor{ struct Executor *_this; Task *task; char *(*setTask)(Task* task); void (*begin)(); void (*cancel)(); }Executor;
char *setTask(Task *task); void begin(); void cancel();
#endif /* _IEXECUTOR_H */
|
實現接口
#include #include "ITask.h"
Task *task = NULL;
void execute();
/* * The construction of Task object. * name : the name of the task * execute : execute method of the task * */ Task *TaskConstruction(char *name, void (*execute)()){ task = (Task*)malloc(sizeof(strlen(name))+sizeof(execute)); task->taskName = name; task->execute = execute; task->_this = task; return (Task*)task;//返回一個自身的指針,通過內部的_this指針,兩者即可實現封裝 }
/* * Destruction of task, not used current time. * */ void TaskDestruction(){ task->taskName = NULL; task->execute = NULL; task->_this = NULL; task = NULL; }
/* * private method, should register to executor * */ void execute(){ task->_this->execute();//調用_this上的execute()方法 } |
執(zhí)行器的實現一樣,稍微復雜一點,構造的時候,將函數指針在內部設置好,當外部調用時動態(tài)的執(zhí)行需要執(zhí)行的函數,這句話可能有些繞口,這么看:在構造Executor的時候,executor->begin = begin; 這條語句是將下面void begin()的實現注冊到結構體中,但是要執(zhí)行什么還是不確切的,當setTask以后,回調函數的地址已經明確:
(executor->_this->task = task;),此時調用begin()即可正確的調用到注冊的Task上。
#include #include "IExecutor.h"
Executor *executor = NULL;
Executor *ExecutorConstruction(){ executor = (Executor*)malloc(sizeof(Executor)); executor->begin = begin; executor->cancel = cancel; executor->setTask = setTask;
executor->_this = executor;
return (Executor*)executor; }
void ExecutorDestruction(){ executor->begin = NULL; executor->cancel = NULL; executor->setTask = NULL; executor = NULL; }
char *setTask(Task *task){ executor->_this->task = task; }
void begin(){ printf("task : [%s] is ready to run\n",executor->_this->task->taskName); executor->_this->task->execute(); }
void cancel(){//這個函數沒有實現,只是做了一個占位符,以后如果有多線程,可以用來停止主動線程。 printf("cancel is invoked here\n"); } |
其實,兩個實現的代碼都不算復雜,如果對C的指針理解的稍好,基本就沒什么問題了。
在C中使用OO
為了試驗,我們不妨設計兩個不同的Task,一個Task是計算兩個數的某四則混合運算,另一個僅僅是用來打印一點信息。然后我們可以看到,他們使用完全相同的接口來執(zhí)行:
#include
void run(){//計算(a+b)*(a-b) float a, b, r; a = 1.2; b = 2.3; r = 0.0; printf("[a = %f, b = %f]\n", a, b); printf("[(a + b) * (a - b) = %f]\n",((a+b)*(a-b))); }
void run2(){//打印一句話,事實上,這些函數可以做任何事,比如I/O,網絡,圖片處理,音樂播放等等。 printf("another type of execute,"); printf("just print out some information\n"); } |
然后,在Main中獎他們注冊給Task,代碼如下所示:
#include #include
#include "ITask.h" #include "IExecutor.h"
extern void run(); extern void run2();
int main(int argc, char** argv) { //代碼的風格上,應該可以看出和OO的風格及其類似。 Task *t1 = TaskConstruction("Task1", run);//new Task("Task 1", run); Executor *exe = ExecutorConstruction();// new Executor(); exe->setTask(t1); exe->begin(); exe->cancel(); Task *t2 = TaskConstruction("Task2", run2); exe->setTask(t2); exe->begin(); exe->cancel(); return (EXIT_SUCCESS); } |
程序的輸出結果上文中已經可以看到了,這里就不貼了。
當然,本文的主要目的不是想說什么“C也可以實現面向對象”之類的幼稚觀點,只要誰沒有嚴重的自虐傾向,相信不會有誰真的會用C來做OO的開發(fā)。只是想表達一下,指針在C中的重要性和指針的一點高級用法。其實現在的OO語言,基本還是以面向過程的表達式來表達面向對象而已。并沒有什么神奇之處,OO主要是思想上的抽象,可以說是語言無關的(language independent)。
【編輯推薦】
- C語言指針概念全面解析
- C++引用計數的智能指針的有效回收方法
- C++中數組和指針類型的關系