Pointer - Function
前言
callback function 的概念對於程式初學者或是C不熟的讀者可能略顯陌生。但是這樣的技巧廣泛運用在各個地方, GUI、kernel、driver,或是各式演算法。尤其是 functional 的程式語言,如 javascript,python,Lisp...等等許多許多。一些標準的用法如 reduce,map,foreach 都見得到 callback function的影子。而在C中我們需要使用 function pointer 來實現這樣的概念。
這篇要說的是 functoin pointer. 顧名思義,指向 function 的指標。
考慮一個 function
char func(int x, int y)
{
return (x + y) & 0xff;
}
這邊故意讓參數跟回傳不同型態以方面下面講解。
通常使用 function pointer都是為了實做 callback function。
一個 function 的型態由他的 prototype 決定,範例語法如下
char (*fp)(int, int) = func;
也就是
return_type (*fun_ptr_name)(args...)
給一個簡單的例子
int do_op(int (*op)(int,int), int x, int y)
{
return (*op)(x, y);
}
int op_add(int x, int y)
{
return x + y;
}
int op_mul(int x, int y)
{
return x * y;
}
int main(int argc, const char * argv[])
{
int add_val = do_op(op_add, 10, 20);
int mul_val = do_op(op_mul, 10, 20);
}
我們從上面的例子去說明基本的 function pointer 使用
首先先從 do_op 開始。
我們可以看到這個 do_op 有三個參數,分別是 int (*op)(int, int), int x 與 int y 。
可以注意到的是我們呼叫的時候是使用 (*op)(x,y) 這樣的方式。但事實上 function pointer使用時寫法很多
也有人會寫 op(x,y) 還有其他很特別的合法寫法(雖然真正寫code不會使用,如 (****op)(x,y)) 。我們這邊先不提這種特殊的,
就先就 (*op)(x,y)與 op(x,y) 比較而言。 雖然 op(x,y) 寫起來更像 "用 function" 也更簡潔,但是無法一眼看出該 function 為一個 function pointer。而 (*op)(x,y) 的寫法雖然看起來比較繁瑣一點點,但卻明確指出 op 為一個 function pointer。 這部分就端看團隊 coding style。
-
op_add 以及 op_mul 我們通常稱做 callback function。詳細的說明可以再去找其他資料,在此就僅介紹名詞。
而我們在使用 do_op 時就會將 op_add 以及 op_mul 當作參數傳入。
在邏輯上的說明是 - do_op 是一個二元運算的框架,因此要傳入一個二元運算子以及兩個運算元,回傳用該運算子運算的結果。
一個有名的使用是 qsort 以及 compare function。
qsort
void qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *))
稍微介紹一下參數 -
假設一個抽象型態 T, 有陣列 T a[20] 要由小到大排序。
base: 要排序的 array,在這邊是 anel: number of elements,此例為 20width: 一個元素的大小,此例為 sizeof(T)compar: compare function,定義大小。 應符合下列要求實做 -
在使用時,若
x,y為型態T的變數,且ret = compar(&x, &y)。ret < 0 若 x < y,ret == 0 若 x == y,ret > 0 若 x > y。
給一個實際例子
#include <stdlib.h>
int cmp(const void * x, const void * y)
{
return *(const int *)x - *(const int*)y;
}
int main(int argc, const char * argv[])
{
int a[5] = {2, 4, 1, 6, 7};
qsort(a, 5, sizeof(int), cmp);
}
這邊說明一下 cmp 的寫法,原本的型態是 const void* 因此要先轉型成 const int*。 轉型後再取值,簡寫成 *(const int*)x。相減兩數就自然符合上述所規範的條件。要注意的是,這是剛好整數所以有一個自然的減法,若是一般型態(如某個自定義 struct)就不會有"減法",取而代之的可能是該structure的某個數值 member。