0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

C語言技巧之回調(diào)函數(shù)

STM32嵌入式開發(fā) ? 來源:STM32嵌入式開發(fā) ? 2023-04-18 11:50 ? 次閱讀

一、函數(shù)指針

在講回調(diào)函數(shù)之前,我們需要了解函數(shù)指針。

我們都知道,C語言的靈魂是指針,我們經(jīng)常使用整型指針,字符串指針,結(jié)構(gòu)體指針等。


int *p1;
char *p2;
STRUCT *p3; // STRUCT為我們定義的結(jié)構(gòu)體

但是好像我們一般很少使用函數(shù)指針,我們一般使用函數(shù)都是直接使用函數(shù)調(diào)用。

下面我們來了解一下函數(shù)指針的概念和使用方法。

1. 概念

函數(shù)指針是指向函數(shù)的指針變量。

通常我們說的指針變量是指向一個整型、字符型或數(shù)組等變量,而函數(shù)指針是指向函數(shù)。


函數(shù)指針可以像一般函數(shù)一樣,用于調(diào)用函數(shù)、傳遞參數(shù)。

函數(shù)指針的定義方式為:

函數(shù)返回值類型 (* 指針變量名) (函數(shù)參數(shù)列表);

“函數(shù)返回值類型”表示該指針變量可以指向具有什么返回值類型的函數(shù);“函數(shù)參數(shù)列表”表示該指針變量可以指向具有什么參數(shù)列表的函數(shù)。這個參數(shù)列表中只需要寫函數(shù)的參數(shù)類型即可。

我們看到,函數(shù)指針的定義就是將“函數(shù)聲明”中的“函數(shù)名”改成“(指針變量名)”。但是這里需要注意的是:“(指針變量名)”兩端的括號不能省略,括號改變了運算符的優(yōu)先級。如果省略了括號,就不是定義函數(shù)指針而是一個函數(shù)聲明了,即聲明了一個返回值類型為指針型的函數(shù)。

那么怎么判斷一個指針變量是指向變量的指針變量還是指向函數(shù)的指針變量呢?首先看變量名前面有沒有“”,如果有“”說明是指針變量;其次看變量名的后面有沒有帶有形參類型的圓括號,如果有就是指向函數(shù)的指針變量,即函數(shù)指針,如果沒有就是指向變量的指針變量。

最后需要注意的是,指向函數(shù)的指針變量沒有 ++ 和 – 運算。

一般為了方便使用,我們會選擇:

typedef 函數(shù)返回值類型 (* 指針變量名) (函數(shù)參數(shù)列表); 比如:


typedef int (*Fun1)(int); //聲明也可寫成int (*Fun1)(int x),但習慣上一般不這樣。
typedef int (*Fun2)(int, int); //參數(shù)為兩個整型,返回值為整型
typedef void (*Fun3)(void); //無參數(shù)和返回值
typedef void* (*Fun4)(void*); //參數(shù)和返回值都為void*指針
2. 如何用函數(shù)指針調(diào)用函數(shù)

給大家舉一個例子:

int Func(int x);   /*聲明一個函數(shù)*/
int (*p) (int x);  /*定義一個函數(shù)指針*/
p = Func;          /*將Func函數(shù)的首地址賦給指針變量p*/
p = &Func;         /*將Func函數(shù)的首地址賦給指針變量p*/
賦值時函數(shù) Func 不帶括號,也不帶參數(shù)。由于函數(shù)名 Func 代表函數(shù)的首地址,因此經(jīng)過賦值以后,指針變量 p 就指向函數(shù) Func() 代碼的首地址了。

下面來寫一個程序,看了這個程序你們就明白函數(shù)指針怎么使用了:

#include 
int Max(int, int);  //函數(shù)聲明
int main(void)
{
    int(*p)(int, int);  //定義一個函數(shù)指針
    int a, b, c;
    p = Max;  //把函數(shù)Max賦給指針變量p, 使p指向Max函數(shù)
    printf("please enter a and b:");
    scanf("%d%d", &a, &b);
    c = (*p)(a, b);  //通過函數(shù)指針調(diào)用Max函數(shù)
    printf("a = %d
b = %d
max = %d
", a, b, c);
    return 0;
}
int Max(int x, int y)  //定義Max函數(shù)
{
    int z;
    if (x > y)
    {
        z = x;
    }
    else
    {
        z = y;
    }
    return z;
}
特別注意的是,因為函數(shù)名本身就可以表示該函數(shù)地址(指針),因此在獲取函數(shù)指針時,可以直接用函數(shù)名,也可以取函數(shù)的地址。

p = Max可以改成 p = &Max
c = (*p)(a, b) 可以改成 c = p(a, b)

3. 函數(shù)指針作為某個函數(shù)的參數(shù)

既然函數(shù)指針變量是一個變量,當然也可以作為某個函數(shù)的參數(shù)來使用的。示例:

#include 
#include 


typedef void(*FunType)(int);
//前加一個typedef關(guān)鍵字,這樣就定義一個名為FunType函數(shù)指針類型,而不是一個FunType變量。
//形式同 typedef int* PINT;
void myFun(int x);
void hisFun(int x);
void herFun(int x);
void callFun(FunType fp,int x);
int main()
{
    callFun(myFun,100);//傳入函數(shù)指針常量,作為回調(diào)函數(shù)
    callFun(hisFun,200);
    callFun(herFun,300);


    return 0;
}


void callFun(FunType fp,int x)
{
    fp(x);//通過fp的指針執(zhí)行傳遞進來的函數(shù),注意fp所指的函數(shù)有一個參數(shù)
}


void myFun(int x)
{
    printf("myFun: %d
",x);
}
void hisFun(int x)
{
    printf("hisFun: %d
",x);
}
void herFun(int x)
{
    printf("herFun: %d
",x);
}

輸出:

e084f52a-dd08-11ed-bfe3-dac502259ad0.jpg

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

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

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

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

5. 函數(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ù)組。


6. 函數(shù)指針總結(jié)

函數(shù)指針常量 :Max;函數(shù)指針變量:p;

數(shù)名調(diào)用如果都得如(*myFun)(10)這樣,那書寫與讀起來都是不方便和不習慣的。所以C語言的設計者們才會設計成又可允許myFun(10)這種形式地調(diào)用(這樣方便多了,并與數(shù)學中的函數(shù)形式一樣)。

在函數(shù)指針變量也可以存入一個數(shù)組內(nèi)。數(shù)組的聲明方法:int (*fArray[10]) ( int );

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

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

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

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

e09a9402-dd08-11ed-bfe3-dac502259ad0.png ?

假設我們要使用一個排序函數(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ù)。

我的理解是:把一段可執(zhí)行的代碼像參數(shù)傳遞那樣傳給其他代碼,而這段代碼會在某個時刻被調(diào)用執(zhí)行,這就叫做回調(diào)。

如果代碼立即被執(zhí)行就稱為同步回調(diào),如果過后再執(zhí)行,則稱之為異步回調(diào)。

回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù)。

回調(diào)函數(shù)不是由該函數(shù)的實現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時由另外的一方調(diào)用的,用于對該事件或條件進行響應。

2. 為什么要用回調(diào)函數(shù)?

因為可以把調(diào)用者與被調(diào)用者分開,所以調(diào)用者不關(guān)心誰是被調(diào)用者。它只需知道存在一個具有特定原型和限制條件的被調(diào)用函數(shù)。

簡而言之,回調(diào)函數(shù)就是允許用戶把需要調(diào)用的方法的指針作為參數(shù)傳遞給一個函數(shù),以便該函數(shù)在處理相似事件的時候可以靈活的使用不同的方法。 e0a841ba-dd08-11ed-bfe3-dac502259ad0.jpg


int Callback()    // /< 回調(diào)函數(shù)
{
    // TODO
    return 0;
}
int main()     // /<  主函數(shù)
{
    // TODO
    Library(Callback);  // /< 庫函數(shù)通過函數(shù)指針進行回調(diào)
    // TODO
    return 0;
}
回調(diào)似乎只是函數(shù)間的調(diào)用,和普通函數(shù)調(diào)用沒啥區(qū)別。

但仔細看,可以發(fā)現(xiàn)兩者之間的一個關(guān)鍵的不同:在回調(diào)中,主程序把回調(diào)函數(shù)像參數(shù)一樣傳入庫函數(shù)。

這樣一來,只要我們改變傳進庫函數(shù)的參數(shù),就可以實現(xiàn)不同的功能,這樣有沒有覺得很靈活?并且當庫函數(shù)很復雜或者不可見的時候利用回調(diào)函數(shù)就顯得十分優(yōu)秀。

3. 怎么使用回調(diào)函數(shù)?

int Callback_1(int a)   // /< 回調(diào)函數(shù)1
{
    printf("Hello, this is Callback_1: a = %d ", a);
    return 0;
}


int Callback_2(int b)  // /< 回調(diào)函數(shù)2
{
    printf("Hello, this is Callback_2: b = %d ", b);
    return 0;
}


int Callback_3(int c)   // /< 回調(diào)函數(shù)3
{
    printf("Hello, this is Callback_3: c = %d ", c);
    return 0;
}


int Handle(int x, int (*Callback)(int))  // /< 注意這里用到的函數(shù)指針定義
{
    Callback(x);
}


int main()
{
    Handle(4, Callback_1);
    Handle(5, Callback_2);
    Handle(6, Callback_3);
    return 0;
}

如上述代碼:可以看到,Handle()函數(shù)里面的參數(shù)是一個指針,在 main()函數(shù)里調(diào)用 Handle()函數(shù)的時候,給它傳入了函數(shù) Callback_1()/Callback_2()/Callback_3()的函數(shù)名,這時候的函數(shù)名就是對應函數(shù)的指針,也就是說,回調(diào)函數(shù)其實就是函數(shù)指針的一種用法。

4. 下面是一個四則運算的簡單回調(diào)函數(shù)例子:

#include 
#include 


/****************************************
 * 函數(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
", (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
", 
            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; 
}

5. 回調(diào)函數(shù)實例(很有用)

一個 GPRS 模塊聯(lián)網(wǎng)的小項目,使用過的同學大概知道 2G、4G、NB 等模塊要想實現(xiàn)無線聯(lián)網(wǎng)功能都需要經(jīng)歷模塊上電初始化、注冊網(wǎng)絡、查詢網(wǎng)絡信息質(zhì)量、連接服務器等步驟,這里的的例子就是,利用一個狀態(tài)機函數(shù)(根據(jù)不同狀態(tài)依次調(diào)用不同實現(xiàn)方法的函數(shù)),通過回調(diào)函數(shù)的方式依次調(diào)用不同的函數(shù),實現(xiàn)模塊聯(lián)網(wǎng)功能,如下:

/*********  工作狀態(tài)處理  *********/
typedef struct
{
    uint8_t mStatus;
    uint8_t (* Funtion)(void); //函數(shù)指針的形式
} M26_WorkStatus_TypeDef;   //M26的工作狀態(tài)集合調(diào)用函數(shù)




/**********************************************
** >M26工作狀態(tài)集合函數(shù)
***********************************************/
M26_WorkStatus_TypeDef M26_WorkStatus_Tab[] =
{    
    {GPRS_NETWORK_CLOSE,  M26_PWRKEY_Off  },    //模塊關(guān)機
    {GPRS_NETWORK_OPEN,  M26_PWRKEY_On  },      //模塊開機
    {GPRS_NETWORK_Start,   M26_Work_Init  },    //管腳初始化
    {GPRS_NETWORK_CONF,  M26_NET_Config  },     //AT指令配置
    {GPRS_NETWORK_LINK_CTC,  M26_LINK_CTC  },   //連接調(diào)度中心  
    {GPRS_NETWORK_WAIT_CTC, M26_WAIT_CTC  },    //等待調(diào)度中心回復 
    {GPRS_NETWORK_LINK_FEM, M26_LINK_FEM  },    //連接前置機
    {GPRS_NETWORK_WAIT_FEM, M26_WAIT_FEM  },    //等待前置機回復
    {GPRS_NETWORK_COMM,  M26_COMM   },          //正常工作    
    {GPRS_NETWORK_WAIT_Sig,  M26_WAIT_Sig  },   //等待信號回復
    {GPRS_NETWORK_GetSignal,  M26_GetSignal  }, //獲取信號值
    {GPRS_NETWORK_RESTART,  M26_RESET   },      //模塊重啟
}
/**********************************************
** >M26模塊工作狀態(tài)機,依次調(diào)用里面的12個函數(shù)   
***********************************************/
uint8_t M26_WorkStatus_Call(uint8_t Start)
{
    uint8_t i = 0;
    for(i = 0; i < 12; i++)
    {
        if(Start == M26_WorkStatus_Tab[i].mStatus)
        {          
      return M26_WorkStatus_Tab[i].Funtion();
        }
    }
    return 0;
}

所以,如果有人想做個 NB 模塊聯(lián)網(wǎng)項目,可以 copy 上面的框架,只需要修改回調(diào)函數(shù)內(nèi)部的具體實現(xiàn),或者增加、減少回調(diào)函數(shù),就可以很簡潔快速的實現(xiàn)模塊聯(lián)網(wǎng)。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7581

    瀏覽量

    135574
  • 回調(diào)函數(shù)
    +關(guān)注

    關(guān)注

    0

    文章

    87

    瀏覽量

    11521
  • typedef
    +關(guān)注

    關(guān)注

    0

    文章

    26

    瀏覽量

    9525
  • 指針變量
    +關(guān)注

    關(guān)注

    0

    文章

    17

    瀏覽量

    7221
  • 函數(shù)指針
    +關(guān)注

    關(guān)注

    2

    文章

    56

    瀏覽量

    3766

原文標題:C語言技巧之回調(diào)函數(shù)

文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    一文詳解C語言函數(shù)指針與調(diào)函數(shù)

    在講調(diào)函數(shù)之前,我們需要了解函數(shù)指針。
    發(fā)表于 10-19 09:34 ?753次閱讀

    C語言里面的函數(shù)指針和調(diào)函數(shù)

    在講調(diào)函數(shù)之前,我們需要了解函數(shù)指針。
    發(fā)表于 12-13 10:28 ?498次閱讀

    C語言使用回調(diào)函數(shù)模擬委托與反射

    函數(shù)C語言的核心概念。主調(diào)函數(shù)(caller)調(diào)用被調(diào)函數(shù)(callee)是一般的調(diào)用關(guān)系,如
    發(fā)表于 08-03 16:12 ?445次閱讀

    C 語言調(diào)函數(shù)詳解

    C 語言調(diào)函數(shù)詳解什么是調(diào)
    發(fā)表于 04-08 10:36

    C語言調(diào)函數(shù)是什么

    什么是調(diào)函數(shù)?為什么要使用回調(diào)函數(shù)?怎么使用回調(diào)函數(shù)
    發(fā)表于 12-28 07:11

    C語言調(diào)函數(shù)學習

    對指針的應用是C語言編程的精髓所在,而回調(diào)函數(shù)就是C語言里面對
    發(fā)表于 05-27 09:44 ?7178次閱讀

    C語言函數(shù)調(diào)函數(shù)

    來源:嵌入式客棧 1 什么是調(diào)函數(shù)?首先什么是調(diào)呢? 我的理解是:把一段可執(zhí)行的代碼像參數(shù)傳遞那樣傳給其他代碼,而這段代碼會在某個時刻被
    的頭像 發(fā)表于 09-11 09:57 ?4068次閱讀

    c語言調(diào)函數(shù)的使用及實際作用詳解

    大家好,我是無際。今天給大家講一下芯片/模塊廠家寫SDK必須會使用的一種技術(shù):調(diào)函數(shù)。調(diào)函數(shù)
    發(fā)表于 11-20 19:51 ?13次下載
    <b class='flag-5'>c</b><b class='flag-5'>語言</b><b class='flag-5'>回</b><b class='flag-5'>調(diào)</b><b class='flag-5'>函數(shù)</b>的使用及實際作用詳解

    C語言使用回調(diào)函數(shù)模擬委托與反射

    函數(shù)C語言的核心概念。主調(diào)函數(shù)(caller)調(diào)用被調(diào)函數(shù)(callee)是一般的調(diào)用關(guān)系,如
    的頭像 發(fā)表于 03-14 10:19 ?1263次閱讀

    詳解調(diào)函數(shù)的概念及使用步驟

    調(diào)函數(shù)就是一個被作為參數(shù)傳遞的函數(shù)。在C語言中,
    的頭像 發(fā)表于 05-26 15:20 ?3923次閱讀

    一文詳解C/C++調(diào)函數(shù)

    首先看一下調(diào)函數(shù)的官方解釋:調(diào)函數(shù)就是一個通過函數(shù)
    的頭像 發(fā)表于 02-12 09:20 ?1428次閱讀

    函數(shù)指針和調(diào)函數(shù)的使用方法

    了解開發(fā)語言的朋友應該都會對調(diào)函數(shù)有所了解,在很多的程序開發(fā)語言中都能看到
    的頭像 發(fā)表于 04-10 15:08 ?1023次閱讀

    C語言|調(diào)函數(shù)的不同用法

    調(diào)函數(shù)是個高級操作技巧,也是日常項目中常常使用到的技能。之所以說調(diào)函數(shù)是個高級操作技巧,是因
    發(fā)表于 07-10 10:34 ?1197次閱讀

    C++生成Dll與調(diào)函數(shù)測試

    描述了VS環(huán)境下,通過C++生成dll的方法,測試調(diào)函數(shù)
    的頭像 發(fā)表于 08-29 16:05 ?1429次閱讀
    <b class='flag-5'>C</b>++生成Dll與<b class='flag-5'>回</b><b class='flag-5'>調(diào)</b><b class='flag-5'>函數(shù)</b>測試

    調(diào)函數(shù)(callback)是什么?調(diào)函數(shù)的實現(xiàn)方法

    調(diào)函數(shù)是一種特殊的函數(shù),它作為參數(shù)傳遞給另一個函數(shù),并在被調(diào)用函數(shù)執(zhí)行完畢后被調(diào)用。
    發(fā)表于 03-12 11:46 ?2469次閱讀