## 將函數變成數據
再來看一下經過大幅簡化的?`create_chain_node`?函數:
~~~
struct chain_node *
create_chain_node(void)
{
struct pair *left_hole = pair(pair_for_double_type(1.0, 1.0), malloc_double(0.5));
struct pair *right_hole = pair(pair_for_double_type(9.0, 1.0), malloc_double(0.5));
struct pair *holes = pair(left_hole, right_hole);
struct pair *body = pair_for_double_type(10.0, 1.0);
struct pair *shape = pair(body, holes);
struct chain_node *ret = malloc(sizeof(struct chain_node));
ret->prev = NULL;
ret->next = NULL;
ret->shape = shape;
return ret;
}
~~~
這個函數對于我們的示例而言,沒有什么問題,但是它只能產生特定形狀的鏈節,這顯然不夠通用。如果我們想更換一下鏈節的形狀,例如將原來的帶兩個小孔的矩形鐵片換成帶兩個小孔的橢圓形鐵片,那么我們將不得不重寫一個`create_elliptic_chain_node`?函數。當我們這樣做的時候,很容易發現?`create_elliptic_chain_node`?函數中同樣需要下面這段代碼:
~~~
struct chain_node *ret = malloc(sizeof(struct chain_node));
ret->prev = NULL;
ret->next = NULL;
ret->shape = shape;
return ret;
~~~
如果我們要生產 100 種形狀的鏈節,那么上述代碼在不同的鏈節構造函數的實現中要重復出現 100 次,這樣肯定不夠好,因為會出現 500 行重復的代碼。太多的重復的代碼,這是對程序猿的最大的羞辱。
面向對象的程序猿可能會想到,我們可以為?`chain_node`?做一個基類,然后將上述共同的代碼封裝到基類的構造函數,然后在各個?`chain_node`?各個派生類的構造函數中制造不同形狀的鏈節……在你要將事情搞復雜之前,建議先看一下這樣的代碼:
~~~
void *
rectangle_shape(void)
{
struct pair *left_hole = pair(pair_for_double_type(1.0, 1.0), malloc_double(0.5));
struct pair *right_hole = pair(pair_for_double_type(9.0, 1.0), malloc_double(0.5));
struct pair *holes = pair(left_hole, right_hole);
struct pair *body = pair_for_double_type(10.0, 1.0);
return pair(body, holes);
}
struct chain_node *
create_chain_node(void *(*fp)(void))
{
struct chain_node *ret = malloc(sizeof(struct chain_node));
ret->prev = NULL;
ret->next = NULL;
ret->shape = fp();
return ret;
}
~~~
看到了吧,我將?`create_chain_node`?函數原定義中負責創建鏈節形狀的代碼全部的抽離了出去,將它們封裝到`rectangle_shape`?函數中,然后再讓?`create_chain_node`?函數接受一個函數指針形式的參數。這樣,當我們需要創建帶兩個小孔的矩形形狀的鏈節時,只需:
~~~
struct chain_node *rect_chain_node = create_chain_node(rectangle_shape);
~~~
如果我們像創建帶兩個小孔的橢圓形狀的鏈節,可以先定義一個?`elliptic_shape`?函數,然后將其作為參數傳給`create_chain_node`,即:
~~~
struct chain_node *elliptic_chain_node = create_chain_node(elliptic_shape);
~~~
這樣做,豈不是要比弄出一大堆類與繼承的代碼更簡潔有效嗎?
在 C 語言中,函數名也是一種指針,它引用了函數代碼所在內存空間的基地址。所以,我們可以將?`rectangle_shape`?這樣函數作為參數傳遞給?`create_chain_node`?函數,然后在后者中調用前者。
由于我們已經將?`chain_node`?結構體中的?`shape`?指針定義為?`void *`?指針了,因此對于?`create_chain_node`?函數所接受的函數,其返回值是?`void *`?沒什么問題。不僅沒問題,更重要的是?`void *(*fp)(void)`?對所有不接受參數且返回指針類型數據的函數的一種抽象。這意味著對于鏈節的形狀,無論它的形狀有多么特殊,我們總是能夠定義一個不接受參數且返回指針的函數來產生這種形狀,于是?`create_chain_node`?函數就因此具備了無限的擴展能力。
如果阿基米的德還活著,也許他會豪放的說,給我一個函數指針與一個?`void *`,我就能描述宇宙!