函數指標(Function Pointer)是一個常見的技術面試題目,特別在C或C++相關的職位中,用來考驗你對程式設計中函數作為一等公民(first-class citizen)的理解,以及記憶體管理和程式控制流的掌握。
問題描述
函數指標是一個儲存函數位址的變數,通過它可以間接呼叫函數。面試中,題目可能要求你:- 定義並使用函數指標。
- 通過函數指標實現靈活的函數調用(例如,傳遞不同行為的函數)。
- 解釋函數指標的用途或底層原理。
典型題目範例:
- 寫一個程式,使用函數指標來選擇性地執行加法或減法。
- 實現一個排序函數,通過函數指標傳入比較函數。
- 解釋函數指標與一般指標的區別。
範例需求: 給定兩個整數 a 和 b,以及一個操作(加法或減法),使用函數指標來執行指定的操作並返回結果。
解法
- 定義函數指標:
- 在C中,函數指標的語法為:
return_type (*pointer_name)(parameter_types)
。 例如,指向一個接受兩個整數並返回整數的函數:int (*func_ptr)(int, int)
。
- 在C中,函數指標的語法為:
- 實現具體函數:
- 定義加法和減法函數,簽名一致。
- 使用函數指標:
- 將函數位址賦值給函數指標。 通過函數指標呼叫函數。
- 靈活選擇:
- 根據輸入選擇不同的函數指標。
程式碼(C語言)
以下是一個實現加法或減法選擇的範例:
#include <stdio.h>
// 定義加法和減法函數,簽名一致
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
// 定義操作函數,使用函數指標
int operate(int a, int b, int (*func_ptr)(int, int)) {
return func_ptr(a, b); // 通過函數指標呼叫
}
int main() {
int a = 10, b = 5;
// 定義函數指標並賦值
int (*operation)(int, int);
// 選擇加法
operation = add;
printf("Add: %d\n", operate(a, b, operation)); // 輸出 15
// 選擇減法
operation = subtract;
printf("Subtract: %d\n", operate(a, b, operation)); // 輸出 5
// 直接傳遞函數位址
printf("Add (direct): %d\n", operate(a, b, add)); // 輸出 15
return 0;
}
程式碼解釋
- 函數定義:
- add 和 subtract 是兩個簡單的函數,接受兩個整數並返回結果。 它們的簽名必須與函數指標的簽名一致。
- 函數指標:
- int (*func_ptr)(int, int) 定義了一個函數指標,指向返回 int 並接受兩個 int 參數的函數。 在 operate 函數中,func_ptr 用來呼叫傳入的函數。
- 靈活調用:
- 在 main 中,通過將 add 或 subtract 的位址賦值給 operation,實現動態選擇。 也可以直接傳遞函數名(如 add),因為函數名在C中會自動轉換為函數位址。
- 輸出:
- 程式展示如何通過函數指標執行不同操作。
時間與空間複雜度
- 時間複雜度:O(1)(函數指標的賦值和呼叫是常數時間操作)。
- 空間複雜度:O(1)(僅儲存一個函數指標,通常是固定大小,如8字節在64位系統上)。
函數指標的用途
面試中可能會問到函數指標的實際應用,這些是常見場景:
- 回調函數(Callbacks):
- 例如,在事件驅動程式設計中,註冊回調函數以響應事件。
- 函數表(Function Table):
- 實現動態分派,例如模擬物件導向的虛函數表。
- 靈活的API設計:
- 允許用戶傳入自定義行為,例如C標準庫的 qsort 使用比較函數指標。
- 模組化程式設計:
- 將不同功能解耦,通過函數指標動態選擇實現。
面試中的注意事項
- 澄清語法:
- 函數指標的語法可能讓人困惑,面試時先寫出簽名並確認:
int (*func_ptr)(int, int); // 正確
int *func_ptr(int, int); // 錯誤,這是返回 int* 的函數
- 注意括號的使用,*func_ptr 必須括起來以區分優先級。
- 講解原理:
- 說明函數指標儲存的是函數的記憶體位址(程式碼段中的位址)。 提到函數名(如 add)在C中會自動轉為位址,因此 operation = add 等價於 operation = &add。 呼叫時,(*func_ptr)(a, b) 和 func_ptr(a, b) 都是合法的。
- 邊界檢查:
- 檢查函數指標是否為空(if (func_ptr == NULL)),避免未定義行為。 確保傳入的函數簽名一致,否則編譯器可能報錯或導致運行時錯誤。
- 進階討論:
- 如果面試官問到C++,提到函數指標與 std::function 或函數物件(functor)的區別: std::function 更靈活,支持lambda、成員函數等。 函數指標更輕量,但只能指向普通函數。 提到型別安全:C的函數指標缺乏型別檢查,C++中可用模板或 std::function 改善。
- 乾淨程式碼:
- 使用有意義的變數名(如 operation 而不是 fp)。 註釋關鍵步驟,特別是函數指標的定義和使用。 確保程式碼簡單且易於理解。
可能的追問
- 如何實現一個回調系統?
- 定義一個函數指標陣列,儲存多個回調函數,根據事件觸發相應函數。
- 函數指標與虛函數的比較?
- 函數指標是靜態綁定,虛函數是動態綁定(通過虛表實現)。 虛函數更適合物件導向,函數指標更通用但手動管理。
- 如何處理多個參數或不同簽名?
- 使用 void* 傳遞上下文(需小心型別轉換)。 在C++中,使用模板或 std::function 處理不同簽名。
- 性能影響?
- 函數指標的間接呼叫可能略微增加開銷(因位址解引用)。 現代編譯器通常會內聯小函數,減少影響。
程式碼進階範例(模擬qsort的比較器)
以下是一個更接近實際應用的範例,展示函數指標在排序中的使用:
#include <stdio.h>
#include <stdlib.h>
// 升序比較
int compare_asc(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}
// 降序比較
int compare_desc(const void* a, const void* b) {
return (*(int*)b - *(int*)a);
}
int main() {
int arr[] = {5, 2, 9, 1, 5};
int n = 5;
// 升序排序
qsort(arr, n, sizeof(int), compare_asc);
printf("Ascending: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
// 降序排序
qsort(arr, n, sizeof(int), compare_desc);
printf("Descending: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
return 0;
}
輸出:
Ascending: 1 2 5 5 9
Descending: 9 5 5 2 1
練習建議
- 練習手寫函數指標的定義和使用,避免語法錯誤。
- 實現一個簡單的回調系統,例如事件處理器。
- 閱讀C標準庫中 qsort 或 signal 的文件,理解函數指標的應用。
- 如果熟悉C++,比較函數指標與 std::function 或lambda的用法。