當前位置: 妍妍網 > 碼農

回呼函式(callback)是什麽?一文理解回呼函式(callback)

2024-04-19碼農

【幹貨】

【幹貨】

轉自:網路

一、什麽是回呼函式

1.1、回呼函式的定義和基本概念

回呼函式是一種特殊的函式,它作為參數傳遞給另一個函式,並在被呼叫函式執行完畢後被呼叫。回呼函式通常用於事件處理、異步編程和處理各種作業系統和框架的API。

基本概念:

  1. 回呼:指被傳入到另一個函式的函式。

  2. 異步編程:指在程式碼執行時不會阻塞程式執行的方式。

  3. 事件驅動:指程式的執行是由外部事件觸發而不是順序執行的方式。

1.2、回呼函式的作用和使用場景

回呼函式是一種常見的編程技術,它可以在異步操作完成後呼叫一個預定義的函式來處理結果。回呼函式通常用於處理事件、執行異步操作或響應使用者輸入等場景。

回呼函式的作用是將程式碼邏輯分離出來,使得程式碼更加模組化和可維護。使用回呼函式可以避免阻塞程式的執行,提高程式的效能和效率。另外,回呼函式還可以實作程式碼的復用,因為它們可以被多個地方呼叫。

回呼函式的使用場景包括:

  1. 事件處理:回呼函式可以用於處理各種事件,例如滑鼠點選、鍵盤輸入、網路請求等。

  2. 異步操作:回呼函式可以用於異步操作,例如讀取檔、發送信件、下載檔等。

  3. 數據處理:回呼函式可以用於處理數據,例如對陣列進行排序、過濾、對映等。

  4. 外掛程式開發:回呼函式可以用於開發外掛程式,例如 WordPress 外掛程式、jQuery 外掛程式等。

回呼函式是一種非常靈活和強大的編程技術,可以讓我們更好地處理各種異步操作和事件。

二、回呼函式的實作方法

回呼函式可以透過函式指標或函式物件來實作。

2.1、函式指標

函式指標是一個變量,它儲存了一個函式的地址。當將函式指標作為參數傳遞給另一個函式時,另一個函式就可以使用這個指標來呼叫該函式。函式指標的定義形式如下:

返回型別 (*函式指標名稱)(參數列)

例如,假設有一個回呼函式需要接收兩個整數參數並返回一個整數值,可以使用以下方式定義函式指標:

int (*callback)(int, int);

然後,可以將一個實際的函式指標賦值給它,例如:

intadd(int a, int b) {
return a + b;
}
callback = add;

現在,可以將這個函式指標傳遞給其他函式,使得其他函式可以使用這個指標來呼叫該函式。

2.2、函式物件/functor

除了函式指標,還可以使用函式物件來實作回呼函式。函式物件是一個類的例項,其中多載了函式呼叫運算子 ()。當將一個函式物件作為參數傳遞給另一個函式時,另一個函式就可以使用這個物件來呼叫其多載的函式呼叫運算子。函式物件的定義形式如下:

classcallback {
public:
返回型別 operator()(參數列) {
// 函式體
}
};

例如,假設有一個回呼函式需要接收兩個整數參數並返回一個整數值,可以使用以下方式定義函式物件:

classAdd {
public:
intoperator()(int a, int b) {
return a + b;
}
};
Add add;

然後,可以將這個函式物件傳遞給其他函式,使得其他函式可以使用這個物件來呼叫其多載的函式呼叫運算子。

2.3、匿名函式/lambda運算式

回呼函式的實作方法有多種,其中一種常見的方式是使用匿名函式/lambda運算式。

Lambda運算式是一個匿名函式,可以作為參數傳遞給其他函式或物件。在C++11之前,如果想要傳遞一個函式作為參數,需要使用函式指標或者函式物件。但是這些方法都比較繁瑣,需要顯式地定義函式或者類,並且程式碼可讀性不高。使用Lambda運算式可以簡化這個過程,使得程式碼更加簡潔和易讀。

下面是一個使用Lambda運算式實作回呼函式的例子:

#include<iostream>
#include<vector>
#include<algorithm>
voidprint(int i){
std::cout << i << " ";
}
voidforEach(conststd::vector<int>& v, constvoid(*callback)(int)){
for(auto i : v) {
callback(i);
}
}
intmain(){
std::vector<int> v = {1,2,3,4,5};
forEach(v, [](int i){std::cout << i << " ";});
}


在上面的例子中,我們定義了一個forEach函式,接受一個vector和一個回呼函式作為參數。回呼函式的型別是void()(int),即一個接受一個整數參數並且返回void的函式指標。在main函式中,我們使用了Lambda運算式來作為回呼函式的實作,即[](int i){std::cout << i << " ";}。Lambda運算式的語法為{/ lambda body */},其中[]表示Lambda運算式的捕獲列表,即可以在Lambda運算式中存取的外部變量;{}表示Lambda函式體,即Lambda運算式所要執行的程式碼塊。

在使用forEach函式時,我們傳遞了一個Lambda運算式作為回呼函式,用於輸出vector中的每個元素。當forEach函式呼叫回呼函式時,實際上是呼叫Lambda運算式來處理vector中的每個元素。這種方式相比傳遞函式指標或者函式物件更加簡潔和易讀。

使用Lambda運算式可以方便地實作回呼函式,使得程式碼更加簡潔和易讀。但是需要註意Lambda運算式可能會影響程式碼的效能,因此需要根據具體情況進行評估和選擇。

三、回呼函式的套用舉例

異步編程中的回呼函式:網路編程中,當某個連線收到數據後,可以使用回呼函式來處理數據。

例如:

voidonDataReceived(int socket, char* data, int size);
intmain(){
int socket = connectToServer();
startReceivingData(socket, onDataReceived);
// ...
}
voidonDataReceived(int socket, char* data, int size){
// 處理數據...
}

回呼函式在GUI編程中的套用:GUI編程中,當使用者觸發了某個操作時,可以使用回呼函式來處理該操作。

例如:

voidonButtonClicked(Button* button);
intmain(){
Button* button = createButton("Click me");
setButtonClickHandler(button, onButtonClicked);
// ...
}
voidonButtonClicked(Button* button){
// 處理按鈕點選事件...
}

事件處理常式中的回呼函式:多執行緒編程中,當某個執行緒完成了一次任務後,可以使用回呼函式來通知主執行緒。

例如:

voidonTaskCompleted(int taskId);
intmain(){
for (int i = 0; i < numTasks; i++) {
startBackgroundTask(i, onTaskCompleted);
}
// ...
}
voidonTaskCompleted(int taskId){
// 處理任務完成事件...
}

四、回呼函式的優缺點

優點:

  • 提高程式碼的復用性和靈活性:回呼函式可以將一個函式作為參數傳遞給另一個函式,從而實作模組化編程,提高程式碼的復用性和靈活性。

  • 解耦合:回呼函式可以將不同模組之間的關系解耦,使得程式碼更易於維護和擴充套件。

  • 可以異步執行:回呼函式可以在異步操作完成後被執行,這樣避免了阻塞執行緒,提高應用程式的效率。

  • 缺點:

  • 回呼函式巢狀過多會導致程式碼難以維護:如果回呼函式巢狀層數過多,程式碼會變得非常復雜,難以維護。

  • 回呼函式容易造成競態條件:如果回呼函式中有共享資源存取,容易出現競態條件,導致程式出錯。

  • 程式碼可讀性差:回呼函式的使用可能會破壞程式碼的結構和可讀性,尤其是在處理大量數據時。

  • 小結:程式碼靈活、易於擴充套件,但是不易於閱讀、容易出錯。

    五、回呼函式與其他編程概念的關系

    5.1、回呼函式和閉包的關系

    回呼函式和閉包之間存在著緊密的關系。回呼函式是一個函式,在另一個函式中被作為參數傳遞,並在該函式執行完後被呼叫。閉包是由一個函式及其相關的參照環境組合而成的實體,可以存取函式外部的變量。

    在某些情況下,回呼函式需要存取到它所在的父函式的變量,這時就需要使用閉包來實作。透過將回呼函式放在閉包內部,可以將父函式的變量保存在閉包的參照環境中,使得回呼函式能夠存取到這些變量。同時,閉包還可以保證父函式中的變量在回呼函式執行時不會被銷毀,從而確保了回呼函式的正確性。

    因此,回呼函式和閉包是一對密切相關的概念,常常一起使用來實作復雜的邏輯和功能。微信搜尋公眾號:Linux技術迷,回復:linux 領取資料 。

    5.2、回呼函式和Promise的關系

    C++回呼函式和Promise都是異步編程的實作方式。

    回呼函式是一種將函式作為參數傳遞給另一個函式,在異步操作完成後執行的技術。在C++中,回呼函式通常使用函式指標或函式物件來實作。當異步操作完成後,會呼叫註冊的回呼函式,以便執行相應的處理邏輯。

    而Promise則是一種更加高級的異步編程模式,它透過解決回呼地獄問題,提供了更加優雅和簡潔的異步編程方式。Promise可以將異步操作封裝成一個Promise物件,並透過鏈式呼叫then()方法來註冊回呼函式,以及catch()方法來捕獲異常。當異步操作完成後,Promise會自動根據操作結果觸發相應的回呼函式。

    因此,可以說C++回呼函式和Promise都是異步編程的實作方式,但是Promise提供了更加高級和優雅的編程模式,能夠更好地管理異步操作和避免回呼地獄問題。

    5.3、回呼函式和觀察者模式的關系

    回呼函式和觀察者模式都是用於實作事件驅動編程的技術。它們之間的關系是,觀察者模式是一種設計模式,它透過定義一種一對多的依賴關系,使得一個物件的狀態發生改變時,所有依賴於它的物件都會得到通知並自動更新。而回呼函式則是一種編程技術,它允許將一個函式作為參數傳遞給另一個函式,在執行過程中呼叫這個函式來完成特定的任務。

    在觀察者模式中,當一個被觀察的物件發生改變時,會遍歷所有的觀察者物件,呼叫其定義好的更新方法,以進行相應的操作。這裏的更新方法就可以看做是回呼函式,因為它是由被觀察物件呼叫的,並且在執行過程中可能需要使用到一些外部參數或上下文資訊。因此,可以說觀察者模式本身就包含了回呼函式的概念,並且借助回呼函式來實作觀察者模式的具體功能。

    六、如何編寫高品質的回呼函式

    回呼函式需要遵循以下幾個原則:

    1. 明確函式的目的和作用域。回呼函式應該有一個清晰的目的,同時只關註與其作用範圍相關的任務。

    2. 確定回呼函式的參數和返回值。在定義回呼函式時,需要明確它所需的參數和返回值型別,這樣可以使呼叫方更容易使用。

    3. 謹慎處理錯誤和異常。回呼函式可能會引發一些異常或錯誤,需要使用 try-catch 塊來處理它們,並給出相應的警告。

    4. 確保回呼函式不會導致死結或阻塞。回呼函式需要盡可能快地執行完畢,以避免影響程式的效能和穩定性。

    5. 使用清晰且易於理解的命名規則。回呼函式的命名應該清晰、簡潔,並盡可能說明其功能和意義。

    6. 編寫文件和範例程式碼。良好的文件和範例程式碼可以幫助其他開發者更容易地使用回呼函式,同時也有助於提高程式碼的可維護性和可重用性。

    7. 遵循編碼規範和最佳實踐。編寫高品質的回呼函式需要遵守編碼規範和最佳實踐,例如使用合適的命名規則、註釋程式碼等。

    6.1、回呼函式的命名規範

    回呼函式的命名規範沒有固定的標準,但是根據通用慣例和編碼規範,回呼函式的命名應該能夠反映函式的作用和功能,讓其他開發者能夠快速理解並使用。

    1. 使用動詞+名詞的方式來描述回呼函式的作用,例如onSuccess、onError等。

    2. 如果回呼函式是用於處理事件的,可以以handleEvent或者onEvent作為函式名。

    3. 如果回呼函式是用於處理異步操作完成後的結果,可以以onComplete或者onResult作為函式名。

    4. 在命名時要註意保持簡潔明了,不要過於冗長,也不要使用縮寫或者不清晰的縮寫。

    5. 盡量使用有意義的單詞或者短語作為函式名,不要使用無意義的字母或數位組合。

    6. 與程式碼中其他的函式名稱保持一致,盡量避免出現命名沖突的情況。

    6.2、回呼函式的參數設計

    回呼函式的參數設計取決於回呼函式所需執行的操作和數據。一般來說,回呼函式需要接收至少一個參數,通常是處理結果或錯誤資訊。其他可選參數根據需要添加。

    例如,如果回呼函式是用於處理異步請求的,則第一個參數可能是錯誤資訊(如果存在),第二個參數則是請求返回的數據。另外,也可以將回呼函式的上下文傳遞給該函式作為參數,以便在回呼函式中使用。

    假設有一個函式 process_data 用於處理數據,但是具體的處理方式需要根據不同的情況進行客製化。這時候我們可以使用回呼函式來實作。

    回呼函式的參數設計如下:

    voidprocess_data(void *data, int len, void (*callback)(void *result));

    其中,data 表示要處理的數據,len 表示數據的長度,callback 是一個函式指標,用於指定處理完數據後的回呼函式。回呼函式的形式如下:

    voidcallback_func(void *result);

    在 process_data 函式中,首先會對數據進行處理,然後將處理結果傳遞給回呼函式進行處理。具體實作如下:

    voidprocess_data(void *data, int len, void (*callback)(void *result)){
    // 處理數據
    void *result = data; // 這裏只是舉個例子,實際上需要根據實際情況進行處理
    // 呼叫回呼函式
    callback(result);
    }

    使用範例:

    #include<stdio.h>
    voidcallback_func(void *result){
    printf("processing result: %s\n", (char *)result); // 這裏只是舉個例子,實際上需要根據實際情況進行處理
    }
    intmain(){
    char data[] = "hello world";
    process_data(data, sizeof(data), callback_func);
    return0;
    }

    七、總結

    回呼函式是一種常見的編程模式,主要內容包括以下幾個方面:

  • 回呼函式的定義:回呼函式是一個作為參數傳遞給其他函式的函式,它能夠被異步呼叫以處理某些事件或完成某些任務。

  • 回呼函式的使用場景:回呼函式通常用於異步編程中,例如在瀏覽器端的 AJAX 請求、Node.js 中的檔讀寫等場景中都會使用回呼函式。

  • 回呼函式的實作方式:回呼函式可以透過直接傳入函式名或者透過匿名函式的方式來實作。

  • 回呼函式的錯誤處理:在回呼函式中,需要對可能出現的錯誤進行處理,例如返回錯誤物件、丟擲異常或透過回呼函式傳遞錯誤資訊等方式。

  • 回呼函式的優缺點:回呼函式可以提高程式碼的靈活性和可重用性,但也容易導致程式碼復雜度增加、巢狀過深等問題。

  • <END>

    點這裏👇關註我,記得標星呀~

    往期精選:

    GPT中文網站

    可以在國內同ChatGPT直接進行對話,支持GPT4.0 和 AI繪圖,簡直太方便了,今天新註冊的直接送4.0提問次數 !

    點「在看」的人都變好看了哦