在 C/C++ 程式設計中,回調函數是一項核心且強大的技術,廣泛應用於事件驅動、信號處理、庫函數擴展等場景。要徹底理解回調函數,必須先掌握其基石——函數指針。本文將由淺入深,從函數指針的概念講起,逐步引入回調函數的原理與實作,並提供完整代碼示例。
指針本質上是一個存儲內存地址的變量。程序運行時,所有相關對象(包括函數)都會被加載到內存中。函數存放在代碼區,同樣擁有入口地址,因此我們可以用指針指向函數,這類指向函數入口地址的指針稱為函數指針。
以經典的 "Hello World" 為例,展示三種調用方式的演進:
int main(int argc, char* argv[]) {
printf("Hello World!\n");
return 0;
}
void Invoke(char* s);
int main(int argc, char* argv[]) {
Invoke("Hello World!\n");
return 0;
}
void Invoke(char* s) {
printf(s);
}
void Invoke(char* s);
int main() {
void (*fp)(char* s); // 聲明函數指針 fp
fp = Invoke; // 將 Invoke 入口地址賦值給 fp
fp("Hello World!\n"); // 通過函數指針調用
return 0;
}
void Invoke(char* s) {
printf(s);
}
關鍵點:函數指針聲明與函數聲明的唯一區別在於用 (*fp) 替換了函數名。聲明時必須保持返回值類型、參數個數、參數類型一致,且指針名必須用括號括起來(如 void (*fp)(char* s))。
實際開發中常用 typedef 簡化函數指針聲明:
typedef void (*FP)(char* s);
void Invoke(char* s);
int main(int argc, char* argv[]) {
FP fp; // 使用 typedef 定義的類型聲明指針
fp = Invoke;
fp("Hello World!\n");
return 0;
}
void Invoke(char* s) {
printf(s);
}
函數指針數組可實現多功能分發,示例:
#include <iostream>
#include <string>
using namespace std;
typedef void (*FP)(char* s);
void f1(char* s) { cout << s; }
void f2(char* s) { cout << "Processed: " << s; }
int main() {
FP funcArray[2] = {f1, f2};
funcArray[0]("Hello ");
funcArray[1]("World!\n");
return 0;
}
回調函數:使用者自定義並實現的函數,作為參數傳遞給其他函數(或系統庫),由後者在運行時調用。簡言之:「你實現函數,別人決定何時調用」。
// 定義回調函數
void PrintfText() {
printf("Hello World!\n");
}
// 定義調用函數(接受函數指針作參數)
void CallPrintfText(void (*callFunc)()) {
callFunc(); // 運行時回調用戶函數
}
int main(int argc, char* argv[]) {
CallPrintfText(PrintfText); // 註冊回調
return 0;
}
// 帶參回調函數
void PrintfText(char* s) {
printf(s);
}
// 帶參調用函數
void CallPrintfText(void (*callFunc)(char*), char* s) {
callFunc(s); // 將參數透傳給回調函數
}
int main(int argc, char* argv[]) {
CallPrintfText(PrintfText, "Hello World!\n");
return 0;
}
qsort 的比較函數參數signal() 註冊處理函數函數指針賦予了 C/C++ 動態綁定能力,回調函數在此基礎上實現了控制反轉。掌握兩者區別與聯繫:
實際開發中,善用 typedef 簡化複雜聲明,結合上下文語義設計清晰的回調接口,能顯著提升代碼靈活性與可擴展性。