深入理解指针(二)

六.函数指针数组

理解:是数组,存放的是函数指针,每一个函数指针存放的是函数地址

以下代码是模拟计算机的四则运算
观察代码不难看出在主函数main里的四种情况中,出现了大量重复
这样的代码过于繁琐,怎样可以简化呢?
四个函数的返回类型和参数都一样,只是函数名不同
这样就要借助函数指针数组了!

#include
void menu()
{
	printf("****************************\n");
	printf("***  1. add      2. sub  ***\n");
	printf("***  3. mul      4. div  ***\n");
	printf("***  0. exit             ***\n");
	printf("****************************\n");
}
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 input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误, 重新选择\n");
			break;
		}
	} while (input);

	return 0;
}

可以先把上述代码中的swich语句改为函数指针数组,如下:
注:1.将原来函数中重复的部分放到do—while循环中
2.为了和menu统一,数组首元素加上NULL,使得数组下标和menu统一

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		函数指针数组 - 转移表
		int (*pfarr[])(int, int) = {null, add, sub, mul, div};
		                          0     1     2   3    4
		if (0 == input)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = pfarr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("选择错误,重新选择!\n");
		}
	} while (input);

这里的pfar将四个函数联系在了一起,又叫做转移表

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

先回顾个稍微简单的概念:指向整形指针数组的指针

int a = 0;
int b = 0;
int c = 0;
int arr = {&a,&b,&c}; ——arr是一个整型指针数组
int* (*p)[3] = &arr;————p是指针,指向的是整型指针数组,该数组有三个元素,每个元素类型为int*

类比上述概念
刚刚写的pfarr就是一个函数指针数组
我们只需要取出pfarr地址,存到p中,不就得到了指向函数指针数组的指针了吗?
需要注意其类型在函数指针数组的基础上继续改造即可

int (*pfarr[])(int, int) = {null, add, sub, mul, div};
p = &pfarr
int(* (*p)[5])(int,int) = &pfarr

八.回调函数

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

回调函数是依赖函数指针的
有了函数指针,才能实现回调函数

例如:
函数A() ---------------------- 函数B(&A)
{ } ----------------------------------->{}
这种情况函数A是由A的指针调用的 ,就说A是回调函数

回到刚刚模拟的计算机,其实四个函数也可以用回调函数实现统一

	void menu()
{
	printf("****************************\n");
	printf("***  1. add      2. sub  ***\n");
	printf("***  3. mul      4. div  ***\n");
	printf("***  0. exit             ***\n");
	printf("****************************\n");
}
//+ - * / && || & | >> <<

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 calc(int (*pf)(int,int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入2个操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}

int main()
{
	int input = 0;

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误, 重新选择\n");
			break;
		}
	} while (input);

	return 0;
}

调用过程可参考下图
深入理解指针(二)_第1张图片

回调函数实例:

qsort函数
qsort函数是一个库函数
底层使用的是快速排列的方法对数据排列
这个函数可以直接使用,但要包含头文件
这个函数可以用来排列任意的数据类型

qsort函数具体是怎么使用的?先看看冒泡排序
目标:将arr数组的数据从小到大排列

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void bubble_sort(int arr[], int sz)
{
	//趟数
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//每一趟冒泡排序的过程
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

int main()
{
	//数据
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);//排序前打印
	bubble_sort(arr, sz);//冒泡排序
	print_arr(arr, sz);//排序后打印
	return 0;
}

冒泡排序只能排整型类型的数据,有局限性

而qsort函数可排序任意类型,它有四个参数:

1.待排序数组中第一个元素的地址
2.待排序数组的元素个数
3. 待排序数组中一个元素的大小,以字节为单位
4. 函数指针cmp指向了一个函数,这个函数是用来比较两个元素的

qsort(void* base,size_t num ,size_t size,int(* compar)(const void*e1,const void*e2)1            2             3               4
                                                      e1和e2存放的是需要比较两个元素的地址
                                                      void*指针不能进行解引用操作
                                                          是用来存放任意类型数据的地址的
                compar函数要求:e1>e2时,返回一个大于0的数
                               e1==e2时,返回一个0
                               e1<e2时,返回一个小于0的数                                           

不同数据类型的数据,比较出大小,方法是有差异的,因此第四个参数使用了函数指针,使其变得通用

测试qsort排序整型数据

#include 
#include 

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;//比较时需要强制类型转换
}

void test1()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);//
	print_arr(arr, sz);
}

结构体数据怎么比较呢?
1. 按照年龄比较
2. 按照名字比较

//测试qsort排序结构体数据
//1. 按照年龄比较
struct Stu
{
	char name[20];
	int age;
};

int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

void test2()
{
	struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}

/2. 按照名字比较

int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void test3()
{
	struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}

______________________________________________________________________________________-
______________________________________________________________________________________

下期继续深入,感谢阅读!

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