函數指針
通常我們可以將指針指向某類型的變量,稱為類型指針(如,整型指針)。若將一個指針指向函數,則稱為函數指針。
函數名的意義
函數名代表函數的入口地址,同樣的,我們可以通過根據該地址進行函數調用,而非直接調用函數名。
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 }
回調函數最大的優勢在於靈活操作,可以實現用戶定制的函數,降低耦合性,實現多樣性。