根据整形指针,数组指针的学习,我们不难得出函数指针变量就是用来存放函数地址的,可以通过地址来调用函数。
类比之前一维数组,二维数组,数组名是首元素的地址,对于函数,函数名就是函数的地址
我们用代码程序测试一下
#include<stdio.h>
void test()
{
printf("...\n");
}
int main()
{
test();
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
显而易见,函数名与&函数名的地址是一样的。
所以如果我们想要把函数的地址存放起来,那我们就要创建函数指针变量
void add(int x, int y)
{
return x + y;
}
int (*pf)(int , int ) = add;
int (*pf)(int x, int y) = &add;//x与y写上省略均可
函数指针变量解析
函数指针变量的写法与数组指针十分相似。
在学习的时候,还是要注意区分函数指针变量和数组指针变量
函数指针 int (*) ()
数组指针 int (*)[]
我们通过做几道题来熟悉一下函数指针变量
1.对于A选项,根据题目fun函数的参数一个是整型,一个是字符指针类型,而选项A pf指向的函数参数一个却是字符char类型,前后类型都不匹配显然错了。而B选项的函数指针形式就是正确的。对于分号后边,函数的地址直接用函数名和&函数名均可
2.根据题意,指针指向的函数参数类型为int*,返回值为int,如果我们明白了上边图片函数指针各部分的解析,这个题的答案就不难答出 为int (*fun) (int*)
3.声明一个含有10个元素的数组的指针,仅仅这句话,我们可以看出来是一个数组指针 (*p)[10],他的每个元素类型是函数指针类型且函数返回值为int类型,参数为int*,int(*)(int *),
两句话综合一下,int(*(*p)[10])(int*),
函数指针变量的使用
通过函数指针调用指针指向的函数
#include<stdio.h>
void add(int x, int y)
{
return x + y;
}
int main()
{
int (*p)(int x, int y) = add;
printf("%d\n", (*p)(2, 3));
printf("%d\n", p(4, 5));
两个代码
再给出两个代码
1.(*(void (*)())0)();
下面,我们将一点一点解释这条代码
首先(void(*)())这一部分是定义了一个函数指针的类型,表明这是一个指向返回值为void(无返回值)的函数的指针类型
然后(void(*)())0是将0强制转化成上述定义的函数指针类型
最后(*(void(*)())0)();通过解引用这个强制转化后的函数指针,并使用后边的括号来调用这个被认为位于地址0处的函数
不过,需要注意的是,在实际编程中,地址0通常是不可用和受保护的,这样的操作很容易导致程序崩溃或产生意想不到的错误
2.void(*signal(int,void(*)(int)))(int);
下面,我们再来一点一点解释这条代码
首先signal是一个函数的名称
”(int,void(*)(int))“这一部分是signal函数的参数列表,一个是int类型,一个是函数指针的类型,该指针指向的函数接受int类型的参数,并且没有返回值
然后整体的void(*)(int),这部分说明了signal函数的返回值的类型是一个函数指针,该指针指向的函数接受一个整型的参数,并且没有返回值
总的来说,这就是一个signal函数,他的两个参数一个是int类型一个是一个特定的函数指针,并且它的返回值类型也是一个函数指针
如果还是不理解,可以类比下面这个代码
#include<stdio.h>
void add(int x, int y)//可以类比这个
//这个是一个add函数,只不过他的两个参数都是int类型,并且返回值为void(没有返回值)
{
return x + y;
}
int main()
{
int (*p)(int x, int y) = add;
printf("%d\n", (*p)(2, 3));
printf("%d\n", p(4, 5));
}
typedef关键字
typedef的作用是对类型进行重命名,以使比较复杂的类型变得更简单
比如,对于unsigned int 类型,当使用比较频繁时,写起来特别麻烦,我们就可以把它重命名一下,比如我们将它命名成uint
typedef unsigned int unit;
//这里就把无符号整型重命名为uint
对于指针类型,我们也可以这样命名
typedef int* par_t;
//这里把整形指针重命名为par_t
当然对于比较复杂的数组指针和函数指针我们是不是也可以这样命名来使他简单化呢?
答案是肯定的,不过数组指针和函数指针相对于指针类型他的重命名的形式是有区别的
比如,我们想对数组指针类型int (*)[7]进行重命名,假如要重命名为ptr_t,
typedef int(*ptr_t)[7];//*一定要写在新的类型名的左边
//这里把数组指针类型重命名为ptr_t
函数指针的重命名与之相似
typedef int(*pf)(int);
//把函数指针类型重命名为pf
当我们了解了重命名之后,我们对上边那个signal函数就可以简化一下了
我们知道,上面第二个代码signal函数,他的参数中有一个函数指针的类型void (*)(int),而signal函数的返回值也是函数指针类型void (*)(int),理所当然,我们就可以对这个函数指针类型进行重命名,
typedef void(*ptrr_t)(int);
ptrr_t signal(int, ptrr_t);
这里我们先把void (*)(int)重命名为ptrr_t,然后把signal函数中的void (*)(Int)类型用新的类型名替换掉
这样当我们再去看signal函数是不是就更加清晰了呢?
函数指针数组
数组是一个存放相同类型数据的储存空间,整型数组int arr[5]存放了5个整型数据,指针数组int *arr[5],存放了5个int*类型的数据,相当于是放了5个整型数据的地址进去
如果我们想把函数的地址放进数组中储存起来,就要用到函数指针数组,
它的定义如下
int(*parr[5])();
根据优先级,parr先与【】结合起来,parr[]是一个数组,这个数组的内容是int (*)()类型的函数指针
学习了函数指针数组,我们就可以利用它实现一个转移表
函数指针数组实现转移表
首先我们把主函数写一下
int main()
{
int input = 0;
int a, b;
int ret = 0;
int (*pf[5])(int a,int b) = { 0,add,sub,mul,div };
do
{
menu();
printf("请选择:");
scanf("%d", &input);
if (input >= 1 && input <= 5)
{
printf("输入操作数:");
scanf("%d %d", &a, &b);
ret = (*pf[input])(a, b);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出程序\n");
}
else
{
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
在主函数里面,我们创建了一个函数指针数组,他的内容是函数指针类型数据,也就是函数的地址,所以我们这个函数指针数组的内容就是函数名,因为我们知道函数名就是函数的地址。这里的0是为了凑数组的第一个下标,使得后边调用数组中的函数名的时候能恰好对应用户输入的数字。
我们把这个函数指针数组的返回值赋给整型变量ret,(*pf[input])(a, b);这一点我们可以这样理解
首先pf是一个函数指针数组,pf[input]是索引在这个数组中,下标为input的元素,而这个元素是一个函数指针(地址),(*pf[input])是对这个函数指针进行解引用,得到指向的这个函数。
(*pf[input])(a,b)就是通过这个解引用得到的函数,传入a,b两个参数来调用这个函数进行计算
在调用menu()函数和add sub mul div 等函数前,我们要先定义函数在使用
void menu()
{
printf("**********************\n");
printf("***1.add***2.sub******\n");
printf("***3.mul***4.div******\n");
printf("***0.退出程序*********\n");
printf("**********************\n");
printf("**********************\n");
}
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
将两部分整合到一起,一个利用函数指针数组实现的转移表就成功了
作者有话说:作者只是一只初学的小白,以上的解释分析均是作者自己在学习后对相关知识的理解,如有错误,感谢指出;如感到有帮助,请留下一个👍