C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针
C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针
本文是一篇关于C语言函数指针的进阶教程,详细介绍了函数指针的基本概念、使用方法、函数指针数组以及指向函数指针数组的指针等知识点。通过理论讲解结合代码示例的方式,帮助读者深入理解这些较为复杂的C语言概念。
1. 指针知识回顾
- 指针就是个变量,用来存放地址,地址唯一标识了一片空间。
- 内存会划分成一个个的内存单元,每个内存单元都有一个独立的编号,编号也称为地址,而C语言也把地址叫做指针。地址(指针)需要存储在变量中,这个变量就被称为指针变量。
- 指针的大小是固定的4/8字节(32位平台、64位平台)
- 地址是由物理的电线产生的(高低频的电信号),32位机器对应的就是32根地址线,产生32个0/1序列,32个0/1组成的二进制序列,把这个二进制序列就作为地址,32个bit位才能存储这个地址也就是需要8个字节才能存储,所以在32位机器下,地址的大小就是4个字节。
- 对于64位机器来说, 对应的就是32根地址线,产生64个0/1序列,64个0/1组成的二进制序列,把这个二进制序列就作为地址,64个bit位才能存储这个地址也就是需要8个字节才能存储,所以在64位机器下,地址的大小就是8个字节。
- 指针是有类型的,指针的类型决定了指针的+-整数的步长,指正解引用操作时候的权限。
- 列如一个字符类型的数据解引用只可以访问一个字节的内存空间
2. 数组知识回顾
C语言基础 -数组
3. 函数指针
3.1 函数指针引入
指向函数的指针
整型指针-----指向整型数据的指针
字符指针----指向字符的指针
那么函数指针就是指向函数的指针,函数指针变量里面保存了函数的地址。
那就是说函数是不是应该有自己的地址,我们知道取地址数组名就得到函数的地址,那么对于函数来说,是不是也是取地址函数名就可以拿到函数的地址呢,我们来看一下。,见如下代码
说明:&函数名和函数名都是函数的地址
那么现在如果我要把函数的地址保存到一个变量里边去,那么就应该是一个指针变量,我们思考一下那么这个指针变量的类型应该是什么样的呢?
首先应该是一个指针
(*p)
接着这个指针的类型应该是函数指针类型,就应该是函数类型,函数类型应该有参数和返回值呀,所以:上述代码中的函数指针就可以写为:
int (p)(int ,int) = &add;
P先和结合,说明其为一个指针,指向一个函数,返回类型是int ,有两个参数
再来看一个例子:
这个函数的指针该如何写呢?
我们应该关注两个点:函数的返回值,函数的参数类型
所以这个函数的函数指针就可以写为:
- void (*p)(char *,int [10]) = &test;//10也可以省略
注意第二个参数为数组类型,当然也可以写为:void (*p)(char *,int *) = &test;因为数组作为形参的本质还是一个指针。 - 如果去掉括号可以吗?
形如void * p(char *,int [10]) = &test;
这样写就导致P先和()结合成为一个函数,void 和 * 结合变成一个返回值类型
3.2 函数指针的使用
这是从指针角度解引用然后进行传参使用。
然后我们对比一下下面这个写法,看看有什么结论
一样是可行的说明,这里的*号可有可无
3.3 阅读两段有趣的代码
- ((void()() ) 0) ()
然后末尾一个括号就是正常的函数传参
所以这段代码要表示的含义就是:
调用0地址处的函数
第一步:把0强制类型转换为void(*)()类型的函数指针
第二步:解引用调用0地址处的函数 - void (signal(int ,(void()(int)))(int)
那么这段代码就这样理解 - signal是一个函数声明
- signal 函数有两个参数,第一个参数的类型是int,第二个参数的类型是 void(*)(int) 函数指针类型
- 该函数指针指向的函数有一个int类型的参数,返回类型是void
- signal函数的返回类型也是void(*)(int) 函数指针类型,该函数指针指向的函数有一个int类型的参数,返回类型是void
那么对代码②进行简化:
3.4 类型重定义,typedef补充
平常我们说这样一个类型比如:unsigned int这个类型,我们觉得每次书写都太长了很麻烦,那我们就可以进行类型重定义:
typeddef unsigned int uint;
那么下次我们书写的时候就可以使用uint来代替unsigned int,使得类型简化。
同样的,对于指针类型也可以重定义,比如将int 重定义
typedef int ptr
那么原来的:int * p1
就可以改写为: ptr p1
同理,也可以对我们的数组指针或者函数指针类型进行重定义,但是稍微不同的是,重定义的类型名要和在一起。
比如:这是一个数组指针类型:int ()[10];如何进行类型重定义?
- typedef int (*)[10] parr_t 这样定义是错误的
正确的定义方法为:
typedef int (*parr_t )[10],重定义的类型名放在1括号里才行,对于函数指针来说也是一样的。
那么上面的代码就可以1这样来做:
typedef void(*ptr_t)(int)
那么:void (signal(int ,(void()(int)))(int)
就可以简化为: ptr_t signal(int ,ptr_t);
4. 函数指针数组
4.1 函数指针数组引入
引言:我们前面知道可以把一个整型类型的指针放入一个数组,比如:int * arr[10],可以把一个字符类型的指针放入一个数组,比如:char *arr[5],那么可不可以把统一类型的函数指针也放入一个数组里面呢。
我们来看一下函数指针数组的形式:
指针:int *p
函数指针:int (*p)(int ,int)
指针数组:int *parr[10]
那么函数指针数组首先肯定是数组,数组的每一个元素应该是函数类型的指针
那么函数指针数组的形式: int (*parr[5])(int,int)
例子引入:计算器实现加减乘除(只针对整数的运算),如果没有引入这个函数指针数组我们会怎么写呢
我们发现四个函数,除了执行的运算不一样,函数的返回类型和参数个人、类型都是一样的,那么如果我们要把函数的地址保存在一个指针变量里面,这个指针变量的类型是不是应该都是一样的,都是 int (*)(int,int)类型,那么我们是不是就可以把这四个指针放到一个数组里面呢(数组的每一个元素的类型是一样的)
如果没有引用到函数指针数组我们来看一下这个计算器的实现过程:
#include<stdio.h>
//定义执行加减乘除的四个运算
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x *y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("************************\n");
printf("****1.add 2.sub********\n");
printf("****3.mul 4.div********\n");
printf("****0.exit ************\n");
printf("************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:\n");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数:\n");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:\n");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:\n");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
defult:
printf("选择错误重新选择\n");
break;
}
} while (input);
return 0;
}
但是,这个代码虽然实现了功能,很多代码确实重复了,代码冗余,,第二个这个函数只能实现四个功能,但是像按位与等运算类型本质也是一样的,如果这样写case语句就会越来越长。所以我们需要更好更简洁的代码书写方式:
4.2 函数指针数组的使用(转移表)
看代码:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x *y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("************************\n");
printf("****1.add 2.sub********\n");
printf("****3.mul 4.div********\n");
printf("****0.exit ************\n");
printf("************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
int (*pfarr[])(int, int) = { NULL,Add,Sub,Mul,Div };
// 0 1 2 3 4
do
{
menu();
printf("请选择:\n");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:\n");
scanf("%d %d", &x, &y);
ret = pfarr[input](x, y);
printf("ret = %d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("选择错误,重新选择\n");
}
使用了函数指针,就直接省略了很长的Switch并且冗余的代码,并且后续添加的时候只用修改很小一部分内容。
我们看一下实现效果:
5. 指向函数指针数组的指针
指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针。
形式:
int (*pf)(int ,int)//这是函数指针
int(*pfarr[4])(int ,int) //这是函数指针数组
数组可以取地址:&pfarr 这是函数指针数组的指针。
在函数指针数组的基础上得到指向函数指针数组的指针
*int ((p)[4])(int ,int) = &pfarr*
数组和指针我个人决定就有点像面多加水,水多加面的相互状态,大家可以多多理解使用。这是本期的所有内容。欢迎大家指正与讨论,创作不易,希望可以收获到大家的三连。