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