# 函式指標 程式在執行時,函式本身在記憶體中也佔有一個空間,而函式名稱本身也就是指向該空間位址的參考名稱,當呼叫函式名稱時,程式就會去執行該函式名稱所指向的記憶體空間中之指令。 我們也可以宣告函式指標,並讓它與某個函式指向相同的空間,函式指標的宣告方式如下所示: ``` 傳回值型態 (*指標名稱)(傳遞參數); ``` - 一個函式型態由傳回值型態與參數列決定,不包括函式名稱 - 一個函式指標可指向具有相同型態的函式,也就是具有相同傳回值型態和參數列的函式 - 如果函式帶有參數,則函式指標本身的宣告也必須指定相同的參數型態與個數 下面這個程式是個簡單的示範,以函式指標`ptr`來取得函式`foo()`的位址,使用它來呼叫函式,將與使用`foo()`來呼叫函式具有相同的作用,程式以整數方式顯示兩個的記憶體空間是相同的: ```cpp= #include <iostream> using namespace std; int foo(); int main() { int (*ptr)() = 0; // 這是函式指標的宣告 ptr = foo; foo(); ptr(); cout << "address of foo:" << (int)foo << endl; //foo()函式的位置 cout << "address of foo:" << (int)ptr << endl; return 0; } int foo() { cout << "function point" << endl; return 0; } ``` :::spoiler 執行結果 :::info ``` function point function point address of foo:4199502 address of foo:4199502 ``` ::: 下面這個程式用來顯示重載函式會分別佔據不同的記憶體空間: ```cpp= #include <iostream> using namespace std; int foo(int, int); char foo(int, char); int main() { int (*ptr1)(int, int) = 0; char (*ptr2)(int, char) = 0; ptr1 = foo; // get address of foo(int, int) ptr2 = foo; // get address of foo(int, char) ptr1(1, 2); ptr2(3, 'c'); cout << "address of foo(int, int): " << (int)ptr1 << endl; cout << "address of foo(int, char): " << (int)ptr2 << endl; return 0; } int foo(int n1, int n2) { cout << "foo(int, int): " << n1 << "\t" << n2 << endl; return 0; } char foo(int n, char c) { cout << "foo(int, char): " << n << "\t" << c << endl; return c; } ```` :::spoiler 執行結果 :::info ``` foo(int, int): 1 2 foo(int, char): 3 c address of foo(int, int): 4199548 address of foo(int, char): 4199648 ``` ::: 由於在宣告函式指標時已指定了參數列的資料型態與個數,所以雖然在指定ptr1與ptr2時都使用foo函式名稱,但程式仍可以自動判別出我們是要指定哪個過載函式的位址。 ## 小例子 來看看一個應用的例子,假設我們要撰寫一個排序`sort()`函式,希望排序時可以由大至小,也可以由小至大 - 比較簡單的作法是在sort()上加上一個額外的參數,可以傳入常數或列舉,例如如果指定1的話就由大至小,指定0的話就由小至大,這需要在函式中加上額外的判斷 - 為了簡化sort()的撰寫,可以傳入一個函式位址,函式中就無需額外的判斷 - `sort.h` ```c= void swap(int&, int&); bool larger(int a, int b); bool smaller(int a, int b); void sort(int*, int, bool (*compare)(int, int)); ``` - `sort.cc` ```cpp= #include "sort.h"; void swap(int &a, int &b) { int t = a; a = b; b = t; } bool larger(int a, int b) { return a > b; } bool smaller(int a, int b) { return a < b; } void sort(int* arr, int length, bool (*compare)(int, int)) { int flag = 1; for(int i = 0; i < length-1 && flag == 1; i++) { flag = 0; for(int j = 0; j < length-i-1; j++) { if(compare(arr[j+1], arr[j])) { swap(arr[j+1], arr[j]); flag = 1; } } } } ``` - `main.cc` ```cpp= #include <iostream> #include "sort.h" using namespace std; int main() { int number1[] = {3, 5, 1, 6, 9}; sort(number1, 5, larger); cout << "大的在前 "; for(int i = 0; i < 5; i++) { cout << number1[i] << " "; } cout << endl; int number2[] = {3, 5, 1, 6, 9}; sort(number2, 5, smaller); cout << "小的在前 "; for(int i = 0; i < 5; i++) { cout << number2[i] << " "; } cout << endl; return 0; } ``` :::spoiler 執行結果 :::info ``` 大的在前 9 6 5 3 1 小的在前 1 3 5 6 9 ``` ::: 在函式中,不必理會傳入的實際函式,只要呼叫compare()就可以了,在這個例子中,我們在sort()上的函式指標宣告有些難以閱讀,我們可以使用typedef,定義一個比較容易閱讀的名稱,例如: - `sort.h` ```c= typedef bool (*CMP)(int, int); void swap(int&, int&); // 換位置(置換空間) bool larger(int a, int b); bool smaller(int a, int b); void sort(int*, int, CMP); ``` - `sort.cc` ```cpp= #include "sort.h" void swap(int &a, int &b) { int t = a; a = b; b = t; } bool larger(int a, int b) { return a > b; } bool smaller(int a, int b) { return a < b; } void sort(int* arr, int length, CMP compare) { int flag = 1; for(int i = 0; i < length-1 && flag == 1; i++) { flag = 0; for(int j = 0; j < length-i-1; j++) { if(compare(arr[j+1], arr[j])) { swap(arr[j+1], arr[j]); flag = 1; } } } } ``` 執行結果 ``` 大的在前 9 6 5 3 1 小的在前 1 3 5 6 9 ``` 我們可以看到,重新使用typedef定義CMP名稱後,函式比較容易閱讀的多了。 另外,我們亦可以宣告函式指標陣列,例如: ```cpp bool (*compare[10])(int, int); ``` 上面這個宣告產生具有10個元素的陣列,可以儲存10個sort函式型態的位址,不過這樣的宣告實在難以閱讀,可以使用typedef來改進: ```cpp typedef bool (*CMP)(int, int); CMP compare[10]; ``` 可以看到這次的宣告比較容易閱讀了。