色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

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

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

3天內不再提示

C語言技巧之回調函數

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

一、函數指針

在講回調函數之前,我們需要了解函數指針。

我們都知道,C語言的靈魂是指針,我們經常使用整型指針,字符串指針,結構體指針等。


int *p1;
char *p2;
STRUCT *p3; // STRUCT為我們定義的結構體

但是好像我們一般很少使用函數指針,我們一般使用函數都是直接使用函數調用。

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

1. 概念

函數指針是指向函數的指針變量。

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


函數指針可以像一般函數一樣,用于調用函數、傳遞參數。

函數指針的定義方式為:

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

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

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

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

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

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

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


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

給大家舉一個例子:

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

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

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

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

3. 函數指針作為某個函數的參數

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

#include 
#include 


typedef void(*FunType)(int);
//前加一個typedef關鍵字,這樣就定義一個名為FunType函數指針類型,而不是一個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);//傳入函數指針常量,作為回調函數
    callFun(hisFun,200);
    callFun(herFun,300);


    return 0;
}


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


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. 函數指針作為函數返回類型

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

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

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

5. 函數指針數組

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

/* 方法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) *的函數指針數組。


6. 函數指針總結

函數指針常量 :Max;函數指針變量:p;

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

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

二、回調函數

1. 什么是回調函數

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

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

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

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

結合這幅圖和上面對回調函數的解釋,我們可以發現,要實現回調函數,最關鍵的一點就是要將函數的指針傳遞給一個函數(上圖中是庫函數),然后這個函數就可以通過這個指針來調用回調函數了。注意,回調函數并不是C語言特有的,幾乎任何語言都有回調函數。在C語言中,我們通過使用函數指針來實現回調函數。

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

如果代碼立即被執行就稱為同步回調,如果過后再執行,則稱之為異步回調。

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

回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應。

2. 為什么要用回調函數?

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

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


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

但仔細看,可以發現兩者之間的一個關鍵的不同:在回調中,主程序把回調函數像參數一樣傳入庫函數。

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

3. 怎么使用回調函數?

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


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


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


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


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

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

4. 下面是一個四則運算的簡單回調函數例子:

#include 
#include 


/****************************************
 * 函數指針結構體
 ***************************************/
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
", (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
", 
            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. 回調函數實例(很有用)

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

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




/**********************************************
** >M26工作狀態集合函數
***********************************************/
M26_WorkStatus_TypeDef M26_WorkStatus_Tab[] =
{    
    {GPRS_NETWORK_CLOSE,  M26_PWRKEY_Off  },    //模塊關機
    {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  },   //連接調度中心  
    {GPRS_NETWORK_WAIT_CTC, M26_WAIT_CTC  },    //等待調度中心回復 
    {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模塊工作狀態機,依次調用里面的12個函數   
***********************************************/
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 模塊聯網項目,可以 copy 上面的框架,只需要修改回調函數內部的具體實現,或者增加、減少回調函數,就可以很簡潔快速的實現模塊聯網。

審核編輯:湯梓紅

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

    關注

    180

    文章

    7608

    瀏覽量

    137122
  • 回調函數
    +關注

    關注

    0

    文章

    87

    瀏覽量

    11588
  • typedef
    +關注

    關注

    0

    文章

    26

    瀏覽量

    9554
  • 指針變量
    +關注

    關注

    0

    文章

    17

    瀏覽量

    7241
  • 函數指針
    +關注

    關注

    2

    文章

    56

    瀏覽量

    3823

原文標題:C語言技巧之回調函數

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

收藏 人收藏

    評論

    相關推薦

    一文詳解C語言函數指針與調函數

    在講調函數之前,我們需要了解函數指針。
    發表于 10-19 09:34 ?798次閱讀

    C語言里面的函數指針和調函數

    在講調函數之前,我們需要了解函數指針。
    發表于 12-13 10:28 ?645次閱讀

    C語言使用回調函數模擬委托與反射

    函數C語言的核心概念。主調函數(caller)調用被調函數(callee)是一般的調用關系,如
    發表于 08-03 16:12 ?479次閱讀

    C 語言調函數詳解

    C 語言調函數詳解什么是調
    發表于 04-08 10:36

    C語言調函數是什么

    什么是調函數?為什么要使用回調函數?怎么使用回調函數
    發表于 12-28 07:11

    C語言調函數學習

    對指針的應用是C語言編程的精髓所在,而回調函數就是C語言里面對
    發表于 05-27 09:44 ?7210次閱讀

    C語言函數調函數

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

    c語言調函數的使用及實際作用詳解

    大家好,我是無際。今天給大家講一下芯片/模塊廠家寫SDK必須會使用的一種技術:調函數。調函數
    發表于 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'>調</b><b class='flag-5'>函數</b>的使用及實際作用詳解

    C語言使用回調函數模擬委托與反射

    函數C語言的核心概念。主調函數(caller)調用被調函數(callee)是一般的調用關系,如
    的頭像 發表于 03-14 10:19 ?1330次閱讀

    詳解調函數的概念及使用步驟

    調函數就是一個被作為參數傳遞的函數。在C語言中,
    的頭像 發表于 05-26 15:20 ?4053次閱讀

    一文詳解C/C++調函數

    首先看一下調函數的官方解釋:調函數就是一個通過函數
    的頭像 發表于 02-12 09:20 ?1623次閱讀

    函數指針和調函數的使用方法

    了解開發語言的朋友應該都會對調函數有所了解,在很多的程序開發語言中都能看到
    的頭像 發表于 04-10 15:08 ?1119次閱讀

    C語言|調函數的不同用法

    調函數是個高級操作技巧,也是日常項目中常常使用到的技能。之所以說調函數是個高級操作技巧,是因
    發表于 07-10 10:34 ?1334次閱讀

    C++生成Dll與調函數測試

    描述了VS環境下,通過C++生成dll的方法,測試調函數
    的頭像 發表于 08-29 16:05 ?1679次閱讀
    <b class='flag-5'>C</b>++生成Dll與<b class='flag-5'>回</b><b class='flag-5'>調</b><b class='flag-5'>函數</b>測試

    調函數(callback)是什么?調函數的實現方法

    調函數是一種特殊的函數,它作為參數傳遞給另一個函數,并在被調用函數執行完畢后被調用。
    發表于 03-12 11:46 ?3024次閱讀
    主站蜘蛛池模板: 玩两个少妇女邻居| 99热视频这里只有久久精品| 超碰在线视频97| 色欲AV精品人妻一区二区麻豆| 怪物高h粗暴无尽| 亚洲国产亚综合在线区尤物| 久久精品视在线观看85| 真实伦 乱| 青青久在线视频免费观看| 国产精品96久久久久久AV网址 | 亚洲国产三级在线观看| 久久婷婷电影网| 芳草地在线观看免费观看| 亚洲色综合狠狠综合区| 青草国产超碰人人添人人碱| 国产综合视频在线观看一区| 99精品成人无码A片观看金桔| 午夜影院美女| 麻豆XXXX乱女少妇精品-百度| 国产第一页浮力影院| 在线观看免费小视频| 色综合欧美色综合七久久| 久久成人无码国产免费播放| 朝鲜美女bbwbbw撒尿| 伊人亚洲综合青草青草久热| 人人碰在线视频| 久久综合中文字幕佐佐木希| 国产3级在线观看| 97人妻精品全国免费视频| 亚洲国产精品热久久| 日本888 xxxx| 麻豆天美国产一区在线播放| 国产三级级在线电影| xnxx高中生| 中文无码不卡的岛国片国产片| 四虎永久在线精品国产免费| 免费欧美大片| 久久99亚洲AV无码四区碰碰| 国产精品欧美久久久久天天影视 | 夜色帮首页| 午夜福利免费0948视频|