函數(shù)在C中實際上只是指向程序中某一個代碼存在位置的指針。就像你創(chuàng)建過的結(jié)構(gòu)體指針、字符串和數(shù)組那樣,你也可以創(chuàng)建指向函數(shù)的指針。函數(shù)指針的主要用途是向其他函數(shù)傳遞“回調(diào)”,或者模擬類和對象。在這歌1練習中我們會創(chuàng)建一些回調(diào),并且下一節(jié)我們會制作一個簡單的對象系統(tǒng)。
函數(shù)指針的格式類似這樣:
int (*POINTER_NAME)(int a, int b)
記住如何編寫它的一個方法是:
int callme(int a, int b)
int (*callme)(int a, int b)
int (*compare_cb)(int a, int b)
這個方法的關(guān)鍵是,當你完成這些之后,指針的變量名稱為compare_cb
,而你可以將它用作函數(shù)。這類似于指向數(shù)組的指針可以表示所指向的數(shù)組。指向函數(shù)的指針也可以用作表示所指向的函數(shù),只不過是不同的名字。
int (*tester)(int a, int b) = sorted_order;
printf("TEST: %d is same as %d\n", tester(2, 3), sorted_order(2, 3));
即使是對于返回指針的函數(shù)指針,上述方法依然有效:
char *make_coolness(int awesome_levels)
char *(*make_coolness)(int awesome_levels)
char *(*coolness_cb)(int awesome_levels)
需要解決的下一個問題是使用函數(shù)指針向其它函數(shù)提供參數(shù)比較困難,比如當你打算向其它函數(shù)傳遞回調(diào)函數(shù)的時候。解決方法是使用typedef
,它是C的一個關(guān)鍵字,可以給其它更復雜的類型起個新的名字。你需要記住的事情是,將typedef
添加到相同的指針語法之前,然后你就可以將那個名字用作類型了。我使用下面的代碼來演示這一特性:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
/** Our old friend die from ex17. */
void die(const char *message)
{
if(errno) {
perror(message);
} else {
printf("ERROR: %s\n", message);
}
exit(1);
}
// a typedef creates a fake type, in this
// case for a function pointer
typedef int (*compare_cb)(int a, int b);
/**
* A classic bubble sort function that uses the
* compare_cb to do the sorting.
*/
int *bubble_sort(int *numbers, int count, compare_cb cmp)
{
int temp = 0;
int i = 0;
int j = 0;
int *target = malloc(count * sizeof(int));
if(!target) die("Memory error.");
memcpy(target, numbers, count * sizeof(int));
for(i = 0; i < count; i++) {
for(j = 0; j < count - 1; j++) {
if(cmp(target[j], target[j+1]) > 0) {
temp = target[j+1];
target[j+1] = target[j];
target[j] = temp;
}
}
}
return target;
}
int sorted_order(int a, int b)
{
return a - b;
}
int reverse_order(int a, int b)
{
return b - a;
}
int strange_order(int a, int b)
{
if(a == 0 || b == 0) {
return 0;
} else {
return a % b;
}
}
/**
* Used to test that we are sorting things correctly
* by doing the sort and printing it out.
*/
void test_sorting(int *numbers, int count, compare_cb cmp)
{
int i = 0;
int *sorted = bubble_sort(numbers, count, cmp);
if(!sorted) die("Failed to sort as requested.");
for(i = 0; i < count; i++) {
printf("%d ", sorted[i]);
}
printf("\n");
free(sorted);
}
int main(int argc, char *argv[])
{
if(argc < 2) die("USAGE: ex18 4 3 1 5 6");
int count = argc - 1;
int i = 0;
char **inputs = argv + 1;
int *numbers = malloc(count * sizeof(int));
if(!numbers) die("Memory error.");
for(i = 0; i < count; i++) {
numbers[i] = atoi(inputs[i]);
}
test_sorting(numbers, count, sorted_order);
test_sorting(numbers, count, reverse_order);
test_sorting(numbers, count, strange_order);
free(numbers);
return 0;
}
在這段程序中,你將創(chuàng)建動態(tài)排序的算法,它會使用比較回調(diào)對整數(shù)數(shù)組排序。下面是這個程序的分解,你應該能夠清晰地理解它。
ex18.c:1~6
通常的包含,用于所調(diào)用的所有函數(shù)。
ex18.c:7~17
這就是之前練習的die
函數(shù),我將它用于錯誤檢查。
ex18.c:21
這是使用typedef
的地方,在后面我像int
或char
類型那樣,在bubble_sort
和test_sorting
中使用了compare_cb
。
ex18.c:27~49
一個冒泡排序的實現(xiàn),它是整數(shù)排序的一種不高效的方法。這個函數(shù)包含了:
ex18.c:27
這里是將typedef
用于compare_cb
作為cmp
最后一個參數(shù)的地方?,F(xiàn)在它是一個會返回兩個整數(shù)比較結(jié)果用于排序的函數(shù)。
ex18.c:29~34
棧上變量的通常創(chuàng)建語句,前面是使用malloc
創(chuàng)建的堆上整數(shù)數(shù)組。確保你理解了count * sizeof(int)
做了什么。
ex18.c:38
冒泡排序的外循環(huán)。
ex18.c:39
冒泡排序的內(nèi)循環(huán)。
ex18.c:40
現(xiàn)在我調(diào)用了cmp
回調(diào),就像一個普通函數(shù)那樣,但是不通過預先定義好的函數(shù)名,而是一個指向它的指針。調(diào)用者可以像它傳遞任何參數(shù),只要這些參數(shù)符合compare_cb
typedef
的簽名。
ex18.c:41-43
冒泡排序所需的實際交換操作。
ex18.c:48
最后返回新創(chuàng)建和排序過的結(jié)果數(shù)據(jù)target
。
ex18.c:51-68
compare_cb
函數(shù)類型三個不同版本,它們需要和我們所創(chuàng)建的typedef
具有相同的定義。否則C編輯器會報錯說類型不匹配。
ex18.c:74-87
這是bubble_sort
函數(shù)的測試。你可以看到我同時將compare_cb
傳給了bubble_sort
來演示它是如何像其它指針一樣傳遞的。
ex18.c:90-103
一個簡單的主函數(shù),基于你通過命令行傳遞進來的整數(shù),創(chuàng)建了一個數(shù)組。然后調(diào)用了test_sorting
函數(shù)。
ex18.c:105-107
最后,你會看到compare_cb
函數(shù)指針的typedef
是如何使用的。我僅僅傳遞了sorted_order
、reverse_order
和strange_order
的名字作為函數(shù)來調(diào)用test_sorting
。C編譯器會找到這些函數(shù)的地址,并且生成指針用于test_sorting
。如果你看一眼test_sorting
你會發(fā)現(xiàn)它把這些函數(shù)傳給了bubble_sort
,并不關(guān)心它們是做了什么。只要符合compare_cb
原型的東西都有效。
ex18.c:109
我們在最后釋放了我們創(chuàng)建的整數(shù)數(shù)組。
運行這個程序非常簡單,但是你要嘗試不同的數(shù)字組合,甚至要嘗試輸入非數(shù)字來看看它做了什么:
$ make ex18
cc -Wall -g ex18.c -o ex18
$ ./ex18 4 1 7 3 2 0 8
0 1 2 3 4 7 8
8 7 4 3 2 1 0
3 4 2 7 1 0 8
$
我打算讓你做一些奇怪的事情來使它崩潰,這些函數(shù)指針都是類似于其它指針的指針,他們都指向內(nèi)存的一塊區(qū)域。C中可以將一種指針的指針轉(zhuǎn)換為另一種,以便以不同方式處理數(shù)據(jù)。這些通常是不必要的,但是為了想你展示如何侵入你的電腦,我希望你把這段代碼添加在test_sorting
下面:
unsigned char *data = (unsigned char *)cmp;
for(i = 0; i < 25; i++) {
printf("%02x:", data[i]);
}
printf("\n");
這個循環(huán)將你的函數(shù)轉(zhuǎn)換成字符串,并且打印出來它的內(nèi)容。這并不會中斷你的程序,除非CPU和OS在執(zhí)行過程中遇到了問題。在它打印排序過的數(shù)組之后,你所看到的是一個十六進制數(shù)字的字符串:
55:48:89:e5:89:7d:fc:89:75:f8:8b:55:fc:8b:45:f8:29:d0:c9:c3:55:48:89:e5:89:
這就應該是函數(shù)的原始的匯編字節(jié)碼了,你應該能看到它們有相同的起始和不同的結(jié)尾。也有可能這個循環(huán)并沒有獲得函數(shù)的全部,或者獲得了過多的代碼而跑到程序的另外一片空間。這些不通過更多分析是不可能知道的。
ex18
,接著找到函數(shù)起始處的十六進制代碼序列,看看是否能在原始程序中找到函數(shù)。compare_cb
,并看看C編輯器會報告什么錯誤。NULL
傳給它,看看程序中會發(fā)生什么。然后運行Valgrind
來看看它會報告什么。test_sorting
使它接收任意的排序函數(shù)和排序函數(shù)的比較回調(diào)。并使用它來測試兩種排序算法。