指针进阶(三)(C 语言)

时间:2024-10-25 15:49:30

目录

  • 一、回调函数
  • 二、快速排序函数 qsort
    • 1. qsort() 函数原型
    • 2. 使用 qsort() 函数
  • 三、仿照 qsort 函数设计一个可以排序任意类型数组的冒泡函数
    • 1. 函数原型
    • 2. 函数设计思路

一、回调函数

在 C 语言中,回调函数是一种通过函数指针调用的函数,也就是一个被作为参数传递的函数。在特定的事件或条件发生时,主程序可以调用这个被传递进来的函数来执行特定的操作。

在指针进阶(二)(C 语言)中的最后我们使用函数指针数组模拟了简单的计算器,我们把其中的输入计算部分,封装成了一个函数 calc(),需要传入对应操作的函数地址,然后在 calc() 函数中通过函数指针来调用相应的函数。而这个被调用的函数就被称为回调函数。而在这里使用回调函数的主要作用是减少代码的冗余,如果不使用回调函数,那么 switch 语句部分就会是如下代码:

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

可以看到上述代码的前四个选项中,除了调用的函数不同,其他代码部分都是相同的。所以,前四个选项的功能设计成一个函数,根据不同的功能传递不同的函数指针,使用回调函数来完成相应的功能。这样减少了代码的冗余,提升了代码的可读性。

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

二、快速排序函数 qsort

C 语言标准库中的 qsort() 函数是专门用来给数组排序的,且只要给它提供一个合适的比较函数,它可以排序任意类型的数组。

1. qsort() 函数原型

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

可以看到上面函数原型中的第一个参数 base 是 void* 指针,因为该函数预先并不知道你要排序哪种类型的数组,所以只能使用 void* 指针来进行接收待排序数组的首地址。第二个参数 nmemb 的类型是 size_t 用来接收数组元素的个数。第三个参数 size 的类型是 size_t 用来接收数组每个元素的大小,单位字节。最后一个参数 compar 的类型是指向需要两个 const void* 的指针作为参数且返回值为 int 的数组,该指针的作用是回调我们传递的比较函数,因为 qsort 函数预先不知道我们需要比较的类型,可能是 int、double,也可能是字符串或者结构体。

2. 使用 qsort() 函数

接下来分别使用 qsort() 函数来排序 int 数组、double 数组、字符串数组以及结构体数组。需要分别为这四种类型设计四个比较函数:

// 头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 学生结构体
typedef struct Stu
{
	char name[20];
	char age;
}Stu;

// int 类型比较
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*) e2;
}

// double 类型比较
int cmp_double(const void* e1, const void* e2)
{
	return *(double*)e1 - *(double*)e2;
}

// 字符串类型比较
int cmp_str(const void* e1, const void* e2)
{
	return strcmp((char*)e1, (char*)e2);
}

// Stu 结构比较
int cmp_Stu(const void* e1, const void* e2)
{
	return (*(Stu*)e1).age - (*(Stu*)e2).age;
}

使用 qsort() 函数需要包含头文件 stdlib.h,使用字符串比较函数 strcmp() 需要包含头文件 string.h。下面是创建所需变量和排序的代码:

int main()
{
	// int 类型排序
	int arr_i[10] = { 1,3,5,7,9,2,4,6,8,0 };
	int sz = sizeof(arr_i) / sizeof(arr_i[0]);
	printf("排序前: ");
	print_arr_i(arr_i, sz);
	printf("排序后: ");
	qsort(arr_i, sz, sizeof(int), cmp_int);
	print_arr_i(arr_i, sz);

	// double 类型排序
	double arr_d[] = { 1.1,9.9,8.8,3.3,6.6,7.7,4.4,5.5 };
	sz = sizeof(arr_d) / sizeof(arr_d[0]);
	printf("排序前: ");
	print_arr_d(arr_d, sz);
	printf("排序后: ");
	qsort(arr_d, sz, sizeof(double), cmp_double);
	print_arr_d(arr_d, sz);

	// 字符串类型排序
	char arr_str[5][20] = {
		"abcde111",
		"ecdba222",
		"mnbvd333",
		"iknhk444",
		"cvbnm555"
	};
	sz = sizeof(arr_str) / sizeof(arr_str[0]);
	printf("\n排序前:\n");
	print_arr_str(arr_str, sz);
	printf("排序后:\n");
	qsort(arr_str, sz, sizeof(arr_str[0]), cmp_str);
	print_arr_str(arr_str, sz);

	// Stu 结构类型排序
	Stu arr_Stu[3] = {
		{"张三", 20},
		{"王五", 19},
		{"李四", 18}
	};
	sz = sizeof(arr_Stu) / sizeof(arr_Stu[0]);
	printf("\n排序前:\n");
	print_arr_Stu(arr_Stu, sz);
	printf("排序后:\n");
	qsort(arr_Stu, sz, sizeof(arr_Stu[0]), cmp_Stu);
	print_arr_Stu(arr_Stu, sz);

	return 0;
}

程序的运行结果如下:
在这里插入图片描述

三、仿照 qsort 函数设计一个可以排序任意类型数组的冒泡函数

1. 函数原型

// 仿照 qsort 函数设计一个可以排序任意类型数组的冒泡函数
void bubble_sort(void* start, size_t n, size_t size, int (*cmp)(const void* e1, const void* e2));

这里需要知道,我们实现的 bubble_sort() 函数和库函数 qsort() 只是排序使用的算法不同,其他的思路都是一样的,所以函数的参数和返回类型也是一样的。

2. 函数设计思路

进入函数 bubble_sort 以后,冒泡排序的整体是不变的,依旧是嵌套循环,外层是排序次数,内层是比较次数。但是由于预先不知道传入数组的元素类型,所以我们面临如下两个问题:
(1)在比较的时候如何传递每个元素的地址
(2)如何交换相邻的元素

首先,我们知道传入数组的首地址以及数组每个元素的大小。那么我们可以把数组首地址强制类型转换为 char* 类型,然后加上数组元素大小的整数倍,那么就可以得到数组的每个元素的地址了。思路如下图:
在这里插入图片描述
所以,就可以写出如下代码:

// 仿照 qsort 函数设计一个可以排序任意类型数组的冒泡函数
void bubble_sort(void* start, size_t n, size_t size, int (*cmp)(const void* e1, const void* e2))
{
	int i;
	for (i = 0; i < n - 1; ++i)  // 外层排序 n-1 轮
	{
		int j;
		for (j = 0; j < n - i - 1; ++j)  // 内层比较 n-i-1 次
		{
			if (cmp((char*)start + j * size, (char*)start + (j + 1) * size) > 0)  // 排升序
			{
				// 交换
			}
		}
	}
}

那么如何交换两个元素?我们知道两个元素的首字节地址,也知道两个元素的大小,那么我们可以通过交换两个元素的每个字节来实现两个元素的交换。思路如下图:
在这里插入图片描述

按理来说应该把这个交换功能设计成一个函数,但是这里为了方便观看,就放在函数内部了:

// 仿照 qsort 函数设计一个可以排序任意类型数组的冒泡函数
void bubble_sort(void* start, size_t n, size_t size, int (*cmp)(const void* e1, const void* e2))
{
	int i;
	for (i = 0; i < n - 1; ++i)  // 外层排序 n-1 轮
	{
		int j;
		for (j = 0; j < n - i - 1; ++j)  // 内层比较 n-i-1 次
		{
			if (cmp((char*)start + j * size, (char*)start + (j + 1) * size) > 0)  // 排升序
			{
				// 交换
				int k;
				for (k = 0; k < size; ++k)
				{
					char tmp = *((char*)start + j * size + k);
					*((char*)start + j * size + k) = *((char*)start + (j + 1) * size + k);
					*((char*)start + (j + 1) * size + k) = tmp;
				}
			}
		}
	}
}

接下来使用前面 qsort() 函数排序的数据检验我们所写的 bubble_sort() 函数,只需要把调用的函数名 qsort 改成 bubble_sort 即可:

int main()
{
	// int 类型排序
	int arr_i[10] = { 1,3,5,7,9,2,4,6,8,0 };
	int sz = sizeof(arr_i) / sizeof(arr_i[0]);
	printf("排序前: ");
	print_arr_i(arr_i, sz);
	printf("排序后: ");
	bubble_sort(arr_i, sz, sizeof(int), cmp_int);
	print_arr_i(arr_i, sz);

	// double 类型排序
	double arr_d[] = { 1.1,9.9,8.8,3.3,6.6,7.7,4.4,5.5 };
	sz = sizeof(arr_d) / sizeof(arr_d[0]);
	printf("排序前: ");
	print_arr_d(arr_d, sz);
	printf("排序后: ");
	bubble_sort(arr_d, sz, sizeof(double), cmp_double);
	print_arr_d(arr_d, sz);

	// 字符串类型排序
	char arr_str[5][20] = {
		"abcde111",
		"ecdba222",
		"mnbvd333",
		"iknhk444",
		"cvbnm555"
	};
	sz = sizeof(arr_str) / sizeof(arr_str[0]);
	printf("\n排序前:\n");
	print_arr_str(arr_str, sz);
	printf("排序后:\n");
	bubble_sort(arr_str, sz, sizeof(arr_str[0]), cmp_str);
	print_arr_str(arr_str, sz);

	// Stu 结构类型排序
	Stu arr_Stu[3] = {
		{"张三", 20},
		{"王五", 19},
		{"李四", 18}
	};
	sz = sizeof(arr_Stu) / sizeof(arr_Stu[0]);
	printf("\n排序前:\n");
	print_arr_Stu(arr_Stu, sz);
	printf("排序后:\n");
	bubble_sort(arr_Stu, sz, sizeof(arr_Stu[0]), cmp_Stu);
	print_arr_Stu(arr_Stu, sz);

	return 0;
}

运行结果和使用 qsort() 函数时是一样的:
在这里插入图片描述