<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁(yè) > 設(shè)計(jì)應(yīng)用 > C語言進(jìn)階之回調(diào)函數(shù)詳解

          C語言進(jìn)階之回調(diào)函數(shù)詳解

          作者: 時(shí)間:2023-08-22 來源: 收藏
          一、函數(shù)指針

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

          本文引用地址:http://www.ex-cimer.com/article/202308/449836.htm

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

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

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

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

          1. 概念

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

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

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

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

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

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

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

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

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

          一般為了方便使用,我們會(huì)選擇:

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

          比如:

          typedef int (*Fun1)(int); //聲明也可寫成int (*Fun1)(int x),但習(xí)慣上一般不這樣。
          typedef int (*Fun2)(intint); //參數(shù)為兩個(gè)整型,返回值為整型
          typedef void (*Fun3)(void); //無參數(shù)和返回值
          typedef void* (*Fun4)(void*); //參數(shù)和返回值都為void*指針

          2. 如何用函數(shù)指針調(diào)用函數(shù)

          給大家舉一個(gè)例子:

          int Func(int x);   /*聲明一個(gè)函數(shù)*/
          int (*p) (int x);  /*定義一個(gè)函數(shù)指針*/
          p = Func;          /*將Func函數(shù)的首地址賦給指針變量p*/
          p = &Func;         /*將Func函數(shù)的首地址賦給指針變量p*/

          賦值時(shí)函數(shù) Func 不帶括號(hào),也不帶參數(shù)。由于函數(shù)名 Func 代表函數(shù)的首地址,因此經(jīng)過賦值以后,指針變量 p 就指向函數(shù) Func() 代碼的首地址了。

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

          #include 
          int Max(intint);  //函數(shù)聲明
          int main(void)
          {
              int(*p)(intint);  //定義一個(gè)函數(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 = %dnb = %dnmax = %dn", 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;
          }

          特別注意的是,因?yàn)楹瘮?shù)名本身就可以表示該函數(shù)地址(指針),因此在獲取函數(shù)指針時(shí),可以直接用函數(shù)名,也可以取函數(shù)的地址。

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

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

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

          #include 
          #include 
          //前加一個(gè)typedef關(guān)鍵字,這樣就定義一個(gè)名為FunType函數(shù)指針類型,而不是一個(gè)FunType變量。

          //形式同 typedef int* PINT;

          typedef void(*FunType)(int);

          void myFun(int x);
          void hisFun(int x);
          void herFun(int x);
          void callFun(FunType fp,int x);
          int main()
          {
              callFun(myFun,100);//傳入函數(shù)指針常量,作為
              callFun(hisFun,200);
              callFun(herFun,300);
              return 0;
          }
          void callFun(FunType fp,int x)
          {
              fp(x);//通過fp的指針執(zhí)行傳遞進(jìn)來的函數(shù),注意fp所指的函數(shù)有一個(gè)參數(shù)
          }
          void myFun(int x)
          {
              printf("myFun: %dn",x);
          }
          void hisFun(int x)
          {
              printf("hisFun: %dn",x);
          }
          void herFun(int x)
          {
              printf("herFun: %dn",x);
          }

          輸出:

          捕獲.PNG

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

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

          void (* func5(intintfloat))(intint)
          {
              ...
          }

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

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

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

          /* 方法 1 */
          void (*func_array_1[5])(intintfloat);
          /* 方法 2 */
          typedef void (*p_func_array)(intintfloat);
          p_func_array func_array_2[5];

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

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

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

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

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

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

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

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

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

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

          捕獲.PNG

          假設(shè)我們要使用一個(gè)排序函數(shù)來對(duì)數(shù)組進(jìn)行排序,那么在主程序(Main program)中,我們先通過庫(kù),選擇一個(gè)庫(kù)排序函數(shù)(Library function)。但排序算法有很多,有冒泡排序,選擇排序,快速排序,歸并排序。

          同時(shí),我們也可能需要對(duì)特殊的對(duì)象進(jìn)行排序,比如特定的結(jié)構(gòu)體等。庫(kù)函數(shù)會(huì)根據(jù)我們的需要選擇一種排序算法,然后調(diào)用實(shí)現(xiàn)該算法的函數(shù)來完成排序工作。這個(gè)被調(diào)用的排序函數(shù)就是回調(diào)函數(shù)(Callback function)。

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

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

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

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

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

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

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


          簡(jiǎn)而言之,回調(diào)函數(shù)就是允許用戶把需要調(diào)用的方法的指針作為參數(shù)傳遞給一個(gè)函數(shù),以便該函數(shù)在處理相似事件的時(shí)候可以靈活的使用不同的方法。

          捕獲.PNG

          int Callback()    // /< 回調(diào)函數(shù)
          {
              // TODO
              return 0;
          }
          int main()     // /<  主函數(shù)
          {
              // TODO
              Library(Callback);  // /< 庫(kù)函數(shù)通過函數(shù)指針進(jìn)行回調(diào)
              // TODO
              return 0;
          }

          回調(diào)似乎只是函數(shù)間的調(diào)用,和普通函數(shù)調(diào)用沒啥區(qū)別。

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

          這樣一來,只要我們改變傳進(jìn)庫(kù)函數(shù)的參數(shù),就可以實(shí)現(xiàn)不同的功能,這樣有沒有覺得很靈活?并且當(dāng)庫(kù)函數(shù)很復(fù)雜或者不可見的時(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ù)是一個(gè)指針,在  main() 函數(shù)里調(diào)用 Handle() 函數(shù)的時(shí)候,給它傳入了函數(shù)  Callback_1()/Callback_2()/Callback_3() 的函數(shù)名,這時(shí)候的函數(shù)名就是對(duì)應(yīng)函數(shù)的指針,也就是說,回調(diào)函數(shù)其實(shí)就是函數(shù)指針的一種用法。

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

          #include 
          #include 
          /****************************************
           * 函數(shù)指針結(jié)構(gòu)體
           ***************************************/

          typedef struct _OP 

          {

              float (*p_add)(floatfloat); 
              float (*p_sub)(floatfloat); 
              float (*p_mul)(floatfloat); 
              float (*p_div)(floatfloat); 
          } 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;
          }
          /****************************************
           * 庫(kù)函數(shù)
           ***************************************/

          float add_sub_mul_div(float a, float b, float (*op_func)(floatfloat))
          {
              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 = %fn", (op->p_add)(1.32.2), (*op->p_sub)(1.32.2), 
                      (op->p_mul)(1.32.2), (*op->p_div)(1.32.2));
               
              /* 調(diào)用回調(diào)函數(shù) */ 
              printf("ADD = %f, SUB = %f, MUL = %f, DIV = %fn"
                      add_sub_mul_div(1.32.2, ADD), 
                      add_sub_mul_div(1.32.2, SUB), 
                      add_sub_mul_div(1.32.2, MUL), 
                      add_sub_mul_div(1.32.2, DIV));
              return 0
          }

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

          一個(gè) GPRS 模塊聯(lián)網(wǎng)的小項(xiàng)目,使用過的同學(xué)大概知道 2G、4G、NB 等模塊要想實(shí)現(xiàn)無線聯(lián)網(wǎng)功能都需要經(jīng)歷模塊上電初始化、注冊(cè)網(wǎng)絡(luò)、查詢網(wǎng)絡(luò)信息質(zhì)量、連接服務(wù)器等步驟,這里的的例子就是,利用一個(gè)狀態(tài)機(jī)函數(shù)(根據(jù)不同狀態(tài)依次調(diào)用不同實(shí)現(xiàn)方法的函數(shù)),通過回調(diào)函數(shù)的方式依次調(diào)用不同的函數(shù),實(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)機(jī)
              {GPRS_NETWORK_OPEN,  M26_PWRKEY_On  },      //模塊開機(jī)
              {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)度中心回復(fù) 
              {GPRS_NETWORK_LINK_FEM, M26_LINK_FEM  },    //連接前置機(jī)
              {GPRS_NETWORK_WAIT_FEM, M26_WAIT_FEM  },    //等待前置機(jī)回復(fù)
              {GPRS_NETWORK_COMM,  M26_COMM   },          //正常工作    
              {GPRS_NETWORK_WAIT_Sig,  M26_WAIT_Sig  },   //等待信號(hào)回復(fù)
              {GPRS_NETWORK_GetSignal,  M26_GetSignal  }, //獲取信號(hào)值
              {GPRS_NETWORK_RESTART,  M26_RESET   },      //模塊重啟
          }
          /**********************************************
          ** >M26模塊工作狀態(tài)機(jī),依次調(diào)用里面的12個(gè)函數(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;
          }

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

          原文:https://blog.csdn.net/qq_41854911/article/details/121058935

          文章來源于網(wǎng)絡(luò),版權(quán)歸原作者所有,如有侵權(quán),請(qǐng)聯(lián)系刪除。



          關(guān)鍵詞: C語言 回調(diào)函數(shù)

          評(píng)論


          相關(guān)推薦

          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();