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

发布于:2023-01-19 ⋅ 阅读:(442) ⋅ 点赞:(0)


前言

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

函数指针的简单应用

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

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

这个代码并不是最好的,同时给人的感觉总是那么的冗长,有很多重复的步骤;
而且我们以后如果想要加入其它计算器的话,我们就需要在加一个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;
}

由于我们所写的函数类型都是一样的,自然的我们可以吧相同类型的函数装在一个数组里面,然后通过数组的访问方式去访问里面的元素,这大大的减少了代码量和重复量,对于后期代码的维护有着极大的方便、维护成本也是更少如果后期我们有了新的需求,我们只需在数组里面加入函数名就行了,其它不用动,相比于前面直接加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;
			}
		}
	}
}

既然大概解了冒泡排序,那么我们既然要模拟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;
}

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

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

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

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

在这里插入图片描述
实际上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;
				}
			}

		}

	}
}

当然我们还能基于插入排序实现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];
		}
	}
}

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

本文含有隐藏内容,请 开通VIP 后查看