此篇文章内容接我上一篇《关于C语言指针、数组和函数的相关内容》(关于C语言指针、数组和函数的相关内容_昵称就是昵称吧的博客-CSDN博客)所写,可以先看上一篇,也可以直接阅读此篇。
目录
一、函数指针
先看定义,函数指针顾名思义就是指向函数的指针,是存放函数地址的指针。那么函数的地址如何取出来了?我们看下面的代码。
#include<stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("p\n",test);
printf("p\n",&test);
return 0;
}
从上面代码,我们可以知道,取函数地址的方法有两种,第一种是用取地址符&,如&test;第二种是直接用函数名,如test。
我们发现和数组的写法是一样的,但是有一点不同,数组名是代表的首元素地址,而函数名则代表函数的地址。即函数名 test 和 &test 完全等价。
那么函数指针是如何用代码表示的了?老规矩,看代码。
#include<stdio.h>
int Add(int x,int y)
{
int z = x+y;
return z;
}
int main()
{
int (*p)(int int) = &Add; //p就是一个函数指针
return 0;
}
上面代码表示了函数指针,如何取理解了?(*p)代表是一个指针,而第一个 int 是函数的返回类型,括号里面的两个 int 是函数的参数类型,按照上面的写法,p就是一个存放函数地址的指针。
如何通过指针反过来调用函数了?看代码:
#include<stdio.h>
int Add(int x,int y)
{
int z = x+y;
return z;
}
int main()
{
int (*p)(int,int) = &Add; //p就是一个函数指针
int ret = (*p)(3,5); //通过指针进行函数的调用
return 0;
}
因为定义了 p 是 Add 这个函数的指针,通过解引用符 * ,即 (*p) ,就可以得到函数名,再用括号赋予相应的实参,就能进行函数的调用。
因为函数名和&函数名是完全等价的,那么函数名和函数指针是否有关系了?
#include<stdio.h>
int Add(int x,int y)
{
int z = x+y;
return z;
}
int main()
{
int (*p)(int,int) = &Add; //p就是一个函数指针
int ret = (*p)(3,5);//通过指针进行函数的调用
int cet = Add(3,5);
int det = p(3,5);
return 0;
}
运行的结果是:
8 8 8
以上代码可知,函数指针 p 和 Add 完全等价,因为函数名 Add 是代表的函数的地址,而函数指针 p 也是存放的函数的地址,所以 Add(3,5) 和 p(3,5) 结果一样。
所以用不用解引用符都可以,但是如果用了解引用符 * ,就必须将 * 和函数指针括起来,即(*p)(3,5),因为括号的优先级要高于解引用符。
那么函数指针的类型是如何用代码表示了?其实和数组指针一样。
int arr[5] = {0};
int (*p)[5] = &arr;
// int (*)[5] 去掉数组指针p就是数组指针的类型
int (*prr)(int,int) = Add;
// int (*)(int,int) 去掉函数指针prr就是函数指针的类型
我们发现函数指针的类型一般都很长,那有没有什么方法可以让这个类型不这么长,但是又不改变其本来的含义,看下面的代码。
#include<stdio.h>
int Add(int x,int y)
{
int z = x+y;
return z;
}
int main()
{
int (*p)(int,int) = &Add; //p就是一个函数指针
// int (*)(int,int)是该函数指针的类型
typedef int (*pAdd)(int,int);
// 此时该函数指针的类型是 pAdd
return 0;
}
通过上面代码可知,可以通过关键字 typedef 重新定义类型的名字。
可能有老铁就会问了,这样重新定义了有什么用了?其实我们可以用在函数的声明里,我们知道函数的声明只需要函数的返回类型、函数名和参数的类型即可,如果这个时候函数的返回类型或者函数的参数是函数指针,就可以用 typedef 定义的类型来减少代码的使用。如下代码所示。
// 以下是函数的申明
void test(int , void (*)(int,int));
typedef void (*a)(int,int); //重定义函数指针类型为a
void test(int , a);
//用了重定义后的函数申明,是不是看着简洁多了
二、 函数指针数组
我们知道数组是一个存放相同类型数据的储存空间,如指针数组,是用来存放指针的数组,如下代码所示:
int* p1;
int* p2;
int* p3;
int* arr[3] = {p1,p2,p3};
//此时的数组arr就是一个指针数组
所以函数指针数组就是一个用来存放函数指针的数组,还是一个数组,那么如何定义存放函数指针的数组了?
#include<stdio.h>
int Add(int x,int y)
{
int z = x+y;
return z;
}
int Sub(int x,int y)
{
int z = x-y;
return z;
}
int main()
{
int (*p1)(int,int) = &Add;
int (*p2)(int,int) = ⋐
int (*parr[2])(int,int) = {p1,p2};
int (*parr[2])(int,int) = {&Add,&Sub)};
int (*parr[2])(int,int) = {Add,sub};
// 上面三个等价,因为指针里面存放的就是地址
// 指针即地址,地址即指针
// 函数名就是函数的地址
return 0;
}
从上面代码可以知道,此时的数组parr就是函数指针数组,但是要注意的是函数的返回类型和参数类型必须一致才能放在同一个函数指针数组里。
关于上面等价的原因,也可以理解为数组里存放的是函数指针,而指针存放的是相应的函数的地址,所以本质上就是存放的地址 ,而函数名就是函数的地址,所以三个等价。
那么如何使用函数指针数组了?依旧是我们的 看代码环节:
#include<stdio.h>
int Add(int x,int y)
{
int z = x+y;
return z;
}
int Sub(int x,int y)
{
int z = x-y;
return z;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int (*p1)(int,int) = &Add;
int (*p2)(int,int) = ⋐
int (*parr[2])(int,int) = {Add,sub};
scanf("%d",&input);
getchar();
scanf("%d%d",&x,&y);
int ret = parr[input](x,y);
int ret = (*parr[input])(x,y);
// 上面两个等价
return 0;
}
可以看出有两种方式调用函数指针数组里面的函数。
第一种:parr[0](3,5),直接用下标访问数组里的元素,因为&数组名和数组名完全等价,所以可以至直接和实参并用就可以调用函数。
第二种:(*parr[0])(3,5),也是用下标访问数组里的元素,这里就是用的本质,是地址,所以通过解引用符来调用函数,注意加括号,因为优先级,再和实参结合实现函数的功能。
三、指向函数指针数组的指针
指向函数指针数组的指针是一个指针,这个指针指向一个数组,而这个数组里面的元素是函数指针,那么如何用代码定义了?
#include<stdio.h>
int Add(int x,int y)
{
int z = x+y;
return 0;
}
int main()
{
int (*p)(int,int) = Add;
int (*parr[2])(int,int) = {Add,Null,Null};
int ( *(*pparr)[2] ) = &parr;
// pparr就是一个指向函数指针数组的指针
return 0;
}
从上面代码可知指向函数指针数组的指针是如何定义的,其实可以做一个了解,因为在实际的编程应用的少。
四、回调函数
上面是书上的定义,我知道有很多老铁可能觉得不太好理解,用通熟的话来讲,就比如现在有两个函数,分别叫1号函数和2号函数,就比如将1号函数的地址让2号函数的参数接收,然后在2号函数内部通过指针调用1号函数,那么我们说这个1号函数就是回调函数。
本篇内容就分享到这里,如果各位老铁觉得有用,可以三连支持一下,我会不定期继续分享c语言的相关内容,争取用最通熟易懂的话让各位老铁明白!向你们敬礼,salute!