四、模拟实现qsort函数
这里我是基于冒泡函数的思路来实现qsort函数的(实际上qsort函数的排序思路是快速排序)
冒泡排序的设计在本篇文末
🧩冒泡排序
#include<stdio.h> 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 - i - 1; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } int main() { int arr[] = { 3,1,7,5,8,9,0,2,4,6 }; int sz = sizeof(arr) / sizeof(arr[0]); printf("冒泡排序前:\n"); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } //冒泡排序 bubble_sort(arr, sz); printf("\n冒泡排序后:\n"); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
运行结果:
- 这里我们发现bubble_sort函数中用来接收待排序数组首元素地址的
指针arr
已经被写死了,是int*类型
,这就表它只能对整型数组进行排序。 - 其次函数内部对数组元素的比较和交换只适用于
int类型
的数据。
现在将利用冒泡排序来实现qsort函数,让它能排序任意类型的数据,该怎么做呢?
- 首先我们知道qsort函数的创作者,他并不知道我们将来需要排序什么类型的数组,但是呢?他却通过qsort函数实现了各种类型数组的排序,这是怎么做到的呢?这就得益于这个函数的
4个参数
了。 - 因此,只要我们给qsort函数提供
待排序数组首元素的地址
,数组中元素的个数
,数组中每个元素所占内存空间的字节大小
,以及一个比较函数
就能实现对这个数组的排序。所以我们也可以通过这些参数来用冒泡排序的思想实现对任意类型数组的排序。
🧩bubble_sort函数(模拟实现的qsort函数)
值得注意的是,这里说的利用冒泡排序来实现
qsort
函数,仅仅是实现了qsort
函数可以对任意类型的数组进行排序这一特点,并不是说实现了qsort
函数的底层原理,qsort
的底层其实是通过快速排序
来实现的。
//利用冒泡排序实现qsort void Swap(char* e1, char* e2, size_t width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *e1; *e1 = *e2; *e2 = tmp; e1++; e2++; } } //注意:这里的compar函数需要根据待排序的类型来书写 void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2)) //第一个参数 - 用来接收待排序数组的首元素地址,因为待排序的数组元素类型不确定,所以形参数组用void*来接收 //第二个参数 - 用来接收数组元素个数 //第三个参数 - 用来接收数组中每个元素的大小,单位是字节 //第四个参数 - 用来接收一个比较函数,根据待排序数组元素的类型来传递对应类型的比较函数 { int i = 0; //趟数 for (i = 0; i < sz - 1; i++) { int flag = 1;//假设数组是排序好的 //一趟冒泡排序的过程 int j = 0; for (j = 0; j < sz - 1 - i; j++) { if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0) //因为我们并不知道数组元素的类型,所以需要将元素转化为最小的char*类型, //即把arr强转为char*类型,arr就可以正常使用,且char*与width配合能访问到任意类型任意位置处的数组元素 //char类型指针+1只会跳过一个字节,+ j*width表示跳过j个元素 { //交换 //由于这里的数组名已经被强转为char类型的指针 //所以要交换数组中的元素,就只能一个字节一个字节进行交换 Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width); //前两个参数是待交换元素的地址,第三个参数是待交换元素的所占字节的大小 flag = 0;//如果数组元素进行交换了,说明数组还没有排好序 } } if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序 { break; } } }
🚩Swap函数剖析
Swap
函数用于交换两个元素的内容,它接受三个参数,这三个参数的作用如下:
void Swap(void* e1, void* e2, size_t width);
- void* e1: 指向第一个待交换元素的指针。由于数组的元素类型是未知的,所以使用 void* 类型来表示元素的指针。在函数内部,你需要将其转换为正确的类型,以便进行元素交换。
- void* e2: 指向第二个待交换元素的指针。同样,你需要在函数内部将其转换为正确的类型,以便进行交换操作。
- size_t width: 表示每个元素所占的字节数。由于元素类型未知,但在 bubble_sort 函数中有提供,所以通过这个参数确保在进行元素交换时能够正确地按字节进行操作。
- 在
Swap
函数内部,通过使用width
参数,以字节为单位逐个交换两个元素的内容。这种设计使得Swap
函数在不知道元素实际类型的情况下,仍能够正确地交换元素内容。
虽然在实际代码中,可能会使用更高级的语言特性来进行元素交换(例如 C++ 中的模板函数或 C 中的宏),但是在这个示例中,通过使用void*
指针和字节级的操作
,实现了一个通用的元素交换函数。
🧩利用bubble_sort函数排序整型数组
代码展示:
#include<stdio.h> //利用bubble_sort函数排序整型数组 void Swap(char* e1, char* e2, size_t width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *e1; *e1 = *e2; *e2 = tmp; e1++; e2++; } } void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2)) { int i = 0; for (i = 0; i < sz - 1; i++) { int flag = 1;//假设数组是排序好的 int j = 0; for (j = 0; j < sz - 1 - i; j++) { if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)//实现升序 { Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width); flag = 0;//如果数组元素进行交换了,说明数组还没有排好序 } } if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序 { break; } } } //比较函数 int cmp_int(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } //主函数 int main() { int arr[] = { 3,1,7,5,8,9,0,2,4,6 }; int sz = sizeof(arr) / sizeof(arr[0]); printf("排序前:\n"); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } //排序 bubble_sort(arr, sz, sizeof(int), cmp_int); printf("\n排序后:\n"); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
运行结果:
🧩利用bubble_sort函数排序结构体数组
1. 【按姓名来排序】
代码展示:
//按姓名来排序 #include <stdio.h> #include <stdlib.h> #include <string.h> //利用bubble_sort函数排序结构体数组 void Swap(char* e1, char* e2, size_t width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *e1; *e1 = *e2; *e2 = tmp; e1++; e2++; } } void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2)) { int i = 0; for (i = 0; i < sz - 1; i++) { int flag = 1;//假设数组是排序好的 int j = 0; for (j = 0; j < sz - 1 - i; j++) { if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)//实现升序 { Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width); flag = 0;//如果数组元素进行交换了,说明数组还没有排好序 } } if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序 { break; } } } //声明一个结构体,并重命名为stu typedef struct student { char name[20]; int age; }stu; //比较函数 int compare_name(const void* a, const void* b) { return strcmp( ((stu*)a)->name, ((stu*)b)->name );//strcmp函数返回值与compare_name函数一致 } int main() { stu s[3] = { {"张三",20},{"李四",18},{"王五",25} }; int sz = sizeof(s) / sizeof(s[0]); printf("排序前:"); for (int i = 0; i < sz; i++) { printf("%s %d", s[i].name, s[i].age); if (i < sz - 1) printf(" | "); } //排序 bubble_sort(s, sz, sizeof(s[0]), compare_name); //打印 printf("\n排序后:"); for (int i = 0; i < sz; i++) { printf("%s %d", s[i].name, s[i].age); if (i < sz - 1) printf(" | "); } printf("\n"); return 0; }
运行结果:
2. 【按年龄来排序】
代码展示:
//按年龄来排序 #include <stdio.h> #include <stdlib.h> //利用bubble_sort函数排序结构体数组 void Swap(char* e1, char* e2, size_t width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *e1; *e1 = *e2; *e2 = tmp; e1++; e2++; } } void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2)) { int i = 0; for (i = 0; i < sz - 1; i++) { int flag = 1;//假设数组是排序好的 int j = 0; for (j = 0; j < sz - 1 - i; j++) { if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)//实现升序 { Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width); flag = 0;//如果数组元素进行交换了,说明数组还没有排好序 } } if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序 { break; } } } //声明一个结构体,并重命名为stu typedef struct student { char name[20]; int age; }stu; //比较函数 int compare_age(const void* a, const void* b) { return (((stu*)a)->age - ((stu*)b)->age); } int main() { stu s[3] = { {"张三",20},{"李四",18},{"王五",25} }; int sz = sizeof(s) / sizeof(s[0]); printf("排序前:"); for (int i = 0; i < sz; i++) { printf("%s %d", s[i].name, s[i].age); if (i < sz - 1) printf(" | "); } //排序 bubble_sort(s, sz, sizeof(s[0]), compare_age); //打印 printf("\n排序后:"); for (int i = 0; i < sz; i++) { printf("%s %d", s[i].name, s[i].age); if (i < sz - 1) printf(" | "); } printf("\n"); return 0; }
运行结果:
总结
回调函数和 qsort
是 C 语言编程中重要的概念,能够提供强大的灵活性和功能。通过理解回调函数的概念,我们可以将特定行为作为参数传递给其他函数,实现代码的模块化和解耦合。qsort
则为数组排序提供了便利,允许我们自定义比较逻辑以满足不同的需求。
通过以上的介绍和模拟实现,希望初学者们能够更好地理解回调函数和 qsort
的核心概念,为日后的编程实践打下坚实的基础。无论是构建灵活的程序结构还是优化代码性能,这些概念都将成为你编程工具箱中不可或缺的工具。
🔥今天的分享就到这里, 如果觉得博主的文章还不错的话, 请👍三连支持一下博主哦🤞