函数指针
通常我们可以将指针指向某类型的变量,称为类型指针(如,整型指针)。若将一个指针指向函数,则称为函数指针。
函数名的意义
函数名代表函数的入口地址,同样的,我们可以通过根据该地址进行函数调用,而非直接调用函数名。
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;//将函数写成函数指针,有些书上会写&testADD11 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))NULL18 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 }
回调函数最大的优势在于灵活操作,可以实现用户定制的函数,降低耦合性,实现多样性。