C語言函數指針和回調函數


 徹底搞定C指針-函數名與函數指針

函數名&函數名取地址

函數指針

通常我們可以將指針指向某類型的變量,稱為類型指針(如,整型指針)。若將一個指針指向函數,則稱為函數指針。

函數名的意義

函數名代表函數的入口地址,同樣的,我們可以通過根據該地址進行函數調用,而非直接調用函數名。

 1 void test001(){
 2     printf("hello, world");
 3 }
 4 
 5 int main(){
 6 
 7     printf("函數入口地址:%d", test001);//qt中的函數入口地址不會變,C中會變,這里僅為了說明問題
 8     //test001();
 9     int *testADD = (int *)20123883;//將地址轉化為int型指針
10     void(*myfunc)() = testADD;//將函數寫成函數指針,有些書上會寫&testADD
11     myfunc(); //調用函數指針
12     system("pause");
13     return 0;
14 }

另外,還有以下結論:

(1)test001的函數名與myfunc函數指針都是一樣的,即都是函數指針。test001函數名是一個函數指針常量,而myfunc是一個函數指針變量,這是它們的關系。

 1 int test001(int a, char b){
 2     printf("hello, world\n");
 3     return 0;
 4 }
 5 
 6 int main(){
 7 
 8     int(*myFun)(int, char) = test001;
 9     myFun = test001;
10 
11     //下面四種表達式的結果是相同的
12     int a = 10;
13     char b = 's';
14     myFun(a, b);
15     (*myFun)(a, b);
16     test001(a, b);
17     (*test001)(a, b);
18 
19     system("pause");
20     return 0;
21 }

(2)testADD和&testADD的值一樣,但表達的含義不同,與數組名和“&數組名”類似,詳見指針和數組的關系

定義函數指針

定義函數指針最簡單的是直接定義函數指針變量,另外還有定義函數類型和定義函數指針類型。

 1 int test001(int a, char b){
 2     printf("hello, world\n");
 3     return 0;
 4 }
 5 
 6 void test002(){
 7 
 8     //定義函數類型
 9     typedef int(Fun)(int, char);
10     Fun *funFir = test001;
11 
12     //定義函數指針類型
13     typedef int(*FunP)(int, char);
14     FunP funSec = test001;
15 
16     //定義函數指針變量
17     int(*funThi)(int, char) = NULL;//若報錯,在強制轉型,(int(*)(int , char))NULL
18     funThi = test001;
19 }

函數指針用於形參

這種用法通常出現在回調函數中,一般回調函數用於定制操作,下面的例子將說明如何進行定制操作

 1 /*
 2 -----------------------
 3 函數指針用作另一個函數的參數
 4 -----------------------
 5 */
 6 int con1(int a, int b){
 7     return a + b;
 8 }
 9 
10 int con2(int a, int b){
11     return a - b;
12 }
13 
14 int con3(int a, int b){
15     return a + b + 10;
16 }
17 
18 //在函數體中顯式調用函數,將失去靈活性
19 //盡管我可以用switch實現三種con的切換
20 void doc(){
21     int a = 10;
22     int b = 20;
23     int ret = con1(a, b);
24 }
25 
26 //用如下的調用方式,調用者並不知道調用的哪個函數
27 //因此根據函數指針的函數原型可以自己實現新函數,並進行調用
28 int doc_p(int(*temp)(int ,char)){
29     int a = 10;
30     int b = 20;
31     int ret = temp(a,b);
32     return ret;
33 }
34 
35 /*
36 ---------------------
37 函數指針數組
38 ---------------------
39 */
40 void func1(){
41     printf("a");
42 }
43 void func2(){
44     printf("a");
45 }
46 void func3(){
47     printf("a");
48 }
49 
50 void test003(){
51     int(*func[3])();
52     func[0] = func1;
53     func[1] = func2;
54     func[2] = func3;
55 
56     for (int i = 0; i < 3; ++i)
57     {
58         func[i];
59     }
60 }

為什么我們要把函數作為參數來調用呢,直接在函數體里面調用不好嗎?

在這個意義上,“把函數做成參數”和“把變量做成參數”目的是一致的,就是以不變應萬變。形參是不變的,而實參是可以定制的。唯一不同的是,普通的實參可以由計算機程序自動產生,而函數這種參數計算機程序是無法自己寫出來的,因為函數本身就是程序,它必須由人來寫。所以對於回調函數這種參數而言,它的“變”在於人有變或者人的需求有變。

回調函數

回調函數和普通函數完成的功能是一樣的,但回調函數更靈活,普通函數在函數體中調用,失去了變量的靈活性,有點類似於模板編程。

(1)首先是通過內存偏移,訪問數組的各元素地址,兩種方法的結果相同

 1 void printAll(void *arr, int eleSize, int len){
 2     
 3     char *start = (char*)arr; //強制轉型
 4     for (int i = 0; i < len; ++i){
 5         printf("%d\n", start+i*eleSize);//內存偏移
 6     }
 7 }
 8 
 9 void test004(){
10     int arr[5] = {1,2,3,4,5};
11     printAll(arr, sizeof(int), 5);
12     printf("-------------------\n");
13     for (int i = 0; i < 5; ++i){
14         printf("%d\n", &arr[i]);
15     }
16 }
17 
18 int main(){
19     test004();
20 
21     system("pause");
22     return 0;
23 }

(2)對上面的函數用函數指針進行改寫

 1 //添加函數指針作形參,必須寫明變量名
 2 void printAll(void *arr, int eleSize, int len, void(*print)(void *data)){
 3     
 4     char *start = (char*)arr; //強制轉型
 5     for (int i = 0; i < len; ++i){
 6         char *eleAddr = start + i*eleSize;
 7         print(eleAddr);
 8         //print(start+i*eleSize);
 9     }
10 }
11 
12 //自定義的被調用函數
13 void Myprint(void * data){
14     int *p = (int *)data;
15     printf("%d\n", *p);
16 }
17 
18 void test004(){
19     int arr[5] = {1,2,3,4,5};
20     printAll(arr, sizeof(int), 5, Myprint);
21 }
22 
23 int main(){
24     test004();
25 
26     system("pause");
27     return 0;
28 }

(3)對上面的函數指針添加自定義的數據類型

 1 //添加函數指針作形參,必須寫明變量名
 2 void printAll(void *arr, int eleSize, int len, void(*print)(void *data)){
 3     
 4     char *start = (char*)arr; //強制轉型
 5     for (int i = 0; i < len; ++i){
 6         char *eleAddr = start + i*eleSize;
 7         print(eleAddr);
 8         //print(start+i*eleSize);
 9     }
10 }
11 
12 //自定義的被調用函數
13 void Myprint(void * data){
14     int *p = (int *)data;
15     printf("%d\n", *p);
16 }
17 
18 struct Person{
19     char name[64];
20     int age;
21 };
22 
23 //添加自定義的數據類型打印函數
24 void MyprintStruct(void * data){
25     struct Person *p = (struct Person  *)data;
26     printf("%s,%d\n", p->name, p->age);
27 }
28 
29 
30 
31 
32 void test004(){
33     int arr[5] = {1,2,3,4,5};
34     printAll(arr, sizeof(int), 5, Myprint);
35 
36     struct Person person[] = {
37         {"aaa", 10},
38         {"bbb", 20}
39     };
40     printAll(person, sizeof(struct Person), 2, MyprintStruct);
41 
42 }
43 
44 int main(){
45     test004();
46 
47     system("pause");
48     return 0;
49 }

回調函數最大的優勢在於靈活操作,可以實現用戶定制的函數,降低耦合性,實現多樣性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM