不懂C語言回調(diào)函數(shù),那就看這篇文章吧!

什么是回調(diào)函數(shù)

我們先來看看百度百科是如何定義回調(diào)函數(shù)的:

回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當(dāng)這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù)?;卣{(diào)函數(shù)不是由該函數(shù)的實現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時由另外的一方調(diào)用的,用于對該事件或條件進行響應(yīng)。

這段話比較長,也比較繞口。下面我通過一幅圖來說明什么是回調(diào):

回調(diào)機制

假設(shè)我們要使用一個排序函數(shù)來對數(shù)組進行排序,那么在主程序(Main program)中,我們先通過庫,選擇一個庫排序函數(shù)(Library function)。但排序算法有很多,有冒泡排序,選擇排序,快速排序,歸并排序。同時,我們也可能需要對特殊的對象進行排序,比如特定的結(jié)構(gòu)體等。庫函數(shù)會根據(jù)我們的需要選擇一種排序算法,然后調(diào)用實現(xiàn)該算法的函數(shù)來完成排序工作。這個被調(diào)用的排序函數(shù)就是回調(diào)函數(shù)(Callback function)。

結(jié)合這幅圖和上面對回調(diào)函數(shù)的解釋,我們可以發(fā)現(xiàn),要實現(xiàn)回調(diào)函數(shù),最關(guān)鍵的一點就是要將函數(shù)的指針傳遞給一個函數(shù)(上圖中是庫函數(shù)),然后這個函數(shù)就可以通過這個指針來調(diào)用回調(diào)函數(shù)了。注意,回調(diào)函數(shù)并不是C語言特有的,幾乎任何語言都有回調(diào)函數(shù)。在C語言中,我們通過使用函數(shù)指針來實現(xiàn)回調(diào)函數(shù)。那函數(shù)指針是什么?不著急,下面我們就先來看看什么是函數(shù)指針。

什么是函數(shù)指針

函數(shù)指針也是一種指針,只是它指向的不是整型,字符型而是函數(shù)。在C中,每個函數(shù)在編譯后都是存儲在內(nèi)存中,并且每個函數(shù)都有一個入口地址,根據(jù)這個地址,我們便可以訪問并使用這個函數(shù)。函數(shù)指針就是通過指向這個函數(shù)的入口,從而調(diào)用這個函數(shù)。

函數(shù)指針的使用

函數(shù)指針的定義

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

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

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

這兩種方式都是定義了一個指向返回值為 void 類型,參數(shù)為 (int, int, float) 的函數(shù)指針。第二種方法是為了讓函數(shù)指針更容易理解,尤其是在復(fù)雜的環(huán)境下;而對于一般的函數(shù)指針,直接用第一種方法就行了。
如果之前沒見過函數(shù)指針,可能會覺得函數(shù)指針的定義比較怪,為什么不是 void ()(int, int, float) *p_func 而是 void (*p_func)(int, int, float) 這種形式?這個問題我也不知道,也沒必要糾結(jié),花點時間理解下它與普通指針的區(qū)別,實在不行就先記住它的形式。

函數(shù)指針的賦值

在定義完函數(shù)指針后,我們就需要給它賦值了我們有兩種方式對函數(shù)指針進行賦值:

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

上面兩種方法都是合法的,對于第二種方法,編譯器會隱式地將 func_2void ()(int, int, float) 類型轉(zhuǎn)換成 void (*)(int, int, float) 類型,因此,這兩種方法都行。想要了解更詳細的說明,可以看看下面這個stackoverflow的鏈接。

使用函數(shù)指針調(diào)用函數(shù)

因為函數(shù)指針也是指針,因此可以使用常規(guī)的帶 * 的方法來調(diào)用函數(shù)。和函數(shù)指針的賦值一樣,我們也可以使用兩種方法:

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

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

方法1和我們平時直接調(diào)用函數(shù)是一樣的,方法2則是用了 * 對函數(shù)指針取值,從而實現(xiàn)對函數(shù)的調(diào)用。

將函數(shù)指針作為參數(shù)傳給函數(shù)

函數(shù)指針和普通指針一樣,我們可以將它作為函數(shù)的參數(shù)傳遞給函數(shù),下面我們看看如何實現(xiàn)函數(shù)指針的傳參:

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

/* func4 調(diào)用函數(shù)func3 */
void func4()
{
    func3(1, 2, 3.0, func_1);
    /* 或者 func3(1, 2, 3.0, &func_1); */
}

函數(shù)指針作為函數(shù)返回類型

有了上面的基礎(chǔ),要寫出返回類型為函數(shù)指針的函數(shù)應(yīng)該不難了,下面這個例子就是返回類型為函數(shù)指針的函數(shù):

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

在這里, func5(int, int, float) 為參數(shù),其返回類型為 void (*)(int, int) 。在C語言中,變量或者函數(shù)的聲明也是一個大學(xué)問,想要了解更多關(guān)于聲明的話題,可以參考我之前的文章 - C專家編程》讀書筆記(1-3章)。這本書的第三章花了整整一章的內(nèi)容來講解如何讀懂C語言的聲明。

函數(shù)指針數(shù)組

在開始講解回調(diào)函數(shù)前,最后介紹一下函數(shù)指針數(shù)組。既然函數(shù)指針也是指針,那我們就可以用數(shù)組來存放函數(shù)指針。下面我們看一個函數(shù)指針數(shù)組的例子:

/* 方法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];

上面兩種方法都可以用來定義函數(shù)指針數(shù)組,它們定義了一個元素個數(shù)為5,類型是 void (*)(int, int, float) 的函數(shù)指針數(shù)組。

回調(diào)函數(shù)

我們前面談的都是函數(shù)指針,現(xiàn)在我們回到正題,來看看回調(diào)函數(shù)到底是怎樣實現(xiàn)的。下面是一個四則運算的簡單回調(diào)函數(shù)例子:

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

/****************************************
 * 函數(shù)指針結(jié)構(gòu)體
 ***************************************/
typedef struct _OP {
    float (*p_add)(float, float); 
    float (*p_sub)(float, float); 
    float (*p_mul)(float, float); 
    float (*p_div)(float, float); 
} OP; 

/****************************************
 * 加減乘除函數(shù)
 ***************************************/
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;
}

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

/****************************************
 * 庫函數(shù)
 ***************************************/
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);
    
    /* 直接使用函數(shù)指針調(diào)用函數(shù) */ 
    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));
     
    /* 調(diào)用回調(diào)函數(shù) */ 
    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; 
}

這個例子有點長,我一步步地來講解如何使用回調(diào)函數(shù)。

第一步

要完成加減乘除,我們需要定義四個函數(shù)分別實現(xiàn)加減乘除的運算功能,這幾個函數(shù)就是:

/****************************************
 * 加減乘除函數(shù)
 ***************************************/
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;
}

第二步

我們需要定義四個函數(shù)指針分別指向這四個函數(shù):

/****************************************
 * 函數(shù)指針結(jié)構(gòu)體
 ***************************************/
typedef struct _OP {
    float (*p_add)(float, float); 
    float (*p_sub)(float, float); 
    float (*p_mul)(float, float); 
    float (*p_div)(float, float); 
} OP; 

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

第三步

我們需要創(chuàng)建一個“庫函數(shù)”,這個函數(shù)以函數(shù)指針為參數(shù),通過它來調(diào)用不同的函數(shù):

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

第四步

當(dāng)這幾部都完成后,我們就可以開始調(diào)用回調(diào)函數(shù)了:

/* 調(diào)用回調(diào)函數(shù) */ 
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));

簡單的四部便可以實現(xiàn)回調(diào)函數(shù)。在這四步中,我們甚至可以省略第二步,直接將函數(shù)名傳入“庫函數(shù)”,比如上面的乘法和除法運算?;卣{(diào)函數(shù)的核心就是函數(shù)指針,只要搞懂了函數(shù)指針再學(xué)回調(diào)函數(shù),那真是手到擒來了。

總結(jié)

本文主要講了如何使用函數(shù)指針和回調(diào)函數(shù)。回調(diào)函數(shù)的核心就是函數(shù)指針,因此我花了大量篇幅講解函數(shù)指針。對于回調(diào)函數(shù)的實現(xiàn),我給出了一個例子,希望這個例子能給你幫助?;卣{(diào)函數(shù)很重要,如果連它都不會,C語言真不算入門了。當(dāng)然了,即使會了它,也不要驕傲,因為C語言還有太多的東西需要我們?nèi)W(xué)習(xí)、實踐。

如果覺得本文對你有幫助,請多多點贊支持,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 指針是C語言中廣泛使用的一種數(shù)據(jù)類型。 運用指針編程是C語言最主要的風(fēng)格之一。利用指針變量可以表示各種數(shù)據(jù)結(jié)構(gòu); ...
    朱森閱讀 3,620評論 3 44
  • 題目類型 a.C++與C差異(1-18) 1.C和C++中struct有什么區(qū)別? C沒有Protection行為...
    阿面a閱讀 7,900評論 0 10
  • 第1章 第一個C程序第2章 C語言基礎(chǔ)第3章 變量和數(shù)據(jù)類型第4章 順序結(jié)構(gòu)程序設(shè)計第5章 條件結(jié)構(gòu)程序設(shè)計第6章...
    小獅子365閱讀 10,893評論 3 71
  • 由于公司項目需要,我抽時間研究了下react native的熱更新功能,其實并不復(fù)雜,自己在原來的demo上做了實...
    sybil052閱讀 1,147評論 2 2
  • 1.三月份兩篇論文。 2.12號之前3篇讀后感。 3.自控力好久沒看啦,撿起來??赐辍<右槐九c壓力做朋友。 4.內(nèi)...
    李紫林閱讀 443評論 0 1

友情鏈接更多精彩內(nèi)容