目录
1.指针知识回顾
2.数组知识回顾
3.函数指针
3.1函数指针引入
3.2函数指针的使用
3.3阅读两段有趣的代码
3.4类型重定义,typedef补充
4.函数指针数组
4.1函数指针数组引入
4.2函数指针数组的使用(转移表)
5.指向函数指针数组的指针
①.指针就是个变量,用来存放地址,地址唯一标识了一片空间。内存会划分成一个个的内存单元,每个内存单元都有一个独立的编号,编号也称为地址,而C语言也把地址叫做指针。地址(指针)需要存储在变量中,这个变量就被称为指针变量。
例如:
int a = 10;
int * p = &a;
这里的p就是一个指针变量
②指针的大小是固定的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个字节。
③指针是有类型的,指针的类型决定了指针的+-整数的步长,指正解引用操作时候的权限。
列如一个字符类型的数据解引用只可以访问一个字节的内存空间
C语言基础 -数组
指向函数的指针
整型指针-----指向整型数据的指针
字符指针----指向字符的指针
那么函数指针就是指向函数的指针,函数指针变量里面保存了函数的地址。
那就是说函数是不是应该有自己的地址,我们知道取地址数组名就得到函数的地址,那么对于函数来说,是不是也是取地址函数名就可以拿到函数的地址呢,我们来看一下。,见如下代码
说明:&函数名和函数名都是函数的地址
那么现在如果我要把函数的地址保存到一个变量里边去,那么就应该是一个指针变量,我们思考一下那么这个指针变量的类型应该是什么样的呢?
首先应该是一个指针
(*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 和 * 结合变成一个返回值类型
这是从指针角度解引用然后进行传参使用。
然后我们对比一下下面这个写法,看看有什么结论
一样是可行的说明,这里的*号可有可无
① (*(void(*)() ) 0) ()
然后末尾一个括号就是正常的函数传参
所以这段代码要表示的含义就是:
调用0地址处的函数
第一步:把0强制类型转换为void(*)()类型的函数指针
第二步:解引用调用0地址处的函数
②void (*signal(int ,(void(*)(int)))(int)
那么这段代码就这样理解
1.signal是一个函数声明
2.signal 函数有两个参数,第一个参数的类型是int,第二个参数的类型是 void(*)(int) 函数指针类型
3.该函数指针指向的函数有一个int类型的参数,返回类型是void
4.signal函数的返回类型也是void(*)(int) 函数指针类型,该函数指针指向的函数有一个int类型的参数,返回类型是void
那么对代码②进行简化:
平常我们说这样一个类型比如: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);
引言:我们前面知道可以把一个整型类型的指针放入一个数组,比如:int * arr[10],可以把一个字符类型的指针放入一个数组,比如:char *arr[5],那么可不可以把统一类型的函数指针也放入一个数组里面呢。
我们来看一下函数指针数组的形式:
指针:int *p
函数指针:int (*p)(int ,int)
指针数组:int *parr[10]
那么函数指针数组首先肯定是数组,数组的每一个元素应该是函数类型的指针
那么函数指针数组的形式: int (*parr[5])(int,int)
例子引入:计算器实现加减乘除(只针对整数的运算),如果没有引入这个函数指针数组我们会怎么写呢
我们发现四个函数,除了执行的运算不一样,函数的返回类型和参数个人、类型都是一样的,那么如果我们要把函数的地址保存在一个指针变量里面,这个指针变量的类型是不是应该都是一样的,都是 int (*)(int,int)类型,那么我们是不是就可以把这四个指针放到一个数组里面呢(数组的每一个元素的类型是一样的)
如果没有引用到函数指针数组我们来看一下这个计算器的实现过程:
#include
//定义执行加减乘除的四个运算 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语句就会越来越长。所以我们需要更好更简洁的代码书写方式:
看代码:
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并且冗余的代码,并且后续添加的时候只用修改很小一部分内容。
我们看一下实现效果:
指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针。
形式:
int (*pf)(int ,int)//这是函数指针
int(*pfarr[4])(int ,int) //这是函数指针数组
数组可以取地址:&pfarr 这是函数指针数组的指针。
在函数指针数组的基础上得到指向函数指针数组的指针
int (*(*p)[4])(int ,int) = &pfarr
数组和指针我个人决定就有点像面多加水,水多加面的相互状态,大家可以多多理解使用。这是本期的所有内容。欢迎大家指正与讨论,创作不易,希望可以收获到大家的三连。