Like Share Discussion Bookmark Smile

J.J. Huang   2019-11-25   C   瀏覽次數:

C語言 - 第三十章 | 函式進階議題 - 函式指標

函式指標

程式在執行時,函式本身在記憶體中也佔有一個空間,而函式名稱本身也就是指向該空間位址的參考名稱,當呼叫函式名稱時,程式就會去執行該函式名稱所指向的記憶體空間中之指令。

1
傳回值型態 (*指標名稱)(傳遞參數);

一個函式型態由傳回值型態與參數列決定,不包括函式名稱,一個函式指標可指向具有相同型態的函式,也就是具有相同傳回值型態和參數列的函式。

簡單的示範,它以函式指標ptr來取得函式foo()的位址,使用它來呼叫函式,這與使用foo()來呼叫函式具有相同的作用,程式以整數方式顯示兩個的記憶體空間是相同的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

int foo();

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

foo();
ptr();

printf("address of foo:%p\n", foo);
printf("address of ptr:%p\n", ptr);

return 0;
}

int foo() {
puts("function pointer");
return 0;
}
1
2
3
4
5
// 執行結果
function pointer
function pointer
address of foo:001813CF
address of ptr:001813CF


如果函式帶有參數,則函式指標本身的宣告也必須指定相同的參數型態與個數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

int foo1(int, int);
char foo2(int, char);

int main() {
int (*ptr1)(int, int) = foo1;
char (*ptr2)(int, char) = foo2;

ptr1(1, 2);
ptr2(3, 'c');

printf("address of foo1(int, int): %p\n", ptr1);
printf("address of foo2(int, char): %p\n", ptr2);

return 0;
}

int foo1(int n1, int n2) {
printf("foo1(int, int): %d\t%d\n", n1, n2);
return 0;
}

char foo2(int n, char c) {
printf("foo2(int, char): %d\t%c\n", n, c);
return c;
}
1
2
3
4
5
6
// 執行結果

foo1(int, int): 1 2
foo2(int, char): 3 c
address of foo1(int, int): 000213DE
address of foo2(int, char): 000213D9


應用的例子,假設要撰寫一個排序sort()函式,希望排序時可以由大至小,也可以由小至大,比較簡單的作法是在sort()加上一個額外的參數,可以傳入常數或列舉,例如如果指定1的話就由大至小,指定0的話就由小至大,不過,這需要在函式中加上額外的判斷。

為了簡化sort()的撰寫,可以傳入一個函式位址,函式中就無需額外的判斷。

1
2
3
4
5
// sort.h
void swap(int*, int*);
int ascending(int, int);
int descending(int, int);
void sort(int*, int, int (*)(int, int));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// sort.c
#include "sort.h"

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

int ascending(int a, int b) {
return a > b;
}

int descending(int a, int b) {
return a < b;
}

void sort(int* arr, int length, int (*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;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// main.c
#include <stdio.h>
#include "sort.h"

void print_array(int*, int);

int main() {
int number1[] = {3, 5, 1, 6, 9};
sort(number1, 5, ascending);
printf("大的在前 ");
print_array(number1, 5);

int number2[] = {3, 5, 1, 6, 9};
sort(number2, 5, descending);
printf("小的在前 ");
print_array(number2, 5);

return 0;
}

void print_array(int *arr, int len) {
for(int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
putchar('\n');
}
1
2
3
// 執行結果
大的在前 9 6 5 3 1
小的在前 1 3 5 6 9


在函式中,不必理會傳入的實際函式,只要呼叫compare()就可以了,在這個例子中,sort.hsort()的函式指標宣告有些難以閱讀,可以使用typedef,定義一個比較容易閱讀的名稱。

1
2
3
4
5
6
7
// sort.h
typedef int (*CMP)(int, int);

void swap(int*, int*);
int ascending(int, int);
int descending(int, int);
void sort(int*, int, CMP);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// sort.c
#include "sort.h"

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

int ascending(int a, int b) {
return a > b;
}

int descending(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;
}
}
}
}

重新使用typedef定義CMP名稱後,函式比較容易閱讀的多了。
也可以宣告函式指標陣列

1
int (*compare[10])(int, int);

上面這個宣告產生具有10個元素的陣列,可以儲存10sort函式型態的位址,不過這樣的宣告實在難以閱讀,可以使用typedef來改進。

1
2
typedef int (*CMP)(int, int);
CMP compare[10];

可以看到這次的宣告比較容易閱讀了。


註:以上參考了
函式指標