C语言函数指针和回调函数 函数名&函数名取地址

时间:2024-01-30 12:22:59

 彻底搞定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 }

回调函数最大的优势在于灵活操作,可以实现用户定制的函数,降低耦合性,实现多样性。