#include <stdio.h> int add (int firstNumber, int secondNumber); int subtract (int firstNumber, int secondNumber); int calculator (int firstNumber, int secondNumber, int (*fp) (int, int)); int main () { int firstNumber = 1; int secondNumber = 2; int (*fp) (int, int) = add; printf ("%d\n", calculator (firstNumber, secondNumber, fp)); fp = subtract; printf ("%d\n", calculator (firstNumber, secondNumber, fp)); return 0; } int add (int firstNumber, int secondNumber) { return firstNumber + secondNumber; } int subtract (int firstNumber, int secondNumber) { return firstNumber - secondNumber; } int calculator (int firstNumber, int secondNumber, int (*fp) (int, int)) { return (*fp) (firstNumber, secondNumber); }
程式碼如上。宣告2個函式add & subtract以及1個函式calculator。
其中calculator的參數允許傳入一個函式來讓calculator呼叫該函式。
在第10行時同樣宣告一個函式指標fp,並且指向add函式。
第11行時則將函式指標fp作為引數傳遞給calculator函式。因為此時fp指向add函式,因此claculator函式會呼叫add函式。
在13行時則將函式指標fp改成指向subtract函式,因此在14行的calculator會呼叫subtract函式。
以上是教科書講到函式指標時經常舉的例子。
我還在念書時對這段程式常有疑惑:
printf ("%d\n", add(firstNumber, secondNumber)); printf ("%d\n", subtract(firstNumber, secondNumber));像這樣直接呼叫add & subtract函式不就好了,為什麼還要多宣告一個函式指標來指向函式?讓函式指標呼叫該函式,這不是多此一舉嗎?
上面的程式只是為了展示函式指標的語法與使用方法而已,其實函式指標的威力是在實現callback函式的時候。
讓我們再舉個常見的範例-氣泡排序法。
現在我們需要讓一個int陣列排序,不過有時候我們需要能以升序排序,但有時候又需要能以降序排序。
為了實現這個目的,我們必須寫出兩個氣泡排序的程式:bubbleSortByAsc & bubbleSortByDesc
void bubbleSortByAsc(int array[], int length ){ int round=0, i=0; int tmp=0; for(round=0; round<length; round++){ for(i=0; i<length-1-round; i++){ if(array[i] > array[i+1]){ tmp = array[i]; array[i] = array[i+1]; array[i+1] = tmp; } } } }
void bubbleSortByDesc(int array[], int length){ int round=0, i=0; int tmp=0; for(round=0; round<length; round++){ for(i=0; i<length-1-round; i++){ if(array[i] < array[i+1]){ tmp = array[i]; array[i] = array[i+1]; array[i+1] = tmp; } } } }
你會注意到這兩個函式幾乎全部相同,唯一不一樣的是在第6行的 > 與 < 符號而已。
讓我們回到氣泡排序法的演算法:
1. 尋訪陣列中的所有元素
2. 尋訪時,每次從陣列中取相鄰的兩個元素(array[i]、array[i+1]),決定是否需要交換兩元素
3. 每次尋訪完陣列後可以得到一個最大(or最小)的一個數字
4. 重複1-3步驟
重新看一次演算法以及程式碼,不難發現決定升序或是降序的關鍵點在於"決定是否需要交換兩元素"的部分。
既然演算法的主體都一樣,唯一的差別是在"決定是否要交換兩元素",
那麼我們可不可以把這一部分從氣泡排序法抽離出來,讓使用氣泡排序法的人自己提供"決定是否交換兩元素"的函式呢?
答案當然是YES
要達成這個目的就是前面提到的函式指標,現在氣泡排序法的程式碼會變成這樣:
void bubbleSort(int array[], int length, int (*cmp)(int, int) ){ int round=0, i=0; int tmp=0; for(round=0; round<length; round++){ for(i=0; i<length-1-round; i++){ if(cmp(array[i], array[i+1]) > 0){ tmp = array[i]; array[i] = array[i+1]; array[i+1] = tmp; } } } }
我們多傳了一個函式指標進來,在第6行時呼叫函式指標所指向的函式來決定是否需要交換陣列元素。
所以這時候我們想要升序排序時需要撰寫sortByAsc函式:
int sortByAsc(int first, int second){ if(first < second){ return -1; } else if(first > second){ return 1; } else{ return 0; } }
使用時將sortByAsc傳遞給bubbleSort即可。
int main() { int array[] = {3, 19, 6, 4, 20}; int length = sizeof(array)/sizeof(array[0]); bubbleSort(array, length, sortByAsc); int i=0; for(i=0; i<length; i++){ printf("%d\n", array[i]); } return 0; }
同樣地,想要降序排序就是寫一個sortByDesc即可。
int sortByDesc(int first, int second){ return sortByAsc(first, second) * -1; }
int main() { int array[] = {3, 19, 6, 4, 20}; int length = sizeof(array)/sizeof(array[0]); bubbleSort(array, length, sortByDesc); int i=0; for(i=0; i<length; i++){ printf("%d\n", array[i]); } return 0; }
其實這邊的sortByAsc以及sortByDesc函式就是所謂的callback函式。
使用函式指標與callback函式可以讓你的程式更加的精簡與具有彈性。
在現代軟體開發往往都是多個人一起共同開發,一個人負責一小部分。
可能氣泡排序法是由你寫的,但是使用排序法的可能是其他人。
想想原本的寫法,首先你必須要知道使用的人需要多少種排序方法,然後我們就要寫多少種幾乎一模一樣的氣泡排序法。
而使用函式指標後我們只要寫好一種氣泡排序法就好了。
況且這兩種方法的最大差別在於面對未來的需求:
現在我們需要一種新的排序方法:使用升序排序,但是奇數數字排前面,偶數數字排後面。
原本的寫法你必須重新寫一份排序方法、編譯、然後請其他人更新檔案、重新連結後才能使用,
但是callback版本時你完全不需要動工,因為我們已經把決定排序方法的這件事丟給使用者決定了。
題外話:
C提供的標準函式庫裡面有qsort可以使用。
撇除掉qsort為了能支援所有資料形態而使用void*指標外,其設計核心也是使用函式指標。
沒有留言:
張貼留言