• 函数指针和回调函数的简单应用



    前言

    本博客主要记录一些函数指针和回调函数的简单应用;

    函数指针的简单应用

    上一篇博客我记录了什么是函数指针?
    那么函数指针有什么应用呢?
    我们先来简单实现一个计算器;

    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;
    	
    	do {
    		menu();
    		printf("请输入你的选择:");
    		scanf("%d", &input);
    		switch (input)
    		{
    			int x = 0; int y = 0; int ret = 0;
    		case 1:
    			printf("请输入两个操作数:");
    			scanf("%d%d",&x,&y);
    			ret=Add(x, y);
    			printf("%d\n",ret);
    			break;
    		case 2:
    			printf("请输入两个操作数:");
    			scanf("%d%d", &x, &y);
    			ret=Sub(x, y); 
    			printf("%d\n", ret);
    			break;
    		case 3:
    			printf("请输入两个操作数:");
    			scanf("%d%d", &x, &y);
    			ret=Mul(x, y); 
    			printf("%d\n", ret);
    			break;
    		case 4:
    			printf("请输入两个操作数:");
    			scanf("%d%d", &x, &y);
    			ret=Div(x, y); 
    			printf("%d\n", ret);
    			break;
    		case 0:printf("退出计算器\n");
    			break;
    		default:printf("选择错误\n");
    		}
    	
    	} while (input);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    这个代码并不是最好的,同时给人的感觉总是那么的冗长,有很多重复的步骤;
    而且我们以后如果想要加入其它计算器的话,我们就需要在加一个case语句,然后又是重复上面的步骤,如果像这样的话,随着我们需求的不断增多,我们需要加入的case语句更是越来越多;我们有没有什么办法来优化一下呢?

    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;
    	do
    	{
    		menu();
    		printf("请输入两个数:");
    		scanf("%d",&input);
    		int(*(p[5]))(int, int) = {0,Add,Sub,Mul,Div};
    		int len = sizeof(p)/sizeof(p[0]);
    		if (!input)
    		{
    			printf("退出计算器\n");
    		}
    		else if (input >= 1 && input <= len - 1)
    		{
    			printf("请输入两个操作数:>");
    				int x = 0;
    			int y = 0;
    			scanf("%d%d",&x,&y);
    			int ret=p[input](x,y);
    			printf(">:%d\n",ret);
    		}
    		else
    		{
    			printf("选择错误\n");
    		}
    	} while (input);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    由于我们所写的函数类型都是一样的,自然的我们可以吧相同类型的函数装在一个数组里面,然后通过数组的访问方式去访问里面的元素,这大大的减少了代码量和重复量,对于后期代码的维护有着极大的方便、维护成本也是更少如果后期我们有了新的需求,我们只需在数组里面加入函数名就行了,其它不用动,相比于前面直接加case更加的简洁方便!!!同时这种借助数组的然后来实现某些功能的方式叫做跳转表!!!;

    什么是回调函数?

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

    利用冒泡排序实现qsort函数

    我们知道冒泡排序(升序)的主要思想就是相邻元素两两比较,较大大的元素,往后挪;
    每冒一趟,就可以右边冒出一个最大值:
    在这里插入图片描述
    当然我们每一趟都会在最右边冒出一个最大值,那么这个最右边的最大值就不用参与下一次的冒泡了,个实际参与冒泡的元素:N-1;后面依次这样进行:
    冒泡排序主要代码:

    void Bubble_Sort(int*a,int len)
    {
    	for (int i = 0; i < len - 1; i++)
    	{
    		for (int j = 0; j < len - 1 - i; j++)
    		{
    			if (a[j] > a[j + 1])
    			{
    				int tmp = a[j+1];
    				a[j + 1] = a[j];
    				a[j] = tmp;
    			}
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    既然大概解了冒泡排序,那么我们既然要模拟qsort那么我们就应该来了解一下qsort函数:
    在这里插入图片描述
    参数解释:
    在这里插入图片描述
    既然介绍了qsort函数,我们来简单使用一下:

    int cmp_int(const void* left, const void* right)
    {
    	return *(int*)left - *(int*)right;//由于我们这个函数是我们设计的,我们肯定知道我们要排的数组是什么类型的元素啊,于是我们直接强转成对应元素指针就行了
    }
    int main()
    {
    	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    	int len = sizeof(arr) / sizeof(arr[0]);
    	int (*cmp)(const void*, const void*) = &cmp_int;
    	qsort(arr,len,sizeof(int),cmp);
    	for (int i = 0; i < len; i++)
    	{
    		printf("%d ",arr[i]);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述
    当然我们还可以用它来比较结构体:

    typedef struct student
    {
    	char name[20];
    	char sex[10];
    	char ID[20];
    	int age;
    
    }ST;
    void printfArr(ST*p,int len)
    {
    	for (int i=0;i<len;i++)
    	{
    		printf("%s\n",(p+i)->name);
    	}
    }
    int cmp_name(const void* left, const void* right)
    {
    	return strcmp(((ST*)left)->name, ((ST*)right)->name);//这里该百年left和right的相减顺序,改变时降序还是升序
    }
    int main()
    {
    	ST arr[] = { {"mahuateng","man","12107980831",46},
    				{"leijun","man","12107930841",45},
    				{"lihongyan","man","1210794250831",21},
    				{"dongmingzhu","woman","321079320831",36},
    				{"zero","man","1124832752",19} };
    	int len = sizeof(arr) / sizeof(arr[0]);
    	int (*cmp)(const void*, const void*) = cmp_name;
    	qsort(arr, len, sizeof(ST), cmp);
    	printfArr(arr, len);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    以名字为结构体排序:
    在这里插入图片描述
    以年龄为结构体排序:

    typedef struct student
    {
    	char name[20];
    	char sex[10];
    	char ID[20];
    	int age;
    
    }ST;
    int cmp_age(const void* left, const void* right)
    {
    	return ((ST*)left)->age - ((ST*)right)->age;//这里就要改变比较方式,比较字符串和比较整数方式不一样
    }
    int main()
    {
    	ST arr[] = { {"mahuateng","man","12107980831",46},
    				{"leijun","man","12107930841",45},
    				{"lihongyan","man","1210794250831",21},
    				{"dongmingzhu","woman","321079320831",36},
    				{"zero","man","1124832752",19} };
    	int len = sizeof(arr) / sizeof(arr[0]);
    	int (*cmp)(const void*, const void*) = cmp_age;
    	qsort(arr, len, sizeof(ST), cmp);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述
    实际上qsort函数,底层是利用快速排序的思想来实现的,但是现在我们不利用快排的思想来实现,我们利用冒泡排序的思想来实现:
    既然是模拟实现,那我们就跟着正品库函数一样设计参数:
    在这里插入图片描述
    然后冒泡排序的框架写出来:
    在这里插入图片描述
    虽然我们不知道数据类型,但是我们知道这个数据所占空间地大小啊,因此,我们也能知道每个元素的首地址!!!,然后我们再把首地址强制转换为char*的指针,加上步长,我们就可以把相两个元素之间的元素进行交换了,一个字节一个字节的交换:
    在这里插入图片描述
    在这里插入图片描述
    就这样在宽度的范围里,一个字节一个字节的交换内容:
    因此主要代码实现:

    void My_qsort(void*arr,size_t len,size_t width,int(*cmp)(const void*,const void*))
    {
    	for (int i = 0; i < len - 1; i++)
    	{
    		for (int j = 0; j < len - 1 - i; j++)
    		{
    			char* left = (char*)arr + j * width;//
    			char* right = (char*)arr + (j + 1) * width;//left和right是相邻元素的首地址
    			if (cmp(left,right) > 0)//这里我们将首地址传过去,让使用者帮我们实现,两个元素之间的判断
    			{
    				for (int k = 0; k < width; k++)//交换宽度范围内的内容
    				{
    					char tmp = left[k];//由于不知道数据类型,只能一字节一字节的交换;
    					left[k] = right[k];
    					right[k] = tmp;
    				}
    			}
    
    		}
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    当然我们还能基于插入排序实现qsort函数:

    
    My_qsort2(void* arr, size_t len, size_t width, int(*cmp)(const void*, const void*))//插入排序实现;
    {
    	char key[100] = { 0 };//将待插入的数据先保存起来;
    	for (int i = 0; i < len - 1; i++)
    	{
    		int right = i;//记录待排序区间最后一个元素下标
    		char* end = (char*)arr + (right+1)* width;//先把待插入数据取出来先存起,然后表示空位置
    		for (int j = 0; j < width; j++)//保存起来
    		{
    			key[j] = end[j];
    		}
    		char* begin = NULL;//当前位置于key的元素进行比较
    		end;//空位//
    		while (right>=0)//开始进行比较
    		{
    			 begin = (char*)arr + right * width;
    			 end = (char*)arr + (right + 1) * width;
    			if (cmp(begin,key) > 0)//具体两个元素怎么比较,交给使用者实现
    			{
    				for (int k = 0; k < width; k++)
    				{
    					end[k] = begin[k];
    				}
    				right--;
    			}
    			else//满足条件直接退出
    				break;
    		}
    		end = (char*)arr + (right + 1) * width;//是break来到这,和循环完来到这
    		for (int t = 0; t < width; t++)//最后将待插入的数据插在空位置
    		{
    			end[t] = key[t];
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    当然还可以利用其它排序来完成。

  • 相关阅读:
    [安洵杯 2019]easy_serialize_php1
    MongoDB
    编程自学路线:开源免费的教育资源 | 开源专题 No.40
    PIL或Pillow学习2
    K8s云原生存储Rook详解
    【论文笔记】A Survey on Federated Learning: The Journey From Centralized to Distributed On-Site Learning and Beyond(综述)
    CilckHouse创建表
    【数据结构】二叉树
    Linux远程调试工具——gdbserver
    AM驱动架构—优质Mini-LED显示技术解决方案
  • 原文地址:https://blog.csdn.net/qq_62106937/article/details/126321754