程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C和指針

C和指針

編輯:關於C語言

前言

指針是C的靈魂,正是指針使得C存在了這麼多年,而且將長期存在下去。事實上,我自己不用C語言寫程序已經有一年了,工作中接觸到的只有java,python和javascript。最近用C完成了一下類似於OO中的封裝(即"類")的概念,順便把指針復習了下,感覺有必要記一下。

本文中的例子有這樣兩個概念:任務(Task),執行器(Executor)。任務有名稱(taskName),並且可以執行(execute)。 而執行器與具體任務所執行的內容無關,只是回調(callback)任務的執行方法,這樣我們的執行器就可以做的比較通用。而任務接口只需要實現一個execute方法即可,這樣我們的任務就可以是多種多樣的,可以通過統一的接口set給執行器執行。這是面向對象中基本的思想,也是比較常用的抽象方式。下面我們具體看下例子。

可以想象,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()函數比較有意思,它不在內部使用,而是讓執行器回調執行的。

#ifndef _ITASK_H
#define    _ITASK_H

typedef struct Task{
    struct Task *_this;
    char *taskName;
    void (*execute)();
}Task;

void execute();
#endif    /* _ITASK_H */

執行器接口比Task接口復雜一些,其中包含_this指針,包含一個對Task的引用,然後是對外的接口begin(), cancel().對接口的使用者來說,他們只需要調用接口實例上的setTask(),將任務傳遞給執行器,然後在適當時期調用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 <stdlib.h>
#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()方法
}

執行器的實現一樣,稍微復雜一點,構造的時候,將函數指針在內部設置好,當外部調用時動態的執行需要執行的函數,這句話可能有些繞口,這麼看:在構造Executor的時候,executor->begin = begin; 這條語句是將下面void begin()的實現注冊到結構體中,但是要執行什麼還是不確切的,當setTask以後,回調函數的地址已經明確:(executor->_this->task = task;),此時調用begin()即可正確的調用到注冊的Task上。

#include <stdlib.h>
#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是計算兩個數的某四則混合運算,另一個僅僅是用來打印一點信息。然後我們可以看到,他們使用完全相同的接口來執行:

#include <stdio.h>

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 <stdio.h>
#include <stdlib.h>

#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的開發。只是想表達一下,指針在C中的重要性和指針的一點高級用法。其實現在的OO語言,基本還是以面向過程的表達式來表達面向對象而已。並沒有什麼神奇之處,OO主要是思想上的抽象,可以說是語言無關的(language independent)。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved