C语言——指针进阶(2)

在这里插入图片描述
继续上次的指针,想起来还有指针的内容还没有更新完,今天来补上之前的内容,上次我们讲了函数指针,并且使用它来实现一些功能,今天我们就讲一讲函数指针数组等内容,废话不多说,我们开始今天的学习吧。

函数指针数组
在引出函数指针的时候,我们会先引出字符指针还有整型指针,那同样的道理,函数指针数组是什么,我们可能还是比较陌生,但是如果是字符指针数组,还有整型指针数组我们可能听起来不是特别陌生。
那再讲之前,我给大家先讲一下函数指针,函数指针是啥呢,函数指针竟然是指针,那就是来存放函数的地址的,那我们先写一个简单的函数,然后来存放它的地址。

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%pn", &Add);
	printf("%pn", Add);

	return 0;
}

我们可以发现打印出来的两个地址都是同一个地址,所以表示两个方式都可以取出函数的地址

在这里插入图片描述
在这里我们可以看到我们的地址是一样的,那我们要存放他们的地址改怎么写呢

我们来看看

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%pn", &Add);
	printf("%pn", Add);
	int (*pf)(int, int) = &Add;
	return 0;
}

这样我们就可以把它函数的地址写出来,这和那个数组指针也特别相似,取出的数组地址也是这样的。
因为时间太长,我帮大家在回忆一下我们的数组指针,比如我们现在要取出的是数组的地址,我们改怎么表示,让我们来看一看吧。

int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	int(*pa)[6] = &arr;
	return 0;
}

在这里我们可以看到的是数组指针,是指向数组地址的指针,可以看到它的格式其实和函数指针的差不多。
那我们的函数指针有什么用呢,答案是可以调用这个函数,比如就我们上面的Add函数,我们来看代码。

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%pn", &Add);
	printf("%pn", Add);
	int (*pf)(int, int) = &Add;
	return 0;
}

我们可以这样写

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%pn", &Add);
	printf("%pn", Add);
	int (*pf)(int, int) = &Add;
	int ret = (*pf)(3, 5);
	printf("%dn", ret);
	return 0;
}

在这里插入图片描述
在这里其实可以不写,意思就是int ret = (*pf)(3, 5);可以写成int ret = pf(3, 5);,其实这两个的结果是一样的,或者说就算我们加很多也不影响我们的结果,来看看吧

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%pn", &Add);
	printf("%pn", Add);
	int (*pf)(int, int) = &Add;
	int ret = (*pf)(3, 5);
	printf("%dn", ret);
	ret = printf("%dn", (*********pf)(4, 6));
	return 0;
}

写了这么多主要是为了后面做铺垫,那我们现在才来讲我们的函数指针数组
数组是什么,我们之前见过字符指针数组,他是来存放字符指针,我们也讲过整型指针数组,它是来存放整型指针的数组,那同样函数指针数组就是来存放函数指针的,我们来举个列子


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;
}

int main()
{
	int (*pf1)(int, int) = &Add;
	int (*pf2)(int, int) = &Sub;
	int (*pf3)(int, int) = &Mul;
	int (*pf4)(int, int) = &Div;
	return 0;
}

我们首先看到这么多的函数,他们的功能类似我们的计算器,这里就只写了加减乘除的功能,通过我们的代码我们可以看到的是四个一样的函数指针,那我们是不能就可以用数组的形似来存放它们。因为数组是不是能存放多个类型相同的元素。
那我们就可以来写一写看

int main()
{
	int (*pf1)(int, int) = &Add;
	int (*pf2)(int, int) = &Sub;
	int (*pf3)(int, int) = &Mul;
	int (*pf4)(int, int) = &Div;
	int(*pfarr[4])(int, int) = { &Add,&Sub,&Mul,&Div };
	//pfarr就是存放函数指针的数组
	return 0;
}

那函数指针数组难道就是一个摆设吗,不!!!它还是有一点作用的,那它的作用是什么呢,让我们来一起看看吧。
我们是不是就可以用它来实现一个计算器,我们来设计一个吧

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("***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("请选择>");
		scanf("%d" ,&input);
		switch (input)
		{
		case 0:
			printf("退出计算器n");
			break;
		case 1:
			printf("请输入两个操作数 :");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %dn", ret);
			break;
		case 2:
			printf("请输入两个操作数 :");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %dn", ret);
			break;
		case 3:
			printf("请输入两个操作数 :");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %dn", ret);
			break;
		case 4:
			printf("请输入两个操作数 :");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("ret = %dn", ret);
			break;
		default:
			printf("选择错误,请重新选择n");
			break;
		}
	} while (input);
	return 0;

}

虽然我们实现一个简单的计算器,但是其实我们的代码看起来并不是特别好看,反而有点冗余,重复的内容有点多,而且如果我们加一些其他的运算的时候,函数会变得越来越长,那我们该怎么改改让我们的代码看起来又好看又实用呢,这个时候我们就可以用函数指针数组进行改正,现在我们来对它进行整改。

我们用这个的时候之前一定要注意的问题是计算器这个函数的类型是一样的,否则其他的话就不行了。

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("***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("请选择>");
		scanf("%d", &input);
		int (*pf[5])(int, int) = { NULL,Add,Sub,Mul,Div };
		if (input == 0)
		{
			printf("退出计算器n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数 :");
			scanf("%d %d", &x, &y);
			ret = pf[input](x, y);
			printf("%dn", ret);
		}
	} while (input);
	return 0;

}

这样也能实现我们计算器的效果,而且好处也是特别大,首先解决了我们的代码冗余的现象,其次是我们如果想加入其他的运算,也可以直接加入,只要改一下数组的大小,并把函数的地址放到里面就可以解决问题,其实很简单。我们也将这个东西叫做转移表,这就是我们函数数组指针应用,但是这只是一个例子,其他还有很多的作用,这里就不扩展了。

指向函数指针数组的指针

一看这个标题不知道大家头是不是大了,反正小编一开始看这个有点云里雾里的感觉,但是经过一段时间的学习,发现它也就这啊,那现在我们开始举列子。

int a = 10;
int b = 20;
int c = 30;
int* arr[] = { &a,&b,&c };//整型指针数组

我们先来看简单的,然后慢慢的引入,先是整型指针数组,那如果是这样的一个问题,要指向整型指针数组的指针,我们该怎么写

int* (*parr)[3] = &arr;

这就是指向整型指针数组的指针,同样的道理我们来看指向函数指针数组的指针。

int (*pf[5])(int, int) = { NULL,Add,Sub,Mul,Div };

int(*(*ppf)[5])(int, int) = &pf;

我们就拿计算器的来写,就是这个样子的。
但是这个其实不用理解太深,稍微了解一下就知道了,不是特别重要。

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。

什么意思呢,就是假设我们有两个函数一个函数叫A,一个函数叫B,我们B函数的参数是A函数的地址,我们在B函数里使用这个函数A。
今天就讲一个例子,还是我们的计算器,下次给大家讲一个qsort,这也叫快速排序,让大家更好的理解我们的内容。

先把我们的计算器在拿出来给大家看一下

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("***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("请选择>");
		scanf("%d" ,&input);
		switch (input)
		{
		case 0:
			printf("退出计算器n");
			break;
		case 1:
			printf("请输入两个操作数 :");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %dn", ret);
			break;
		case 2:
			printf("请输入两个操作数 :");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %dn", ret);
			break;
		case 3:
			printf("请输入两个操作数 :");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %dn", ret);
			break;
		case 4:
			printf("请输入两个操作数 :");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("ret = %dn", ret);
			break;
		default:
			break;
		}
	} while (input);
	return 0;

}

我们可以把相同的部分也就是case语句分装成一个函数的思路去实现

我们可以看到我们的代码哪里是不太一样的地方,其实就是我们调用函数的地方,如果我们能写一个函数calc,来放这些函数的地址,问题是不是解决了,其实就是写成这个样子。

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("***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("请选择>");
		scanf("%d" ,&input);
		switch (input)
		{
		case 0:
			printf("退出计算器n");
			break;
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		default:
			break;
		}
	} while (input);
	return 0;

}

那现在要做的是什么,就是完成我们的calc函数这样就可以实现计算器的功能

calc我们的返回类型就是void 函数他们的地址,那我们是不是可以写成int (*pf)(int,int)


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("***1.Add  2.Sub ****n");
	printf("***3.Mul  4.Div ****n");
	printf("****0.exit      ****n");
	printf("********************n");
}

void calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数 :");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %dn", ret);
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择>");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出计算器n");
			break;
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		default:
			break;
		}
	} while (input);
	return 0;

}

这样就把之前的功能也实现了。我们后面再讲一个qsort,今天懒得讲了,摆烂ing

我们下次再见,拜拜