1. 程式人生 > >一文搞懂C語言回撥函式

一文搞懂C語言回撥函式

什麼是回撥函式

我們先來看看百度百科是如何定義回撥函式的:

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

這段話比較長,也比較繞口。下面我通過一幅圖來說明什麼是回撥:

假設我們要使用一個排序函式來對陣列進行排序,那麼在主程式(Main program)中,我們先通過庫,選擇一個庫排序函式(Library function)。但排序演算法有很多,有氣泡排序,選擇排序,快速排序,歸併排序。同時,我們也可能需要對特殊的物件進行排序,比如特定的結構體等。庫函式會根據我們的需要選擇一種排序演算法,然後呼叫實現該演算法的函式來完成排序工作。這個被呼叫的排序函式就是回撥函式(Callback function)。

結合這幅圖和上面對回撥函式的解釋,我們可以發現,要實現回撥函式,最關鍵的一點就是要將函式的指標傳遞給一個函式(上圖中是庫函式),然後這個函式就可以通過這個指標來呼叫回撥函數了。注意,回撥函式並不是C語言特有的,幾乎任何語言都有回撥函式。在C語言中,我們通過使用函式指標來實現回撥函式。那函式指標是什麼?不著急,下面我們就先來看看什麼是函式指標。

什麼是函式指標

函式指標也是一種指標,只是它指向的不是整型,字元型而是函式。在C中,每個函式在編譯後都是儲存在記憶體中,並且每個函式都有一個入口地址,根據這個地址,我們便可以訪問並使用這個函式。函式指標就是通過指向這個函式的入口,從而呼叫這個函式。

函式指標的使用

函式指標的定義

函式指標雖然也是指標,但它的定義方式卻和其他指標看上去很不一樣,我們來看看它是如何定義的:

/* 方法1 */
void (*p_func)(int, int, float) = NULL;

/* 方法2 */
typedef void (*tp_func)(int, int, float);
tp_func p_func = NULL;

這兩種方式都是定義了一個指向返回值為 void 型別,引數為 (int, int, float) 的函式指標。第二種方法是為了讓函式指標更容易理解,尤其是在複雜的環境下;而對於一般的函式指標,直接用第一種方法就行了。
如果之前沒見過函式指標,可能會覺得函式指標的定義比較怪,為什麼不是 void ()(int, int, float) *p_func

 而是 void (*p_func)(int, int, float) 這種形式?這個問題我也不知道,也沒必要糾結,花點時間理解下它與普通指標的區別,實在不行就先記住它的形式。

函式指標的賦值

在定義完函式指標後,我們就需要給它賦值了我們有兩種方式對函式指標進行賦值:

void (*p_func)(int, int, float) = NULL;
p_func = &func1;
p_func = func2;

上面兩種方法都是合法的,對於第二種方法,編譯器會隱式地將 func_2 由 void ()(int, int, float) 型別轉換成 void (*)(int, int, float) 型別,因此,這兩種方法都行。想要了解更詳細的說明,可以看看下面這個stackoverflow的連結

使用函式指標呼叫函式

因為函式指標也是指標,因此可以使用常規的帶 * 的方法來呼叫函式。和函式指標的賦值一樣,我們也可以使用兩種方法:

/* 方法1 */
int val1 = p_func(1,2,3.0);

/* 方法2 */
int val2 = (*p_func)(1,2,3.0);

方法1和我們平時直接呼叫函式是一樣的,方法2則是用了 * 對函式指標取值,從而實現對函式的呼叫。

將函式指標作為引數傳給函式

函式指標和普通指標一樣,我們可以將它作為函式的引數傳遞給函式,下面我們看看如何實現函式指標的傳參:

/* func3 將函式指標 p_func 作為其形參 */
void func3(int a, int b, float c, void (*p_func)(int, int, float))
{
    (*p_func)(a, b, c);
}

/* func4 呼叫函式func3 */
void func4()
{
    func3(1, 2, 3.0, func_1);
    /* 或者 func3(1, 2, 3.0, &func_1); */
}

函式指標作為函式返回型別

有了上面的基礎,要寫出返回型別為函式指標的函式應該不難了,下面這個例子就是返回型別為函式指標的函式:

void (* func5(int, int, float ))(int, int)
{
    ...
}

在這裡, func5 以 (int, int, float) 為引數,其返回型別為 void (*)(int, int) 。在C語言中,變數或者函式的宣告也是一個大學問,想要了解更多關於宣告的話題,可以參考我之前的文章 - C專家程式設計》讀書筆記(1-3章)。這本書的第三章花了整整一章的內容來講解如何讀懂C語言的宣告。

函式指標陣列

在開始講解回撥函式前,最後介紹一下函式指標陣列。既然函式指標也是指標,那我們就可以用陣列來存放函式指標。下面我們看一個函式指標陣列的例子:

/* 方法1 */
void (*func_array_1[5])(int, int, float);

/* 方法2 */
typedef void (*p_func_array)(int, int, float);
p_func_array func_array_2[5];

上面兩種方法都可以用來定義函式指標陣列,它們定義了一個元素個數為5,型別是 void (*)(int, int, float) 的函式指標陣列。

回撥函式

我們前面談的都是函式指標,現在我們回到正題,來看看回調函式到底是怎樣實現的。下面是一個四則運算的簡單回撥函式例子:

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

/****************************************
 * 函式指標結構體
 ***************************************/
typedef struct _OP {
    float (*p_add)(float, float); 
    float (*p_sub)(float, float); 
    float (*p_mul)(float, float); 
    float (*p_div)(float, float); 
} OP; 

/****************************************
 * 加減乘除函式
 ***************************************/
float ADD(float a, float b) 
{
    return a + b;
}

float SUB(float a, float b) 
{
    return a - b;
}

float MUL(float a, float b) 
{
    return a * b;
}

float DIV(float a, float b) 
{
    return a / b;
}

/****************************************
 * 初始化函式指標
 ***************************************/
void init_op(OP *op)
{
    op->p_add = ADD;
    op->p_sub = SUB;
    op->p_mul = &MUL;
    op->p_div = &DIV;
}

/****************************************
 * 庫函式
 ***************************************/
float add_sub_mul_div(float a, float b, float (*op_func)(float, float))
{
    return (*op_func)(a, b);
}

int main(int argc, char *argv[]) 
{
    OP *op = (OP *)malloc(sizeof(OP)); 
    init_op(op);
    
    /* 直接使用函式指標呼叫函式 */ 
    printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", (op->p_add)(1.3, 2.2), (*op->p_sub)(1.3, 2.2), 
            (op->p_mul)(1.3, 2.2), (*op->p_div)(1.3, 2.2));
     
    /* 呼叫回撥函式 */ 
    printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", 
            add_sub_mul_div(1.3, 2.2, ADD), 
            add_sub_mul_div(1.3, 2.2, SUB), 
            add_sub_mul_div(1.3, 2.2, MUL), 
            add_sub_mul_div(1.3, 2.2, DIV));

    return 0; 
}

這個例子有點長,我一步步地來講解如何使用回撥函式。

第一步

要完成加減乘除,我們需要定義四個函式分別實現加減乘除的運算功能,這幾個函式就是:

/****************************************
 * 加減乘除函式
 ***************************************/
float ADD(float a, float b) 
{
    return a + b;
}

float SUB(float a, float b) 
{
    return a - b;
}

float MUL(float a, float b) 
{
    return a * b;
}

float DIV(float a, float b) 
{
    return a / b;
}

第二步

我們需要定義四個函式指標分別指向這四個函式:

/****************************************
 * 函式指標結構體
 ***************************************/
typedef struct _OP {
    float (*p_add)(float, float); 
    float (*p_sub)(float, float); 
    float (*p_mul)(float, float); 
    float (*p_div)(float, float); 
} OP; 

/****************************************
 * 初始化函式指標
 ***************************************/
void init_op(OP *op)
{
    op->p_add = ADD;
    op->p_sub = SUB;
    op->p_mul = &MUL;
    op->p_div = &DIV;
}

第三步

我們需要建立一個“庫函式”,這個函式以函式指標為引數,通過它來呼叫不同的函式:

/****************************************
 * 庫函式
 ***************************************/
float add_sub_mul_div(float a, float b, float (*op_func)(float, float))
{
    return (*op_func)(a, b);
}

第四步

當這幾部都完成後,我們就可以開始呼叫回撥函數了:

/* 呼叫回撥函式 */ 
printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", 
        add_sub_mul_div(1.3, 2.2, op->p_add), 
        add_sub_mul_div(1.3, 2.2, op->p_sub), 
        add_sub_mul_div(1.3, 2.2, MUL), 
        add_sub_mul_div(1.3, 2.2, DIV));

簡單的四部便可以實現回撥函式。在這四步中,我們甚至可以省略第二步,直接將函式名傳入“庫函式”,比如上面的乘法和除法運算。回撥函式的核心就是函式指標,只要搞懂了函式指標再學回調函式,那真是手到擒來了。

總結

本文主要講了如何使用函式指標和回撥函式。回撥函式的核心就是函式指標,因此我花了大量篇幅講解函式指標。對於回撥函式的實現,我給出了一個例子,希望這個例子能給你幫助。回撥函式很重要,如果連它都不會,C語言真不算入門了。當然了,即使會了它,也不要驕傲,因為C語言還有太多的東西需要我們去學習、實踐。

相關推薦

C語言函式

什麼是回撥函式我們先來看看百度百科是如何定義回撥函式的:回撥函式就是一個通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方直接呼叫,而是在特定的事件或條件發生時由

C/C++中指標那些事(上篇)

一 指標變數 1.間接存取        指標變數的值為地址;普通變數的值為資料;其中“*”為指標運算子。&是地址操作符,用來引用一個記憶體地址。通過在變數名字前使用&操作符,我們可以得到該變數的記憶體地址。        針對記憶體資料的

C語言函式熟練—使用方法(構建程式框架方便好用)

通俗點不行嗎?啊,不行嗎?老外把國人玩的都不是人了。國人還自己玩自己。非把一個簡單的東西複雜化。叫那麼難理解!!窩裡鬥。。。。。。典型!!!!!!!! 不說那麼複雜的,誰是狗屎,豬屎。就說怎麼用回撥。使用步驟: 1.寫一個函式A,A裡面有一個引數是個指標函式 比如: int shao(in

C語言——函式

C語言——回撥函式 宗旨:技術的學習是有限的,分享的精神是無限的。          如果引數是一個函式指標,呼叫者可以傳遞一個函式的地址給實現者,讓實現者去呼叫它,這就是回撥函式。 void func(void (*f)(void*), void *p) 提供一個回撥

C 語言函式

1.回撥函式:回撥函式是指 使用者自己定義一個函式,實現這個函式的程式內容,然後把這個函式(入口地址)作為引數傳入別的函式中,由別的函式在執行時來呼叫的函式。函式是你實現的,但由別人(或系統)的函式在執行時通過引數傳遞的方式呼叫,這就是所謂的回撥函式。簡單來說,就是由別人的函式執行期間來回調你實現的

c語言函式 -----qsort

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

C語言函式的定義和寫法

1 定義和使用場合 回撥函式是指 使用者自己定義一個函式,實現這個函式的程式內容,然後把這個函式(入口地址)作為引數傳入別人(或系統)的函式中,由別人(或系統)的函式在執行時來呼叫的函式。函式是你實現的,但由別人(或系統)的函式在執行時通過引數傳遞的方式呼叫,這就是所謂的回撥函式。簡單來說,就是由別人的函式

C語言函式一個簡單的例子

原文地址:http://blog.csdn.net/zgrjkflmkyc/article/details/9198519 回撥函式在linux核心或是微控制器上應用得太多,由此也可以大致判斷,一個初學者和有個有經驗的區別。我轉寫在這兒,希望更多的人能夠看到,一起進步!!

:詞法作用域、動態作用域、函式、閉包

不管什麼語言,我們總要學習作用域(或生命週期)的概念,比如常見的稱呼:全域性變數、包變數、模組變數、本地變數、區域性變數等等。不管如何稱呼這些作用域的範圍,實現它們的目的都一樣: (1)為了避免名稱衝突; (2)為了限定變數的生命週期(本文以變數名說事,其它的名稱在規則上是一樣的)

「詞嵌入」在自然語言處理中扮演什麼角色?Word Embeddings的背後原理

原文來源:DATASCIENCE 作者:Ruslana Dalinina 「機器人圈」編譯:嗯~阿童木呀、多啦A亮 「機器人圈」正式更名為「雷克世界」,後臺回覆「雷克世界」檢視更多詳

各種 Docker 網絡 - 每天5分鐘玩轉 Docker 容器技術(72)

docker 教程 容器 前面各小節我們先後學習了 Docker Overaly,Macvaln,Flannel,Weave 和 Calico 跨主機網絡方案。目前這個領域是百家爭鳴,而且還有新的方案不斷湧現。本節將從不同維度比較各種網絡方案,大家在選擇的時候可以參考。CloudMan 的建議是:

Java 線程中斷

回復 代碼 信號 過程 執行 except 實例 二維 微信公眾 在之前的一文《如何"優雅"地終止一個線程》中詳細說明了 stop 終止線程的壞處及如何優雅地終止線程,那麽還有別的可以終止線程的方法嗎?答案是肯定的,它就是我們今天要分享的——線程中斷。 下面的這斷代碼大家應

C語言-

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

資料結構與演算法隨筆之------二叉樹的遍歷(二叉樹的四種遍歷)

二叉樹的遍歷 二叉樹的遍歷(traversing binary tree)是指從根結點出發,按照某種次序依次訪問二叉樹中所有的結點,使得每個結點被訪問依次且僅被訪問一次。 遍歷分為四種,前序遍歷,中序遍歷,後序遍歷及層序遍歷 前序 中

k近鄰(k-NN)演算法(

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Python裝飾器是款神奇的神器!你知道怎麼用嗎?它!

    進群:548377875  即可獲取小編精心準備的教程以及大量的PDF呢! 1.引子 #功能函式 def add(x,y): return x+y #裝飾函式 def logger(fn): print('frist') x =

交叉熵在機器學習中的使用,透徹理解交叉熵背後的直覺

關於交叉熵在loss函式中使用的理解 交叉熵(cross entropy)是深度學習中常用的一個概念,一般用來求目標與預測值之間的差距。以前做一些分類問題

db2 的鎖(表鎖、行鎖、共享鎖、排他鎖)

鎖,很好理解,每個人都在自己的房屋上安裝有鎖,你擁有了鎖,房屋只有你能獨佔,別人不能訪問。資料庫中的鎖也一樣,只不過更加細分。 db2 中基本的鎖有兩類: 排他鎖(X鎖),也叫寫鎖,當某行資料正在被修改時,其他程序不能再讀取或修改 共享鎖(S鎖),也叫讀鎖,當某行資料正

Java 執行緒中斷

在之前的一文《如何"優雅"地終止一個執行緒》中詳細說明了 stop 終止執行緒的壞處及如何優雅地終止執行緒,那麼還有別的可以終止執行緒的方法嗎?答案是肯定的,它就是我們今天要分享的——執行緒中斷。 下面的這斷程式碼大家應該再熟悉不過了,執行緒休眠需要捕獲或者丟擲

Raft演算法

  raft是工程上使用較為廣泛的強一致性、去中心化、高可用的分散式協議。在這裡強調了是在工程上,因為在學術理論界,最耀眼的還是大名鼎鼎的Paxos。但Paxos是:少數真正理解的人覺得簡單,尚未理解的人覺得很難,大多數人都是一知半解。本人也花了很多時間、看了很多材料也沒有真正理解。直到看到raft的論文,兩