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 }
回调函数最大的优势在于灵活操作,可以实现用户定制的函数,降低耦合性,实现多样性。