一篇文章让你搞懂函数指针数组,指向函数指针数组的指针,回调函数

1函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如
整型指针放在一个数组中

#include<stdio.h>
int main()
{
    
    
	//函数指针数组---数组中每个元素是函数指针类型
	int* arr1[5];//整型指针数组
	char* arr2[5];//字符指针数组
	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("***************************\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("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
    
    
		case 1:
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:");
			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;
}

如果我把

	printf("请输入两个操作数:");
	scanf("%d %d", &x, &y);

放到下面这段代码后面,逻虽然i解决了case语句冗余的问题,如果case语句有更多的话,冗余现象更明显,解决起来更麻烦,但是代码逻辑上是有问题的行不通的

	menu();
	printf("请选择:>");
	scanf("%d", &input);
	printf("请输入两个操作数:");
	scanf("%d %d", &x, &y);

为了解决casey=语句冗余的问题,现在我们就要用到函数指针数组
看下面代码

#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;
	//函数指针数组的使用 - 转移表
	int (* pfArr[5])(int, int) = {
    
    NULL, Add, Sub, Mul, Div};
    //                            0     1    2    3    4
	do
	{
    
    
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
    
    
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else if(input == 0)
		{
    
    
			printf("退出计算器\n");
		}
		else
		{
    
    
			printf("选择错误,重新选择\n");
		}
	} while (input);
	return 0;
}

对于下面这一行d代码给大家做一下解释

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

这里如果没有NULL的话,Add的起始位置就是1,后面一次加1,现在我要将Add放在1的位置上以便和下面的case语句对应上,Sub后面的同理,这个是个我们就多增加一个元素,我在Add前面放一个元素NULL,这时Add就自然而然的在1这个位置了,后面的同理
但是这个函数指针数组只适用于整型的运算
这里呢还是需要注意一下代码的书写习惯,要符合编译器的语法要求

int main()
{
    
    
	int* p;//正确
	int(*pf)(int, int);//正确
	int (*)(int, int)pf//写法错误
	return 0;
}

前两种正确,第三种写法不符合语法规定,应按照第二种来书写

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

再次复习一下前面讲过的数组和函数知识
数组名是数组首元素的地址
&数组名是整个数组的地址
函数
函数名是函数的地址
&函数名也是函数的地址

#include<stdio.h>
int main()
{
    
    
	int(*pf)(int, int);//函数指针
	int (*pfArr[4])(int, int);//函数指针数组
	int (*(*p[4]))(int, int);//p就是指向函数指针数组的指针
	return 0;
}

大家看到这里也许有点懵逼,没关系我给大家做一下解释
看下面代码

#include<stdio.h>
void test(const char* str)
{
    
    
	printf("%s\n", str);
}
int main()
{
    
    
	void (*pf)(const char*) = test;//pf是函数指针变量//这个大家没有问题
	void (*pfArr[10])(const char*);//pfArr是存放函数指针的数组//这个大家没有问题
	void (* (*p) [10])(const char*) = &pfArr;//p指向函数指针数组的指针//(*p)的()优先级更高,说明p是一个指针,这个指针指向的一个有10个元素的数组,这个数组是用于存放函数指针/所以p是指向函数指针数组的指针
	return 0;
}

8. 回调函数

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

#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");
}
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 = %d\n", ret);
}
int main()
{
    
    
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 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;
}

这里的pf相当于指向了我们要调用的函数,然后后面的代码根据时机进行调用
在这里插入图片描述
这就是回调函数的基本用法
C语言标准库里面有一个函数qsort,是用来排序的
此处我们将冒泡排序复习一下
这里注意一下我们要用到俩个for循环,外循环循环n-1趟,最后一趟已经是升序的格式了,所以就是n-1趟
对于内循环这则是在外循环每循环一趟的基础上再-1,下面请看代码演示

#include<stdio.h>
#include<Windows.h>
//冒泡排序
//有一组整数,需要排序为升序
//1. 两两相邻的元素比较
//2. 如果不满足顺序就交换
//
//当前的代码只适合于整型数据

	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[10] = {
    
     9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
    
    
		printf("%d ", arr[i]);
	}
	printf("\n");
	system("pause");
	return 0;
}

冒泡排序图片演示
在这里插入图片描述
好了,搞懂了冒泡排序之后我们=正式进入qsort函数的学习,大家可以先点击这个链接自己尝试着自主理解一下
qsort函数

void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));

在这里插入图片描述
对于qsort函数里面各个参数的意思我给大家做以下解释
我将解释附在代码后面了

void qsort(void* base, //指向了需要排序的数组的第一个元素
    size_t num, //排序的元素个数
    size_t size,//一个元素的大小,单位是字节
    int (*cmp)(const void*, const void*));//函数指针类型 - 这个函数指针指向的函数,能够比较base指向数组中的两个元素

现在我们就代码来给大家分析一下

//void qsort(void* base, //指向了需要排序的数组的第一个元素
//    size_t num, //排序的元素个数
//    size_t size,//一个元素的大小,单位是字节
//    int (*cmp)(const void*, const void*));//函数指针类型 - 这个函数指针指向的函数,能够比较base指向数组中的两个元素
//测试qsort排序结构体数据
//测试qsort排序整型数据
#include <stdlib.h>
#include<stdio.h>
#include<string.h>
int cmp_int(const void* p1, const void* p2)
{
    
    
	return (*(int*)p1 - *(int*)p2);
}
void print(int arr[], int sz)
{
    
    
	int i = 0;
	for (i = 0; i < sz; i++)
	{
    
    
		printf("%d ", arr[i]);
	}
}
void test1()
{
    
    
	int arr[10] = {
    
     3,1,5,2,4,7,9,6,8,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//默认是升序的
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
}
//测试qsort排序结构体数据
struct Stu
{
    
    
	char name[20];
	int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
    
    
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test2()
{
    
    
	struct Stu arr[] = {
    
     {
    
    "zhangsan", 20}, {
    
    "lisi", 50},{
    
    "wangwu", 15} };
	int sz = sizeof(arr) / sizeof(arr[0]); 
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
    
    
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test3()
{
    
    
	struct Stu arr[] = {
    
     {
    
    "zhangsan", 20}, {
    
    "lisi", 50},{
    
    "wangwu", 15} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
    
    
	test1();
	test2();
	test3();
	return 0;
}
//void* 的指针 - 无具体类型的指针
//void* 类型的指针可以接收任意类型的地址
//这种类型的指针是不能直接解引用操作的
//也不能直接进行指针运算的

这里qsort函数本质上是运用到了函数指针的知识
qsort的第四个函数指针的参数类型为什么为void呢?
这里就不得不给大家讲解一下void
的含义
void* 的指针 - 无具体类型的指针
void* 类型的指针可以接收任意类型的地址
这种类型的指针是不能直接解引用操作的
也不能直接进行指针运算的
对于返回值(return value)
在这里插入图片描述
返回大于/等于/或者小于0的数
int(*cmp)(const void, const void));//函数指针类型 - 这个函数指针指向的函数,能够比较base指向数组中的两个元素,这个是比较好理解的
那么对于qsort排序结构体数据我们应该怎么样测试呢?
我们来看下面代码

#include <stdlib.h>
#include<stdio.h>
#include<string.h>
int cmp_int(const void* p1, const void* p2)
{
    
    
	return (*(int*)p1 - *(int*)p2);
}
void print(int arr[], int sz)
{
    
    
	int i = 0;
	for (i = 0; i < sz; i++)
	{
    
    
		printf("%d ", arr[i]);
	}
}
void test1()
{
    
    
	int arr[10] = {
    
     3,1,5,2,4,7,9,6,8,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//默认是升序的
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
}
//测试qsort排序结构体数据
struct Stu
{
    
    
	char name[20];
	int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
    
    
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test2()
{
    
    
	struct Stu arr[] = {
    
     {
    
    "zhangsan", 20}, {
    
    "lisi", 50},{
    
    "wangwu", 15} };
	int sz = sizeof(arr) / sizeof(arr[0]); 
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
    
    
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test3()
{
    
    
	struct Stu arr[] = {
    
     {
    
    "zhangsan", 20}, {
    
    "lisi", 50},{
    
    "wangwu", 15} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
    
    
	test1();
	test2();
	test3();
	return 0;
}
//void* 的指针 - 无具体类型的指针
//void* 类型的指针可以接收任意类型的地址
//这种类型的指针是不能直接解引用操作的
//也不能直接进行指针运算的

这里我们需要将void*类型的指针强制类型转化为结构体指针
对于名字是一个字符串,我们需要运用strcmp函数进行字符串的比较
到了这里相信大家大体上明白了qsort函数的用法
现在我们来模拟实现以下qsort
因为我们没有学习过快速排序算法
所以我们使用冒泡排序的思想,实现一个功能类似qsort的函数
bubble_sort()
1.使用冒泡排序的思想
2.适用于任意类型数据的排序
我们还是先直接把代码给大家呈现出来,然后一步步的去分析

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素
{
    
    
	int i = 0;
	char tmp = 0;
	for (i = 0; i < size; i++)
	{
    
    
		tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{
    
    
	int  i = 0;
	//趟数
	for (i = 0; i < num - 1; i++)
	{
    
    
		int j = 0;
		//一趟内部比较的对数
		for (j = 0; j < num - 1 - i; j++)
		{
    
    
			//假设需要升序cmp返回>0,交换
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)//两个元素比较,需要将arr[j],arr[j+1]的地址要传给cmp
			{
    
    
				//交换
				Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}

}

struct Stu
{
    
    
	char name[20];
	int age;
};


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

//测试bubble_sort 排序结构体数据
//void test2()
//{
    
    
//	struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
//	int sz = sizeof(arr) / sizeof(arr[0]); 
//	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
//}

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


void test3()
{
    
    
	struct Stu arr[] = {
    
     {
    
    "zhangsan", 20}, {
    
    "lisi", 50},{
    
    "wangwu", 15} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sizeof(struct Stu));
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}


//B
int cmp_int(const void* p1, const void* p2)
{
    
    
	return (*(int*)p1 - *(int*)p2);
}


//测试bubble_sort 排序整型数据
void test1()
{
    
    
	int arr[10] = {
    
     3,1,5,2,4,7,9,6,8,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
}
int main()
{
    
    
	//test1();
	//test2();
	test3();

	return 0;
}

我们再次来看一下qsort函数的各个参数

void qsort(void* base, //指向了需要排序的数组的第一个元素
    size_t num, //排序的元素个数
    size_t size,//一个元素的大小,单位是字节
    int (*cmp)(const void*, const void*));//函数指针类型 - 这个函数指针指向的函数,能够比较base指向数组中的两个元素

void qsort(void* base, //指向了需要排序的数组的第一个元素
size_t num, //排序的元素个数
size_t size,//一个元素的大小,单位是字节
int (cmp)(const void, const void*));//函数指针类型 - 这个函数指针指向的函数,能够比较base指向数组中的两个元素
base:排序的数据的起始位置
num:从base开始向后有num个元素
size:一个元素的大小
类似于冒泡排序,外循环和内循环的趟数是不会改变的
唯一可以改变的是内循环里面的判断部分,看下图
在这里插入图片描述
对于结构体的比较不能简单的用大于小于符号进行操作了
对于不同类型的数据不能简单的使用大于小于符号进行比较
不同的数据类型交换方式有所差距
这里我们参照qsort函数的参数类型来写,类似于下面代码

void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))

这里面的Swap函数的原理是什么呢?
我给大家画个图演示一下
在这里插入图片描述
通过解引用拿到了第一个数据和第二个数据
因为一个整型占4个字节
所以char每一次访问一个字节每利用完一次中间变量进行两个整型的每一个字节的 交换,然后buf1++,buf2++依次交换完两个元素的字节,也就是交本质上进行了两个元素的交换
对于结构体我们应该怎么处理呢?
因为我们Swap函数传过去的参数已经告示我们了参数类型应该为char而不是void
这里的size大小就为24,说明我们的Swap函数内部要进行24次交换
两个字符串比较的时候,也就是strcmp函数在进行字符串比较的时候是每一个字符进行比较比如zhangsan和lisi,z大于l,所以进行交换,依此类推进行交换
最后给大家讲一下下面这个代码的意思

cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0

为什么要强制类型转化为char而不是其他指针类型呢?
size表示一个元素的大小,base是void
不能进行加减,需要进行强制类型转化转化为char*
因为我们的指针类型就决定了我们的指针类型指向多大的一块空间,所以要对一个内存进行连续的拷贝所以强制类型转为为char*,一个字节一个字节的跨过去
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/fjj2397194209/article/details/131599326