1. 程式人生 > >Linux C 函式指標應用---回撥函式

Linux C 函式指標應用---回撥函式

    (這裡引用了知乎上一些知友的回答,感覺不錯,有助於理解,這裡引用作為借鑑,如有冒犯,煩請告知)

    我們先來回顧一下函式指標,函式指標是專門用來存放函式地址的指標,函式地址是一個函式的入口地址,函式名代表了函式的入口地址。當一個函式指標指向了一個函式,就可以通過這個指標來呼叫該函式,可以將函式作為引數傳遞給函式指標。

    那函式指標在我們實際程式設計中會起到怎樣的作用呢?前一篇關於函式指標的文章中,我們已經提到執行緒建立、訊號註冊中都用到了函式指標,下面我們將介紹函式指標的一個經典應用---回撥函式。

    看一看百度百科如何定義的回撥函式:

    回撥函式就是一個通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用於對該事件或條件進行響應。

     簡單的講,一般寫程式是你呼叫系統的API,如果把關係反過來,你寫一個函式,讓系統呼叫你的函式,那就是回調了,那個被系統呼叫的函式就是回撥函式

     說詳細點,我們知道,程式設計分為兩類:系統程式設計(system programming)和應用程式設計(application programming)。所謂系統程式設計,簡單來說,就是編寫;而應用程式設計就是利用寫好的各種庫來編寫具某種功用的程式,也就是應用。系統程式設計師會給自己寫的庫留下一些介面,即API(application programming interface,應用程式設計介面),以供應用程式設計師使用。所以在抽象層的圖示裡,庫位於應用的底下。

     當程式跑起來時,一般情況下,應用程式(application program)會時常通過API呼叫庫裡所預先備好的函式。但是有些庫函式(library function)卻要求應用先傳給它一個函式,好在合適的時候呼叫,以完成目標任務。這個被傳入的、後又被呼叫的函式就稱為回撥函式(callback function)。

     我們可以這樣理解:有一家旅館提供叫醒服務,但是要求旅客自己決定叫醒的方法。可以是打客房電話,也可以是派服務員去敲門,睡得死怕耽誤事的,還可以要求往自己頭上澆盆水。這裡,“叫醒”這個行為是旅館提供的,相當於庫函式,但是叫醒的方式是由旅客決定並告訴旅館的,也就是回撥函式。而旅客告訴旅館怎麼叫醒自己的動作,也就是把回撥函式傳入庫函式的動作,稱為登記回撥函式

(to register a callback function)。如下圖所示:

這樣看是不是都暈了,沒事,我們再來一次總結:

回撥函式通俗的解釋:

普通函式:你所寫的函式呼叫系統函式,你只管呼叫,不管實現。

回撥函式:系統呼叫你所寫的函式,你只管實現,不管呼叫。

那回調函式到底是如何使用的呢?我們先來解決個小問題:

1、回撥函式在什麼場景有用?
我要在特定時候執行一個任務,至於是什麼時候我自己都不知道。比如某一時間到了或者某一事件發生或者某一中斷觸發。

2、回撥函式怎麼起作用?
把我要執行的這個任務寫成一個函式,將這個函式和某一時間或者事件或者中斷建立關聯。當這個關聯完成的時候,這個函式華麗的從普通函式變身成為回撥函式。

3、回撥函式什麼時候執行?
當該回撥函式關心的那個時間或者事件或者中斷觸發的時候,回撥函式將被執行。
一般是觸發這個時間、事件或中斷的程式主體(通常是個函式或者物件)觀察到有一個關注這個東東的回撥函式的時候,這個主體負責呼叫這個回撥函式。

4、回撥函式有什麼好處?
最大的好處是你的程式變成非同步了。也就是你不必再呼叫這個函式的時候一直等待這個時間的到達、事件的發生或中斷的發生(萬一一直不發生,你的程式會怎麼樣?)。再此期間你可以做做別的事情,或者四處逛逛。當回撥函式被執行時,你的程式重新得到執行的機會,此時你可以繼續做必要的事情了。

    借鑑知友的一個例子:

你去食堂打飯,你喜歡吃小炒熱飯菜,所以你去了一個小炒視窗。
你跟老闆說了要×××蓋飯,老闆說:你是100號,喊到你的號你就來拿菜。
然後你在旁邊跟同學吹牛、或者看手機、或者乾點你想幹的任何事情。。。
然後你聽到老闆喊100號並且把菜放到視窗,你走到視窗,拿到你的菜。

這裡面有幾個函式:
老闆的部分:
1、老闆提供一個點餐的函式 boss.Order(string 菜名,double 錢)
2、老闆有個做飯的函式,此函式耗時較長boss.Cook()
3、老闆提供一個事件,當boss.cook()執行完時,該事件被觸發,boss.OnCookFinish;

你的部分:
1、你需要有一個函式去訂餐,也就是你的函式中需要執行類似於boss.Order("紅燒肉蓋澆飯",20),比如是me.Hungry()
2、你需要有一個函式作為回撥函式去關注boss.OnCookFinish事件,這樣當老闆做好飯,你就可以知道是不是你的好了。
由於老闆的事件發生的時候中會喊編號並且吧菜放到視窗,所以你的回撥函式需要能夠接受1個編號和1個菜作為引數。
比如me.AcceptFood(int currNumber,object food)

所以整個程式的流程其實是這樣的:

me.Hungry()
{
	boss.Order("紅燒肉蓋澆飯",20);
	boss.OnCookFinish+=me.AcceptFood;//此處表面,AcceptFood這個回撥函式關心OnCookFinish事件,並且變成這個事件的回撥函式
	//此時這個函式執行完,不再等待
}

boss.Order("紅燒肉蓋澆飯",20)
{
	//收錢
	//配菜 前2個耗時較短
	boss.Cook();//此處一般會開新執行緒執行cook動作
}

boss.Cook()
{
	//cooking~~~~~~~~~~
	//完成了,下面將要觸發事件,系統將檢查這個事件是否有回撥函式關心,有的話逐個回撥。
	OnCookFinish(100號,紅燒肉蓋澆飯);
}

至此案例基本完成了一個完整的任務流程。

終於到我們的例項環節了!
回撥函式例項一:

#include<stdio.h>

//函式指標的格式為:int (*ptr)(char *p) 即:返回值(指標名)(引數列表)
typedef int (*CallBackFun)(char *p);    //為回撥函式命名,型別命名為CallBackFun,引數為char *p
                                                                                                                                    //函式 Afun,格式符合 CallBackFun 的格式,因此可以看作是一個 CallBackFun   
int Afun(char *p)
{
	printf("Afun 回撥打印出字元%s!\n", p);   
	return 0;
}

//函式Cfun,格式符合 CallBackFun 的格式,因此可以看作是一個CallBackFun
int Cfun(char *p)
{   
	printf("Cfun 回撥列印:%s, Nice to meet you!\n", p);   
	return 0;
}


//執行回撥函式,方式一:通過命名方式,pCallBack可以看做是CallBackFun的別名
int call(CallBackFun pCallBack, char *p)
{   
	printf("call 直接打印出字元%s!\n", p);   
	pCallBack(p);   
	return 0;
}

// 執行回撥函式,方式二:直接通過方法指標    
int call2(char *p, int (*ptr)())  //或者是int call2(char *p, int (*ptr)(char *))同時ptr可以任意取名
{
	printf("======================================\n");    
	(*ptr)(p);
}

int main()
{   
	char *p = "hello";
	call(Afun, p);   
	call(Cfun, p);
	call2(p, Afun);   
	call2(p, Cfun);
         return 0;
}

執行結果如下:

fs@ubuntu:~/qiang/huidiao$ ./huidiao1
call 直接打印出字元hello!
Afun 回撥打印出字元hello!
call 直接打印出字元hello!
Cfun 回撥列印:hello, Nice to meet you!
======================================
Afun 回撥打印出字元hello!
======================================
Cfun 回撥列印:hello, Nice to meet you!
fs@ubuntu:~/qiang/huidiao$ ./huidiao1

回撥函式應用例項二:

#include <stdio.h>

typedef void (*callback)(char *);

void repeat(callback function, char *para)
{
	function(para);
	function(para);
}

void hello(char* a)
{
	printf("Hello %s\n",(const char *)a);
}

void count(char *num)
{	
	int i;
	for(i = 1;i < (int)num;i++)
		printf("%d",i);
	putchar('\n');
}

int main(void)
{
	repeat(hello,"xiaoqiang");
	repeat(count, (char *)4);
}

執行結果如下:

fs@ubuntu:~/qiang/huidiao$ ./huidiao2
Hello xiaoqiang
Hello xiaoqiang
123
123
fs@ubuntu:~/qiang/huidiao$ 

希望對大家有幫助,謝謝!