1. 程式人生 > 其它 >C語言中函式指標和回撥函式的詳解

C語言中函式指標和回撥函式的詳解

函式指標:指向函式的指標變數。 因此“函式指標”本身首先應是指標變數,只不過該指標變數指向函式。這正如用指標變數可指向整型變數、字元型、陣列一樣,這裡是指向函式。如前所述,C在編譯時,每一個函式都有一個入口地址,該入口地址就是函式指標所指向的地址。有了指向函式的指標變數後,可用該指標變數呼叫函式,就如同用指標變數可引用其他型別變數一樣,在這些概念上是大體一致的。函式指標有兩個用途:呼叫函式和做函式的引數。

我們首先來看一個函式指標的例子:

#include <stdio.h>
#include <stdlib.h>
void (*pfun)(int data);
void
myfun(int data) { printf("get data:%d\n",data); } int main(int argc,char *argv[]) { pfun = myfun; (*pfun)(100); return 0; } 從這個例子可以看到,我們首先定義了一個函式指標pfun ,這個函式指標的返回值為void型,然後我們給函式指標賦值,賦值為myfun,也就是myfun函式的首地址,在C99中myfun函式名就是myfun函式的首地址,此時pfun獲得了myfun的地址,pfun的地址等於myfun的地址,所以最終呼叫pfun();也就相當於呼叫了myfun(); 第二種用法:typedef 原變數型別 別名 也可以用typedef來定義一個指標函式這樣使在大型程式碼中更加簡潔 #include
<stdio.h> #include <stdlib.h> typedef void (*pfun)(int data); /*typedef的功能是定義新的型別。第一句就是定義了一種pfun的型別,並定義這種型別為指向某種函式的指標,這種函式以一個int為引數並返回void型別。*/ void myfun(int data) { printf("get data:%d\n",data); } int main(int argc,char *argv[]) { pfun p= myfun; //函式指標指向執行函式的地址 p(100); return
0; } 這裡面的pfun代表的是函式的型別,通過pfun來代表void (*)(int)函式型別即pfun是指標函式的別名,pfun p相當於定義了一個 void (*p)(int)函式指標。p = myfun可以理解為將函式指標p指向myfun函式的地址,p(100);相當於執行myfun(100); 第三種用結構體函式指標的方法 #include <stdio.h> #include <stdlib.h> typedef struct gfun{ void (*pfun)(int); }gfun; void myfun(int data) { printf("get data:%d\n",data); } int main(int argc,char *argv[]) { gfun gcode={ .pfun = myfun, //將函式指標指向要呼叫函式的地址 }; gcode.pfun(100); return 0; }

這三種方法執行的結果一樣

回撥函式:通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用於對該事件或條件進行響應。
下面是一個回撥函式的例子:

#include <stdio.h>
#include <stdlib.h>
typedef struct gfun{
    int (*pfun)(int);    
}gfun;

int myfun(int data)
{
    printf("get data:%d\n",data);
    return (data*2);
}
int rt_data(int data,int (*tr_fun)())
{
    return ((*tr_fun)(data));
}  
int main(int argc,char *argv[])
{
    int ret;
    gfun gf;
    gf.pfun = myfun;
    ret = rt_data(100,gf.pfun);
    printf("return data:%d\n",ret);
    return 0;
}

執行的結果如下:

通過上面的例子我們可以看到將結構體中的函式指標指向了myfun函式地址,在回撥函式中我們將函式指標gf.pfun作為rt_data(int data,int (*tr_fun)())函式的引數即為int (*tr_fun)();回撥函式中的return (*tr_fun)(data)相當於對指標進行了簡引用,返回這個指標指向地址的內容值。

回撥函式的意義
可以把呼叫者與被呼叫者分開,所以呼叫者不關心誰是被呼叫者。它只需知道存在一個具有特定原型和限制條件的被呼叫函式。簡而言之,回撥函式就是允許使用者把需要呼叫的函式的指標作為引數傳遞給一個函式,以便該函式在處理相似事件的時候可以靈活的使用不同的方法。
回撥函式在實際中有什麼作用?先假設有這樣一種情況:我們要編寫一個庫,它提供了某些排序演算法的實現(如氣泡排序、快速排序、shell排序、shake排序等等),為了能讓庫更加通用,不想在函式中嵌入排序邏輯,而讓使用者來實現相應的邏輯;或者,能讓庫可用於多種資料型別(int、float、string),此時,該怎麼辦呢?可以使用函式指標,並進行回撥。
回撥可用於通知機制。例如,有時要在A程式中設定一個計時器,每到一定時間,A程式會得到相應的通知,但通知機制的實現者對A程式一無所知。那麼,就需一個具有特定原型的函式指標進行回撥,通知A程式事件已經發生。實際上,API使用一個回撥函式SetTimer()來通知計時器。如果沒有提供回撥函式,它還會把一個訊息發往程式的訊息佇列。
談完回撥函式的意義,我們就有了使用者和開發者之間的概念,舉個例子,使用者是實現myfun這個函式,開發者是實現rt_data函式,根據需求使用者將myfun函式以引數的形式傳入開發者的rt_data函式中,rt_data函式就能返回給相應的資料給使用者,開發者不用告訴使用者它實現了什麼,使用者也並不知道開發者怎麼實現,使用者只用傳入自己的函式,便可以得到開發者實現的函式返回值,開發者可以將內容封裝起來,將標頭檔案以及庫檔案提供給使用者。
下面看個封裝的例子
main.c是上層使用者開發的
fun.c fun.h是開發者開發的

mian.c程式碼如下

#include "fun.h"
#include<stdio.h>
#include<stdlib.h>

typedef struct gfun{
    int (*pfun)(int);    
}gfun;

int myfun(int data)
{
    printf("get data:%d\n",data);
    return (data*2);
}
 
int main(int argc,char *argv[])
{
    int ret;
    gfun gf;
    gf.pfun = myfun;
    ret = rt_data(100,gf.pfun);
    printf("return data:%d\n",ret);
    return 0;
}

fun.h程式碼:

#ifndef _FUN_H_
#define _FUN_H_
int rt_data(int data,int (*tr_fun)());

#endif

fun.c程式碼:

#include "fun.h"
int rt_data(int data,int (*tr_fun)())
{
    return ((*tr_fun)(data));
}  

最後用gcc main.c fun.c -o main編譯完成後生成mian執行檔案
將執行檔案執行後的結果如下: