日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
手把手教你寫函數(shù)指針與回調(diào)函數(shù)

在我們平時開發(fā)STM32或者其它單片機時,我們經(jīng)常都會用到原廠提供的固件庫函數(shù),固件庫函數(shù)中有非常多回調(diào)函數(shù)。那么什么是回調(diào)函數(shù)呢?回調(diào)函數(shù)是作為參數(shù)傳遞給另一個函數(shù)的函數(shù)。接受回調(diào)作為參數(shù)的函數(shù)預計會在某個時間點執(zhí)行它?;卣{(diào)機制允許下層軟件層調(diào)用上層軟件層定義的函數(shù)。

成都創(chuàng)新互聯(lián)公司專注于汕城企業(yè)網(wǎng)站建設,響應式網(wǎng)站設計,電子商務商城網(wǎng)站建設。汕城網(wǎng)站建設公司,為汕城等地區(qū)提供建站服務。全流程定制網(wǎng)站,專業(yè)設計,全程項目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務

應用程序代碼和硬件驅(qū)動程序之間的交互

硬件驅(qū)動程序是一個獨立的可重用驅(qū)動程序,它不了解上面的層(用戶應用程序)。硬件驅(qū)動程序提供API函數(shù),允許用戶應用程序?qū)⒑瘮?shù)注冊為回調(diào)。然后,此回調(diào)函數(shù)由硬件驅(qū)動程序作為執(zhí)行的一部分進行調(diào)用。如果不使用回調(diào),就會被編碼為直接調(diào)用。這將使硬件驅(qū)動程序特定于特定的高級軟件級別,并降低其可重用性。回調(diào)機制的另一個好處是,在程序執(zhí)行期間可以動態(tài)更改被調(diào)用的回調(diào)函數(shù)。

一、函數(shù)指針

函數(shù)指針,顧名思義它就是一個指針,只不過它是一個函數(shù)指針,所以指向的是一個函數(shù)。類比一般的變量指針,指針變量,實質(zhì)上是一個變量,只不過這個變量存放的是一個地址,在32位單片機中,任何類型的指針變量都存放的是一個大小為4字節(jié)的地址。

int   a; < = > void cal_sum(void);
int * p; < = > void (*func_ptr)(void);
p=&a; < = > func_ptr= &cal_sum;

左邊走義變量a,右邊定義函數(shù)cal_sum;

左邊定義int指針,右邊定義func_ptr;

左邊賦值指針,右邊賦值函數(shù)指針;

可能這樣大家還是不太清楚,我是搞嵌入式單片機的,有本事你在Keil中給我舉一個例子啊?

可以啊,沒問題,請看!

#include "sys.h"
#include "led.h"
#include "delay.h"
#include "usart.h"
uint8_t cal_sum(uint8_t a, uint8_t b)
{
return a + b;
}
int main(void)
{
delay_init();
uart_init(9600);
printf("www.zhiguoxin.cn\r\n");
printf("微信公眾號:果果小師弟\r\n");
uint8_t a = 10;
uint8_t b = 8;
/*定義一個函數(shù)指針*/
uint8_t (*func_ptr)(uint8_t, uint8_t);
/*將函數(shù)名賦值給函數(shù)指針*/
func_ptr = cal_sum;
printf("cal_sum_address =0x%p\r\n", cal_sum);
printf("func_ptr_address =0x%p\r\n", func_ptr);
printf("%d + %d = %d\r\n", a, b, cal_sum(a, b));
printf("%d + %d = %d\r\n", a, b, func_ptr(a, b));
while(1)
{
}
}

這樣寫大家應該很熟悉吧,我首先定義了一個函數(shù)指針func_ptr,接著將我寫得cal_sum函數(shù)賦值給了函數(shù)指針func_ptr 。然后分別打印函數(shù)cal_sum的地址,函數(shù)指針func_ptr的地址,以及使用cal_sum計算出來的值,和函數(shù)值指針func_ptr計算出來的值。

那么結(jié)果是啥樣呢?

可以發(fā)現(xiàn)函數(shù)指針func_ptr和cal_sum函數(shù)的存儲的地址以及他們所計算出來的值是一樣的。

比如在上面求兩個數(shù)和的基礎上再求兩個數(shù)的乘積和差,會是啥樣的呢?

代碼是這樣的

#include "sys.h"
#include "led.h"
#include "delay.h"
#include "usart.h"
uint8_t cal_sum(uint8_t a, uint8_t b)
{
return a + b;
}
uint8_t cal_sub(uint8_t a, uint8_t b)
{
return a - b;
}
uint8_t cal_mul(uint8_t a, uint8_t b)
{
return a * b;
}
int main(void)
{
delay_init();
uart_init(9600);
printf("www.zhiguoxin.cn\r\n");
printf("微信公眾號:果果小師弟\r\n");
uint8_t a = 10;
uint8_t b = 8;
/*定義一個函數(shù)指針*/
uint8_t (*func_ptr)(uint8_t, uint8_t);
/*將函數(shù)名賦值給函數(shù)指針*/
func_ptr = cal_sum;
printf("cal_sum_address =0x%p\r\n", cal_sum);
printf("func_ptr_address =0x%p\r\n", func_ptr);
printf("%d + %d = %d\r\n", a, b, cal_sum(a, b));
printf("%d + %d = %d\r\n\n", a, b, func_ptr(a, b));
/*將函數(shù)名賦值給函數(shù)指針*/
func_ptr = cal_sub;
printf("cal_sub_address =0x%p\r\n", cal_sub);
printf("func_ptr_address =0x%p\r\n", func_ptr);
printf("%d - %d = %d\r\n", a, b, cal_sub(a, b));
printf("%d - %d = %d\r\n\n", a, b, func_ptr(a, b));
/*將函數(shù)名賦值給函數(shù)指針*/
func_ptr = cal_mul;
printf("cal_mul_address =0x%p\r\n", cal_mul);
printf("func_ptr_address =0x%p\r\n", func_ptr);
printf("%d * %d = %d\r\n", a, b, cal_mul(a, b));
printf("%d * %d = %d\r\n", a, b, func_ptr(a, b));
while(1)
{
}
}

截個圖看的更清楚一點

串口打印結(jié)果:

指向函數(shù)的指針被稱作是函數(shù)指針。通過函數(shù)指針,我們可以靈活的調(diào)用各種形式相同,但是功能不同的函數(shù)這樣做大大的增加了代碼的靈活程度。

1、typedef 函數(shù)指針

我們在定義一個函數(shù)指針時常常會這樣寫

uint8_t (*func_ptr)(void);

比較好理解,但是下面這個就不好理解了

typedef uint8_t (*func_ptr) (void);

是不是看著有點懵,因為一般的typedef是這樣用的

typedef  原類型 別名

用法:

#include
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef uint8_t zhiguoxin;
void main()
{
printf("www.zhiguoxin.cn\n");
printf("微信公眾號:果果小師弟\n\n");
zhiguoxin a =10;
printf("a=%d\n",a);
}

使用nodepad++編譯一下

然后在keil中試驗

那這樣是啥意思呢?

typedef uint8_t (*func_ptr) (void);

這里是把定義了一個別名叫(*func_ptr) (void) 的嗎,顯然不對,其含義是:

上面的例子定義func_ptr是一個函數(shù)指針, 函數(shù)類型是不帶形參, 返回參數(shù)是uint8_t。

要定義的類型是uint8_t (*)(void),沒有輸入?yún)?shù),返回值為uint8_t 的函數(shù)指針,定義的別名是func_ptr。

在分析這種形式的定義的時候可以這樣看:先去掉typedef和別名, 剩下的就是原變量的類型。去掉typedef和func_ptr以后就剩:uint8_t (*)(void)。

2.為啥使用typedef定義函數(shù)指針

答:typedef定義的函數(shù)指針類型是比較方便和明了的,因為typedef實際上就是定義一個新的數(shù)據(jù)類型,typedef有這樣的一個作用,就可以用它來定義函數(shù)指針類型,這個定義的函數(shù)指針類型是能夠指向返回值是uint8_t的,并且函數(shù)的參數(shù)是void類型。

這里定義的typedef uint8_t (*func_ptr) (void);;就相當于把uint8_t (*) (void); 定義成了另一個別名 func_ptr了。這個func_ptr就表示了函數(shù)指針類型。

注意:這里的uint8_t (*) (void);實際上不存在這樣的寫法,只是為了方便理解,這樣的寫法是不允許的,也是錯誤的!這樣的寫法并不代表是一個類型!

C語言真是博大精深!

3.函數(shù)指針常規(guī)定義

如果不使用typedef就應該這樣定義:

#include "sys.h"
#include "led.h"
#include "delay.h"
#include "usart.h"
uint8_t cal_sum(uint8_t a, uint8_t b)
{
return a + b;
}
int main(void)
{
delay_init();
uart_init(9600);
printf("www.zhiguoxin.cn\r\n");
printf("微信公眾號:果果小師弟\r\n");
uint8_t a = 10;
uint8_t b = 8;
/*定義一個函數(shù)指針*/
uint8_t (*func_ptr)(uint8_t, uint8_t);
/*將函數(shù)名賦值給函數(shù)指針*/
func_ptr = cal_sum;
printf("%d + %d = %d\r\n", a, b, func_ptr(a, b));
while(1)
{
}
}

在keil中測試:

4.函數(shù)指針typedef定義

如果使用typedef就應該這樣定義:

#include "sys.h"
#include "led.h"
#include "delay.h"
#include "usart.h"
uint8_t cal_sum(uint8_t a, uint8_t b)
{
return a + b;
}
int main(void)
{
delay_init();
uart_init(9600);
printf("www.zhiguoxin.cn\r\n");
printf("微信公眾號:果果小師弟\r\n");
uint8_t a = 10;
uint8_t b = 8;
/*定義一個函數(shù)指針*/
typedef uint8_t (*func_ptr)(uint8_t, uint8_t);
/*聲明了一個函數(shù)指針變量 pFun*/
func_ptr pFun;
/*將這個pFun指向了cal_sum函數(shù)*/
pFun = cal_sum;
printf("%d + %d = %d\r\n", a, b, pFun(a, b));
while(1)
{
}
}

為啥要這樣?為啥要使用typedef來定義函數(shù)指針?其實這個也是類比結(jié)構體的操作,在結(jié)構體中我們也常常給結(jié)構體起別名。

綜上所述:定義函數(shù)指針就有了兩種方法

/* 方法1 */
uint8_t (*func_ptr)(uint8_t, uint8_t) = NULL;
/* 方法2 */
typedef uint8_t (*func_ptr)(uint8_t, uint8_t);;
func_ptr pFun = NULL;

函數(shù)指針也有兩種賦值方法:

uint8_t (*func_ptr)(uint8_t, uint8_t) = NULL;
/* 方法1 */
func_ptr= &cal_sum;
/* 方法2 */
func_ptr= cal_sum;

上面兩種方法都是合法的。其實函數(shù)名就是函數(shù)的地址,你將函數(shù)名cal_sum賦值給函數(shù)指針func_ptr,與將函數(shù)的地址&cal_sum賦值給函數(shù)指針func_ptr是一樣的。

同樣調(diào)用函數(shù)也有兩種方法:

/* 方法1 */
func_ptr(a,b)
/* 方法2 */
(*func_ptr)(a,b)

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

既然函數(shù)指針和去普通的指針一樣,普通的指針可以作為函數(shù)的形參,那么函數(shù)指針是不是也可以作為函數(shù)的形參呢?

答:是的,肯定可以。

那么函數(shù)指針作為函數(shù)的形參我們把這個函數(shù)指針叫啥呢?

答:回調(diào)函數(shù)。

回調(diào)函數(shù)原來是這樣得來的啊,學到了!

能不能舉一個簡單的例子呢?

uint8_t compute_func(uint8_t, uint8_t);

首先我們這樣寫一個函數(shù)是沒有問題的,但是我們現(xiàn)在要將函數(shù)指針作為函數(shù)的形參,這樣合法嗎?

uint8_t compute_func(uint8_t (*func_ptr)(uint8_t, uint8_t),uint8_t, uint8_t);

編譯一下

發(fā)現(xiàn)沒有錯誤也沒有警告,說明我們把函數(shù)指針當做函數(shù)的形參是沒有任何問題的。

在這個函數(shù)當中,通過該函數(shù)指針調(diào)用的函數(shù)被稱為回調(diào)函數(shù)。這種開發(fā)方式的用途非常廣泛。具體來說,在回調(diào)函數(shù)的應用場景當中,會出現(xiàn)兩個角色。分別是某功能函數(shù)的開發(fā)者以及該功能函數(shù)的使用者。compute_func函數(shù)就是開發(fā)者寫的函數(shù),是非常牛逼的寫庫和底層的那一類人寫的函數(shù),我們每一個單片機的使用者,需要寫出各種各樣的具體的功能函數(shù),只要我們寫得功能函數(shù)的形參和返回值和函數(shù)指針的類型相同就可以了。

怎么理解?

#include "sys.h"
#include "delay.h"
#include "usart.h"
/*使用者寫的函數(shù)*/
uint8_t cal_sum(uint8_t a, uint8_t b)
{
return a + b;
}
/*開發(fā)者寫的函數(shù)*/
uint8_t (compute_func)(uint8_t (*func_ptr)(uint8_t, uint8_t), uint8_t a, uint8_t b)
{
return func_ptr(a, b);
}
int main(void)
{
delay_init();
uart_init(9600);
printf("www.zhiguoxin.cn\r\n");
printf("微信公眾號:果果小師弟\r\n");
uint8_t a = 10;
uint8_t b = 8;
printf("compute_func(cal_sum,a,b) =%d\r\n", compute_func(cal_sum, a, b));
while(1)
{
}
}

注意:這里要注意的是我們使用者寫的函數(shù)的類型一定要于開發(fā)者寫的回調(diào)函數(shù)類型一樣,比如形參和返回值的類型要一樣。不然肯定不能調(diào)用的。

換句話說就是,下面的這兩個函數(shù)的形參和返回值都必須是相同的類型才可以,不能一個有返回值一個沒有,明明函數(shù)指針有兩個形參,你寫的函數(shù)卻只有一個形參也是不行的。

正確寫法:
uint8_t cal_mul(uint8_t , uint8_t )
uint8_t (*func_ptr)(uint8_t, uint8_t)
錯誤寫法:
void cal_mul(uint8_t , uint8_t ) //你寫的函數(shù)卻沒有返回值
uint8_t (*func_ptr)(uint8_t, uint8_t)//函數(shù)指針有返回值
錯誤寫法:
uint8_t cal_mul(uint8_t) //你寫的函數(shù)卻只有一個形參
uint8_t (*func_ptr)(uint8_t, uint8_t)//函數(shù)指針有兩個形參

我們來驗證一下:

#include "sys.h"
#include "led.h"
#include "delay.h"
#include "usart.h"
/*使用者寫的函數(shù)*/
uint8_t cal_sum(uint8_t a, uint8_t b)
{
return a + b;
}
/*使用者寫的函數(shù)*/
void cal_sub(uint8_t a, uint8_t b)
{
printf("666");
}
/*使用者寫的函數(shù)*/
uint8_t cal_mul( uint8_t a)
{
return a;
}
/*開發(fā)者寫的函數(shù)*/
uint8_t (compute_func)(uint8_t (*func_ptr)(uint8_t, uint8_t), uint8_t a, uint8_t b)
{
return func_ptr(a, b);
}
int main(void)
{
delay_init();
uart_init(9600);
printf("www.zhiguoxin.cn\r\n");
printf("微信公眾號:果果小師弟\r\n");
uint8_t a = 10;
uint8_t b = 8;
printf("%d\r\n", compute_func(cal_sum, a, b));
printf("%d\r\n", compute_func(cal_sub, a, b));
printf("%d\r\n", compute_func(cal_mul, a, b));
while(1)
{
}
}

看到了在keil中編譯器不會報錯,但是會報警告。因為在keil中編譯做了優(yōu)化。那么如果我們gcc記事本編譯一下會是啥樣的呢?

會發(fā)現(xiàn)同樣會有兩個警告,但是還是可以運行。

如何理解回調(diào)函數(shù)

有時候會遇到這樣一種情況,當上層人員將一個功能交給下層程序員完成時,上層程序員和下層程序員同步工作,這個時候該功能函數(shù)并未完成,這個時候上層程序員可以定義一個API來交給下層程序員,而上層程序員只要關心該API就可以了而無需關心具體實現(xiàn),具體實現(xiàn)交給下層程序員完成即可(這里的上層和下層程序員不指等級關系,而是項目的分工關系)。這種情況下就會用到回調(diào)函數(shù)(Callback Function),現(xiàn)在假設程序員A需要一個FFT算法,這個時候程序員A將FFT算法交給程序員B來完成,現(xiàn)在來讓實現(xiàn)這個過程:

#include 
int InputData[100]={0};
int OutputData[100]={0};
/*定義回調(diào)函數(shù)*/
void CallBack_FFT_Function(int *inputData,int *outputData,int num)
{
while(num--)
{
printf("www.zhiguoxin.cn\r\n");
}
}
/*用來注冊回調(diào)函數(shù)的功能函數(shù)*/
void TaskA(void (*fft)(int*,int*,int))
{
fft(InputData,OutputData,5);
}
int main(void)
{
/*注冊FFT_Function作為回調(diào)*/
TaskA(CallBack_FFT_Function);
return 0;
}

這個例子是不是跟上面的那個例子是相同的,只是我們在這里換了一種說法而已。也就是我們硬件層實現(xiàn)的某個功能,當然可以在應用層直接調(diào)用,但是這種做法太low了,一看就是小學生的水平,或者說硬件層的東西應用層根本不需要關心,這就是分層的思想。硬件的東西就給硬件工程師做,應用工程師只關心自己的需要實現(xiàn)的任務。這也就是驅(qū)動工程師和應用工程師的區(qū)別,我硬件工程師只需要寫好對應的API函數(shù),你應用層直接調(diào)用就好了,你不需要關心這個API函數(shù)的內(nèi)部是怎么實現(xiàn)的。而這兩者之間的橋梁就是回調(diào)函數(shù)。而回調(diào)函數(shù)的形參就是函數(shù)指針,所以本篇最開始講的是函數(shù)指針,只要你函數(shù)指針明白了,你就會寫回調(diào)函數(shù),也就理解了這其中到底只一個什么原理。

果果小師弟

上面的代碼中CallBack_FFT_Function是回調(diào)函數(shù),該函數(shù)的形參為一個函數(shù)指針,TaskA是用來注冊回調(diào)函數(shù)的功能函數(shù)。可以看到用來注冊回調(diào)函數(shù)的功能函數(shù)中申明的函數(shù)指針必須和回調(diào)函數(shù)的類型完全相同。

函數(shù)指針結(jié)構體

但是很多時候我們一般在結(jié)構體中定義函數(shù)指針用的比較多一點。下面再舉一個簡單的例子。

#include "sys.h"
#include "led.h"
#include "delay.h"
#include "usart.h"
/****************************************
* 函數(shù)指針結(jié)構體 開發(fā)者寫的結(jié)構體
***************************************/
typedef struct
{
uint8_t (*p_sum)(uint8_t, uint8_t);
uint8_t (*p_sub)(uint8_t, uint8_t);
uint8_t (*p_mul)(uint8_t, uint8_t);
float (*p_div)(uint8_t, uint8_t);
} Operation_T;
/*聲明結(jié)構體變量g_Operation*/
Operation_T g_Operation;
/*使用者寫的回調(diào)函數(shù)*/
uint8_t cal_sum(uint8_t a, uint8_t b)
{
return a + b;
}
/*使用者寫的回調(diào)函數(shù)*/
uint8_t cal_sub(uint8_t a, uint8_t b)
{
return a - b;
}
/*使用者寫的回調(diào)函數(shù)*/
uint8_t cal_mul( uint8_t a, uint8_t b)
{
return a * b;
}
/*使用者寫的回調(diào)函數(shù)*/
float cal_div(uint8_t a, uint8_t b)
{
return a / b;
}
/*結(jié)構體變量g_Operation初始化*/
Operation_T g_Operation = {cal_sum, cal_sub, cal_mul, cal_div};
int main(void)
{
delay_init();
uart_init(9600);
printf("www.zhiguoxin.cn\r\n");
printf("微信公眾號:果果小師弟\r\n");
uint8_t a = 10;
uint8_t b = 8;
/*使用函數(shù)指針調(diào)用函數(shù)*/
printf("%d\r\n", g_Operation.p_sum(a, b));
printf("%d\r\n", g_Operation.p_sub(a, b));
printf("%d\r\n", g_Operation.p_mul(a, b));
printf("%f\r\n", g_Operation.p_div(a, b));
while(1)
{
}
}

三、回調(diào)在嵌入式系統(tǒng)中的實際使用

回調(diào)可用于多種情況,并廣泛用于嵌入式固件開發(fā)。它們提供了更大的代碼靈活性,并允許我們開發(fā)可由最終用戶進行微調(diào)而無需更改代碼的驅(qū)動程序。

在我們的代碼中具有回調(diào)功能所需的元素是:

  • 將被調(diào)用的回調(diào)函數(shù)cal_sum
  • 將用于訪問回調(diào)函數(shù)的函數(shù)指針p_sum
  • 將調(diào)用回調(diào)函數(shù)的調(diào)用函數(shù)compute_func

在stm32的HAL庫中,是使用了大量的回調(diào)函數(shù)的,串口、定時器等外設都是有對應的回調(diào)函數(shù)的,回調(diào)機制可以更好地分離代碼,應用層和驅(qū)動層完全分離,降低耦合性。

簡單來看幾個例子,串口回調(diào)函數(shù):

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

使用的時候,我們只需要把串口解析處理邏輯放在對應的回調(diào)函數(shù)中處理即可,拿串口接收來舉例,定義的是一個弱函數(shù),我們在自己的文件中重新實現(xiàn)就好。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
/*****Data Processing********/
}
else if (huart->Instance == USART2)
{
/*****Data Processing********/
}
}

又比如我們在OS中的創(chuàng)建任務的函數(shù)就是一個用來注冊回調(diào)函數(shù)的功能函數(shù), 本文題目:手把手教你寫函數(shù)指針與回調(diào)函數(shù)
分享鏈接:http://www.dlmjj.cn/article/dpggdpc.html