C/C++ 基礎(chǔ) 之回調(diào)函數(shù)示例詳解
前言
在寫項(xiàng)目的時(shí)候,對(duì)于回調(diào)函數(shù)一知半解,這次將重新學(xué)習(xí)一下,重新理解一下 回調(diào)函數(shù) 的魅力所在
回調(diào)函數(shù)預(yù)備知識(shí)
在講回調(diào)函數(shù) 回調(diào)函數(shù) 回調(diào)函數(shù)之前,我們需要了解函數(shù)指針。
我們都知道,C語(yǔ)言的靈魂是指針,我們經(jīng)常使用整型指針,字符串指針,結(jié)構(gòu)體指針等
函數(shù)指針
int *p1; // p1是一個(gè)指向整數(shù)(int)類型的指針變量,可以存儲(chǔ)一個(gè)int類型數(shù)據(jù)的地址 char *p2; // p2是一個(gè)指向字符(char)類型的指針變量,可以存儲(chǔ)一個(gè)char類型數(shù)據(jù)的地址 STRUCT *p3; // p3是一個(gè)指向結(jié)構(gòu)體類型STRUCT的指針變量,STRUCT是我們定義的結(jié)構(gòu)體類型
但是好像我們一般很少使用函數(shù)指針,我們一般使用函數(shù)都是直接使用函數(shù)調(diào)用。
下面我們來(lái)了解一下函數(shù)指針的概念和使用方法。
什么是函數(shù)指針
函數(shù)指針也是個(gè)指針,但是和通常的指針不一樣,通常的指針指向的是整型、字符型或數(shù)組等變量
而函數(shù)指針,指向的是函數(shù)
函數(shù)指針的語(yǔ)法
返回類型 (*指針變量名)(參數(shù)類型列表);
- 返回類型: 函數(shù)返回的數(shù)據(jù)類型(如 int double void 等)。
- 指針變量名: 你給這個(gè)函數(shù)指針起的名字。
- 參數(shù)類型列表: 函數(shù)接受的參數(shù)類型(如果沒(méi)有參數(shù),可以留空或?qū)?void )。
這里需要注意的是:(*指針變量名)兩端的括號(hào)不能省略,括號(hào)改變了運(yùn)算符的優(yōu)先級(jí)。如果省略了括號(hào),就不是定義函數(shù)指針。而是一個(gè)函數(shù)聲明了,即聲明了一個(gè)返回值類型為指針型的函數(shù)。
那么怎么判斷一個(gè)指針變量是指向變量的還是指向函數(shù)呢?
- 首先看變量名前面有沒(méi)有 “*”,如果有 “*” 說(shuō)明是指針變量;
- 其次看變量名有沒(méi)有帶 (),如果有就是指向函數(shù)的指針變量,即函數(shù)指針,如果沒(méi)有就是指向變量的指針變量。
最后需要注意的是:指向函數(shù)的指針變量沒(méi)有 ++ 和 – 運(yùn)算。
一般為了方便使用,我們會(huì)選擇用typedef進(jìn)行函數(shù)指針類型的別名定義
// 定義一個(gè)函數(shù)指針類型 別名為:Fun1,它指向返回 int 類型、接受一個(gè) int 參數(shù)的函數(shù) typedef int (*Fun1)(int); // 定義一個(gè)函數(shù)指針類型 別名為:Fun2,它指向返回 int 類型、接受兩個(gè)參數(shù)(int 和 int)的函數(shù) typedef int (*Fun2)(int, int); // 定義一個(gè)函數(shù)指針類型 別名為:Fun3,它指向返回 void 類型、無(wú)參數(shù)的函數(shù) typedef void (*Fun3)(void); // 定義一個(gè)函數(shù)指針類型 別名為:Fun4,它指向返回 void* 類型、接受一個(gè) void* 參數(shù)的函數(shù) typedef void* (*Fun4)(void*);
為什么要使用typedef呢?
如何用函數(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)過(guò)賦值以后,指針變量 p 就指向函數(shù) Func() 代碼的首地址了。
下面來(lái)寫一個(gè)程序,看了這個(gè)程序你們就明白函數(shù)指針怎么使用了:
特別注意的是,因?yàn)楹瘮?shù)名本身就可以表示該函數(shù)地址(指針),因此在獲取函數(shù)指針時(shí),可以直接用函數(shù)名,也可以用&取函數(shù)的地址。
函數(shù)指針作為函數(shù)的參數(shù)
我們見(jiàn)過(guò),函數(shù)的形參是指針的
// 函數(shù)接受一個(gè)整數(shù)指針作為參數(shù),并修改該值 void modifyValue(int *ptr) { *ptr = 20; // 修改指針指向的值 }
那么函數(shù)指針作為指針,肯定也能放到函數(shù)的形參中的
#include <stdio.h> // 定義一個(gè)函數(shù)類型:別名為operation,返回類型是int,參數(shù)類型是int和int typedef int (*operation)(int, int); // 一個(gè)加法函數(shù) int add(int a, int b) { return a + b; } // 一個(gè)減法函數(shù) int sub(int a, int b) { return a - b; } // 函數(shù) modifyValue 接受一個(gè)函數(shù)指針作為參數(shù),并調(diào)用它 void modifyValue(int *ptr, operation op) { *ptr = op(*ptr, 5); // 使用傳入的函數(shù)指針 op 來(lái)修改 ptr 指向的值 } int main() { int num = 10; printf("Before: %d\n", num); // 輸出修改前的值 // 傳遞加法函數(shù)的指針 modifyValue(&num, add); printf("After add: %d\n", num); // 輸出加法操作后的值 // 傳遞減法函數(shù)的指針 modifyValue(&num, sub); printf("After sub: %d\n", num); // 輸出減法操作后的值 return 0; }
運(yùn)行結(jié)果
為什么函數(shù)指針我們用的是別名operation,在下面調(diào)用modifyValue的時(shí)候,直接傳入add,sub就能識(shí)別了呢?
在C語(yǔ)言中,函數(shù)名(如add或sub)在沒(méi)有括號(hào)時(shí)會(huì)自動(dòng)轉(zhuǎn)換為指向該函數(shù)的指針。這與operation類型 的期望使用一個(gè)函數(shù)指針相匹配。
函數(shù)指針作為函數(shù)返回類型
有了上面的基礎(chǔ),要寫出返回類型為函數(shù)指針的函數(shù)應(yīng)該不難了,下面這個(gè)例子就是返回類型為函數(shù)指針的函數(shù):
void (* func5(int, int, float ))(int, int) { ... }
在這里, func5 以 (int, int, float) 為參數(shù),其返回類型為 void (\*)(int, int) 。
函數(shù)指針數(shù)組
在開(kāi)始講解回調(diào)函數(shù)前,最后介紹一下函數(shù)指針數(shù)組。既然函數(shù)指針也是指針,那我們就可以用數(shù)組來(lái)存放函數(shù)指針。下面我們看一個(gè)函數(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];
回調(diào)函數(shù)
什么是回調(diào)函數(shù)
我們先來(lái)看看百度百科是如何定義回調(diào)函數(shù)的:
回調(diào)函數(shù)就是一個(gè)通過(guò)函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個(gè)函數(shù),當(dāng)這個(gè)指針被用來(lái)調(diào)用其所指向的函數(shù)時(shí),我們就說(shuō)這是回調(diào)函數(shù)。回調(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時(shí)由另外的一方調(diào)用的,用于對(duì)該事件或條件進(jìn)行響應(yīng)。
這段話比較長(zhǎng),也比較繞口。下面我通過(guò)一幅圖來(lái)說(shuō)明什么是回調(diào):
假設(shè)我們要使用一個(gè)排序函數(shù)來(lái)對(duì)數(shù)組進(jìn)行排序,那么在 主程序(Main program) 中,我們先通過(guò)庫(kù),選擇一個(gè) 庫(kù)排序函數(shù)(Library function)。但排序算法有很多,有冒泡排序,選擇排序,快速排序,歸并排序。同時(shí),我們也可能需要對(duì)特殊的對(duì)象進(jìn)行排序,比如特定的結(jié)構(gòu)體等。庫(kù)函數(shù)會(huì)根據(jù)我們的需要選擇一種排序算法,然后調(diào)用實(shí)現(xiàn)該算法的函數(shù)來(lái)完成排序工作。這個(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ù)就可以通過(guò)這個(gè)指針來(lái)調(diào)用回調(diào)函數(shù)了。注意,回調(diào)函數(shù)并不是C語(yǔ)言特有的,幾乎任何語(yǔ)言都有回調(diào)函數(shù)。在C語(yǔ)言中,我們通過(guò)使用函數(shù)指針來(lái)實(shí)現(xiàn)回調(diào)函數(shù)。
總結(jié):把一段可執(zhí)行的代碼(函數(shù))像參數(shù)傳遞那樣傳給其他代碼,而這段代碼會(huì)在某個(gè)時(shí)刻被調(diào)用執(zhí)行,這就叫 回調(diào) 。如果代碼立即被執(zhí)行就稱為同步回調(diào),如果過(guò)后再執(zhí)行,則稱之為異步回調(diào)
回調(diào)函數(shù) 就是一個(gè)通過(guò)函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個(gè)函數(shù),當(dāng)這個(gè)指針被用來(lái)調(diào)用其所指向的函數(shù)時(shí),我們就說(shuō)這是 回調(diào)函數(shù) 。
回調(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時(shí)由另外的一方調(diào)用的,用于對(duì)該事件或條件進(jìn)行響應(yīng)。
為什么要用回調(diào)函數(shù)
因?yàn)榭梢园颜{(diào)用者與被調(diào)用者分開(kāi),所以調(diào)用者不關(guān)心誰(shuí)是被調(diào)用者。它只需知道存在一個(gè)具有特定原型和限制條件的被調(diào)用函數(shù)。
簡(jiǎn)而言之,回調(diào)函數(shù)就是允許用戶把需要調(diào)用的方法的指針作為參數(shù)傳遞給一個(gè)函數(shù),以便該函數(shù)在處理相似事件的時(shí)候可以靈活的使用不同的方法。
int Callback() ///< 回調(diào)函數(shù) { // TODO return 0; } int main() ///< 主函數(shù) { // TODO Library(Callback); ///< 庫(kù)函數(shù)通過(guò)函數(shù)指針進(jìn)行回調(diào) // TODO return 0; }
回調(diào)似乎只是函數(shù)間的調(diào)用,和普通函數(shù)調(diào)用沒(méi)啥區(qū)別。
但仔細(xì)看,可以發(fā)現(xiàn)兩者之間的一個(gè)關(guān)鍵的不同:在回調(diào)中,主程序把回調(diào)函數(shù)像參數(shù)一樣傳入庫(kù)函數(shù)。
這樣一來(lái),只要我們改變傳進(jìn)庫(kù)函數(shù)的參數(shù),就可以實(shí)現(xiàn)不同的功能,這樣有沒(méi)有覺(jué)得很靈活?并且當(dāng)庫(kù)函數(shù)很復(fù)雜或者不可見(jiàn)的時(shí)候利用回調(diào)函數(shù)就顯得十分優(yōu)秀。如果還不明白??匆幌孪旅娴睦泳蜁?huì)恍然大悟了
怎么使用回調(diào)函數(shù)
int Callback_1(int a) //回調(diào)函數(shù)1 { cout << "Hello, this is Callback_1。" << "a=" << a << endl; return 0; } int Callback_2(int b) //回調(diào)函數(shù)2 { cout << "Hello, this is Callback_2。" << "b=" << b << endl; return 0; } int Callback_3(int c) //回調(diào)函數(shù)3 { cout << "Hello, this is Callback_3。" << "c=" << c << endl; return 0; } int Handle(int x, int (*Callback)(int x)) { Callback(x); return 0; } int main() { Handle(10, Callback_1); Handle(20, Callback_2); Handle(30, Callback_3); return 0; }
可能同學(xué)會(huì)有一個(gè)疑問(wèn):Callback(x); 這里就一個(gè)函數(shù)是怎么做到識(shí)別Callback_1 2 3的呢?
Handle(10, Callback_1); Handle(20, Callback_2); Handle(30, Callback_3);本質(zhì)上是把Callback_1 2 3函數(shù)的地址傳給Handle函數(shù)。Handle函數(shù)里的Callback(x); 它存儲(chǔ)的是具體回調(diào)函數(shù)的內(nèi)存地址,比如說(shuō)你傳入的是 Handle(10, Callback_1);,那Callback存儲(chǔ)的是Callback_1的地址,存儲(chǔ)的x變量是10,它就會(huì)找到Callback_1并且把10傳給它
下面是一個(gè)四則運(yùn)算的簡(jiǎn)單回調(diào)函數(shù)例子:
typedef float (*Operation)(float, float); float ADD(float a, float b) { cout << "a+b=" << a + b << endl; return a + b; } float SUB(float a, float b) { cout << "a-b=" << a - b << endl; return a - b; } float MUL(float a, float b) { cout << "a*b=" << a * b << endl; return a * b; } float DIV(float a, float b) { if (b == 0) { printf("Error: Division by zero!\n"); return 0; } cout << "a/b=" << a / b << endl; return a / b; } float add_sub_mul_div(float a, float b, Operation op) { op(a, b); return 0; } int main() { add_sub_mul_div(1.1, 2.2, ADD); add_sub_mul_div(1.1, 2.2, SUB); add_sub_mul_div(1.1, 2.2, MUL); add_sub_mul_div(1.1, 2.2, DIV); return 0; }
總結(jié)
這下對(duì)于回調(diào)函數(shù)是更加理解了,希望各位在今后的學(xué)習(xí)中能夠看見(jiàn)回調(diào)函數(shù)不再迷惑!
到此這篇關(guān)于C/C++ 基礎(chǔ) 之回調(diào)函數(shù)示例詳解的文章就介紹到這了,更多相關(guān)c++ 回調(diào)函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言函數(shù)指針數(shù)組實(shí)現(xiàn)計(jì)算器功能
這篇文章主要通過(guò)C語(yǔ)言函數(shù)指針數(shù)組實(shí)現(xiàn)了計(jì)算器的功能,是一個(gè)很好而且流程詳細(xì)的小例子,感興趣的新手朋友們可以自己動(dòng)手也寫一遍2022-04-04基于C++ cin、cin.get()、cin.getline()、getline()、gets()函數(shù)的使用詳解
學(xué)C++的時(shí)候,這幾個(gè)輸入函數(shù)弄的有點(diǎn)迷糊;這里做個(gè)小結(jié)2013-05-05虛函數(shù)表-C++多態(tài)的實(shí)現(xiàn)原理解析
這篇文章主要介紹了虛函數(shù)表-C++多態(tài)的實(shí)現(xiàn)原理,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02C語(yǔ)言中sscanf()函數(shù)的字符串格式化用法
這篇文章介紹的是C語(yǔ)言中sscanf()函數(shù),本文介紹了sscanf()函數(shù)的含義與用法,對(duì)大家日常使用C語(yǔ)言的sscanf()函數(shù)很有幫助,有需要的可以參考借鑒。2016-08-08C++中關(guān)于委派(Delegates)的實(shí)現(xiàn)示例
這篇文章主要介紹了C++中關(guān)于委派(Delegates)的實(shí)現(xiàn)示例,針對(duì)C++11的一些新特性進(jìn)行講解,需要的朋友可以參考下2015-07-07VCPKG安裝和使用教程(經(jīng)驗(yàn)總結(jié))
這篇文章主要介紹了VCPKG安裝和使用教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01