C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第1张图片

目录

1.指针知识回顾 

2.数组知识回顾 

3.函数指针

3.1函数指针引入

3.2函数指针的使用

3.3阅读两段有趣的代码

3.4类型重定义,typedef补充

4.函数指针数组

4.1函数指针数组引入

4.2函数指针数组的使用(转移表)

 5.指向函数指针数组的指针


 

1.指针知识回顾 


①.指针就是个变量,用来存放地址,地址唯一标识了一片空间。

  内存会划分成一个个的内存单元,每个内存单元都有一个独立的编号,编号也称为地址,而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个字节。

③指针是有类型的,指针的类型决定了指针的+-整数的步长,指正解引用操作时候的权限。

列如一个字符类型的数据解引用只可以访问一个字节的内存空间 

2.数组知识回顾 

C语言基础 -数组

3.函数指针

3.1函数指针引入

指向函数的指针

整型指针-----指向整型数据的指针

字符指针----指向字符的指针

那么函数指针就是指向函数的指针,函数指针变量里面保存了函数的地址。

那就是说函数是不是应该有自己的地址,我们知道取地址数组名就得到函数的地址,那么对于函数来说,是不是也是取地址函数名就可以拿到函数的地址呢,我们来看一下。,见如下代码

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第2张图片 

 说明:&函数名和函数名都是函数的地址

那么现在如果我要把函数的地址保存到一个变量里边去,那么就应该是一个指针变量,我们思考一下那么这个指针变量的类型应该是什么样的呢?

 首先应该是一个指针

(*p)

接着这个指针的类型应该是函数指针类型,就应该是函数类型,函数类型应该有参数和返回值呀,所以:上述代码中的函数指针就可以写为:

int (*p)(int ,int) = &add;

P先和*结合,说明其为一个指针,指向一个函数,返回类型是int ,有两个参数

再来看一个例子:

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第3张图片 

 这个函数的指针该如何写呢?

我们应该关注两个点:函数的返回值,函数的参数类型

所以这个函数的函数指针就可以写为:

①void (*p)(char *,int [10]) = &test;//10也可以省略

注意第二个参数为数组类型,当然也可以写为:void (*p)(char *,int *) = &test;因为数组作为形参的本质还是一个指针。

②如果去掉括号可以吗?

形如void * p(char *,int [10]) = &test;

这样写就导致P先和()结合成为一个函数,void 和 * 结合变成一个返回值类型

3.2函数指针的使用

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第4张图片

这是从指针角度解引用然后进行传参使用。

然后我们对比一下下面这个写法,看看有什么结论

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第5张图片 

一样是可行的说明,这里的*号可有可无

3.3阅读两段有趣的代码

① (*(void(*)() )  0) ()

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第6张图片

然后末尾一个括号就是正常的函数传参

所以这段代码要表示的含义就是: 

调用0地址处的函数

第一步:把0强制类型转换为void(*)()类型的函数指针

第二步:解引用调用0地址处的函数

②void (*signal(int ,(void(*)(int)))(int)

 

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第7张图片

那么这段代码就这样理解

1.signal是一个函数声明

2.signal 函数有两个参数,第一个参数的类型是int,第二个参数的类型是 void(*)(int) 函数指针类型

3.该函数指针指向的函数有一个int类型的参数,返回类型是void

4.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)

 例子引入:计算器实现加减乘除(只针对整数的运算),如果没有引入这个函数指针数组我们会怎么写呢

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第8张图片

我们发现四个函数,除了执行的运算不一样,函数的返回类型和参数个人、类型都是一样的,那么如果我们要把函数的地址保存在一个指针变量里面,这个指针变量的类型是不是应该都是一样的,都是 int (*)(int,int)类型,那么我们是不是就可以把这四个指针放到一个数组里面呢(数组的每一个元素的类型是一样的) 

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第9张图片

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第10张图片

 如果没有引用到函数指针数组我们来看一下这个计算器的实现过程:

#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语句就会越来越长。所以我们需要更好更简洁的代码书写方式:

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并且冗余的代码,并且后续添加的时候只用修改很小一部分内容。

我们看一下实现效果:

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针_第11张图片

 5.指向函数指针数组的指针

指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针。

形式:

int (*pf)(int ,int)//这是函数指针

int(*pfarr[4])(int ,int) //这是函数指针数组

数组可以取地址:&pfarr 这是函数指针数组的指针。

在函数指针数组的基础上得到指向函数指针数组的指针

int (*(*p)[4])(int ,int) = &pfarr

 数组和指针我个人决定就有点像面多加水,水多加面的相互状态,大家可以多多理解使用。这是本期的所有内容。欢迎大家指正与讨论,创作不易,希望可以收获到大家的三连。

你可能感兴趣的:(c语言,数据结构,c++,算法)