函式指標


程式在執行時,函式在記憶體中也佔有一個空間,將函式名稱作為指定來源時,函式名稱會自動轉為指標,型態由傳回值型態與參數列決定,若要將之指定給另一函式指標,型態的宣告方式如下:

傳回值型態 (*名稱)(參數列);

函式指標代表著一個函式,相同型態的函式可以指定給具有相同型態的指標,例如:

#include <iostream> 
using namespace std; 

int foo(int); 

int main() { 
    int (*fp)(int) = foo; 

    foo(10);  // 顯示 10
    fp(20);   // 顯示 20

    return 0; 
} 

int foo(int n) { 
    cout << "n = " << n << endl; 
    return 0; 
}

foo 指定給 fp,等效於 &foo 指定給 fp,在指定之後,fp 儲存了 foo 的位址,在呼叫時,fp(20) 等效於 (*fp)(20)

重載函式具有不同的簽署,因此在指定時,雖然具有相同名稱,然而依函式指標的型態不同,編譯器會選擇對應的重載函式:

#include <iostream> 
using namespace std; 

void foo(int); 
int foo(int, int);

int main() { 
    void (*fp)(int) = foo; 
    int (*add)(int, int) = foo; 

    foo(10);                                  // 顯示 10
    cout << "1 + 2 = " << add(1, 2) << endl;  // 顯示 1 + 2 = 3

    return 0; 
} 

void foo(int n) { 
    cout << "n = " << n << endl; 
}

int foo(int a, int b) { 
    return a + b;
}

函式指標可以用來傳遞函式,例如,你想撰寫用於陣列的 sort 函式,希望大小順序可以由呼叫者指定,這就可以傳遞函式來指定,例如:

#include <iostream> 
using namespace std; 

void sort(int*, int, bool (*compare)(int, int));
bool ascending(int, int);
bool descending(int, int);

int main() { 
    int number[] = {3, 5, 1, 6, 9};

    sort(number, 5, ascending);
    // 顯示 1 3 5 6 9
    for(auto n : number) {
        cout << n << " ";
    }
    cout << endl;

    sort(number, 5, descending);
    // 顯示 9 6 5 3 1
    for(auto n : number) {
        cout << n << " ";
    }
    cout << endl;

    return 0; 
} 

void swap(int &a, int &b) {
    int t = a; 
    a = b; 
    b = t;
}

void sort(int* arr, int length, bool (*compare)(int, int)) { 
    for(int flag = 1, 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; 
            } 
        } 
    } 
}

bool ascending(int a, int b) {
    return a < b;
}

bool descending(int a, int b) {
    return a > b;
}

在這個例子中,sort 上的函式指標宣告有些難以閱讀,可以使用 typedef,定義一個比較容易閱讀的名稱,例如:

#include <iostream> 
using namespace std; 

typedef bool (*CMP)(int, int);

void sort(int*, int, CMP);
...略

void sort(int* arr, int length, CMP compare) { 
    for(int flag = 1, 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; 
            } 
        } 
    } 
}

...略

C++ 11鼓勵使用 using 來取代 typedef,因為比較直覺,而且可以結合模版。例如:

using CMP = bool (*)(int, int);

另一個方式是使用 decltype,這可以就一個既有的型態來進行型態宣告。例如:

#include <iostream> 
using namespace std; 

bool cmp(int, int);

void sort(int*, int, decltype(cmp));
bool ascending(int, int);
bool descending(int, int);

...略

void sort(int* arr, int length, decltype(cmp) compare) { 
    for(int flag = 1, 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; 
            } 
        } 
    } 
}

...略

在參數的型態宣告複雜時,雖然不能使用 autodecltype 的運用可以稍微緩解一下型態宣告的負擔。

也可以宣告函式指標陣列,例如:

bool (*compare[10])(int, int) = {nullptr};

上面這個宣告產生具有 10 個元素的陣列,可以儲存 10 個 bool (*)(int, int) 型態的函式位址,目前都初始為 nullptr,不過這樣的宣告實在難以閱讀,可以使用 using 來改進:

using CMP = bool (*)(int, int);
CMP compare[10] = nullptr;

若是使用 decltype 的話,必須是:

bool cmp(int, int);
decltype(cmp) *compare[10] = {nullptr};

傳遞函式時多半使用函式指標,不過也可以建立函式參考,例如:

#include <iostream> 
using namespace std; 

int foo(int); 

int main() { 
    int (&fr)(int) = foo; 

    foo(10);  // 顯示 10
    fr(20);   // 顯示 20

    return 0; 
} 

int foo(int n) { 
    cout << "n = " << n << endl; 
    return 0; 
}

fr 就只是 foo 的別名,也可以參數列上宣告函式參考:

#include <iostream> 
using namespace std; 

void sort(int*, int, bool (&compare)(int, int));
bool ascending(int, int);
bool descending(int, int);

int main() { 
    int number[] = {3, 5, 1, 6, 9};

    sort(number, 5, ascending);
    // 顯示 1 3 5 6 9
    for(auto n : number) {
        cout << n << " ";
    }
    cout << endl;

    sort(number, 5, descending);
    // 顯示 9 6 5 3 1
    for(auto n : number) {
        cout << n << " ";
    }
    cout << endl;

    return 0; 
} 

...略

void sort(int* arr, int length, bool (&compare)(int, int)) { 
    for(int flag = 1, 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; 
            } 
        } 
    } 
}

...略

可以建立函式指標的陣列,然而,參考是別名,不是物件,無法建立參考的陣列(array of references),或許這是傳遞函式時,多半使用函式指標的原因。

C++ 11 提供了 function,定義於 functional 標頭檔,該類別的實例可以接受 Callable 物件,函式指標是其中之一,使用它來定義接受函式指標的參數,語法上會比較輕鬆一些,例如:

#include <iostream> 
#include <functional>
using namespace std; 

void sort(int*, int, function<bool(int, int)>);
bool ascending(int, int);
bool descending(int, int);

int main() { 
    int number[] = {3, 5, 1, 6, 9};

    sort(number, 5, ascending);
    // 顯示 1 3 5 6 9
    for(auto n : number) {
        cout << n << " ";
    }
    cout << endl;

    sort(number, 5, descending);
    // 顯示 9 6 5 3 1
    for(auto n : number) {
        cout << n << " ";
    }
    cout << endl;

    return 0; 
} 

...略

void sort(int* arr, int length, function<bool(int, int)> compare) { 
    for(int flag = 1, 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; 
            } 
        } 
    } 
}

...略