文章目录
概要
本次我们将要学习一个库函数,该函数可以将你需要排序的数据进行排序,任何类型的数据都可以,比如整形数组,字符数组,或者结构体。并且本章我也会自己写一个函数模拟qsort的实现。
qsort函数介绍
qsort是一个C语言里面的库函数,它用于将用户指定给它的数据进行排序,它的底层逻辑是使用快速排序算法。
函数引用的头文件
函数需要包含头文件stdio.h
函数参数介绍
void qsort(void* base, size_t num,
size_t size,
int (*cmp)(const void* p1, const void* p2));
这是函数的声明,其中它的参数有四个。
第一个是无类型指针,它用于接收第一个数据的地址。
第二个是正整数类型的整数,它用于接收数据元素的总个数。
第三个是正整数类型的整数,它用于接收第一个元素的大小,相当于所占内存空间。
第四个是一个函数指针,它指向的函数返回类型是int,参数是两个const修饰的无类型指针。
下面先说说那个函数指针指向的函数需要满足的条件。
函数指针
函数指针指向的那个函数需要用户自己写出来,作用是比较两个数据的大小,如果前一个数比后一个数大,那么就返回大于0的数,如果相等则返回0,如果小于则返回小于0的数。
假如你想比较两个整数的大小,你可以这样写。
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1-*(int *)p2;
}
提醒一下,由于p1,p2是无类型指针,不能直接解引用,所以需要
先强制转换成int*类型,才能解引用,比如上面这段代码,前一个数比后一个数大,则直接作差返回差值,便满足了函数需求。
介绍完了qsort函数,下面讲讲如何使用
//测试qsort排序整形数组(升序)
void print_int_arr(int arr[], int sz)
{
int i;
for (i = 0; i < sz; i++)
printf("%d ",arr[i]);
printf("\n");
}
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1-*(int *)p2;
}
void test1()
{
int arr[10] = { 2,3,5,1,4,7,6,9,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_int_arr(arr, sz);
}
int main()
{
test1();
return 0;
}
在test1()中,我们实现了整形数组进行升序排序,讲讲传参的过程,arr代表数组首元素的地址,也就是qsort需要的第一个数据的地址,第二个是数组元素个数,也就是qsort需要的数据个数,第三个是用sizeof操作符计算出来首元素的大小,单位是字节,第四个传的是我自己写的比较函数的地址,函数名相当于地址。
最后打印一下数组,看看排序效果。
再试试结构体的排序
//测试排序结构体的年龄大小
struct STU
{
char name[10];
int age;
};
int cmp_stu_age(const void* p1, const void* p2)
{
return ((struct STU*)p1)->age - ((struct STU*)p2)->age;
}
void test3()
{
struct STU arr[3] = { {"zhangsan",59},{"lisi",19},{"madan",70} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_age);
}
成功将结构体每个成员的年龄进行排序。
学会了如何使用qsort,那么下面就模拟实现一下qsort函数吧。
qsort函数模拟实现:
qsort函数排序的算法使用的是快速算法,而我使用的是冒泡排序的算法。
我的参数设置和qsort的设置很相似,就不再过多赘述,讲讲函数内部。
void bubble_myqsort(void* base, size_t sz, size_t width, int (*p)(const void* p1, const void* p2))
{
int i, j;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - i - 1; j++)
{
if (p((char*)base + width * j, (char*)base + (j + 1) * width) > 0)
{
swap((char*)base + width * j, (char*)base + (j + 1) * width, width);
}
}
}
}
在相邻元素比较的时候,我们需要判断两个数是否需要交换位置,先用函数指针调用,比较两个数据的大小,由于函数调用时,不确定指针指向的是哪种类型的数据,在下一次比较两个数据的时候,不能知道指针跳过多少字节,所以先将指针类型强制转换成char*类型,然后再用width获得每个数据的内存空间大小,则只需要让指针+width*j则可以跳到下一个数据的地址。
在交换时,因为不知道时哪种类型的数据,所以直接交换位置是行不通的,那么我们就需要获得两个数据的地址来进行相办法交换。
讲讲我的swap函数是如何交换两个未知类型数据的。
void swap(char* p1, char* p2, size_t width)
{
int i;
char temp;
for (i = 0; i < width; i++)
{
temp = *(p1 + i);
*(p1 + i) = *(p2 + i);
*(p2 + i) = temp;
}
}
参数为两个字符类型指针,用于存储两个数据的地址,width用于存放数据所占字节数。
不知道数据类型,则不能转换成对应类型的指针,也就不能解引用来进行交换,那么获得数据第一个字节的地址,以及它所占的宽度,那么我们就可以一个一个字节的交换,如上面代码,for循环width次,将每个字节的数据依次交换,则实现了两个数据位置的交换。
下面试试能不能将整形数据排序。
void test5()
{
int arr[10] = { 2,3,5,1,4,7,6,9,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_myqsort(arr, sz, sizeof(arr[0]), cmp_int);
print_int_arr(arr, sz);
}
ok,实现目标。
小结
qsort函数主要解决的是它可以让你不用思考使用哪种排序的算法,你只要将需要排序的数据传参给它,以及自己实现一个满足它要求的函数,用来比较两个数据大小的函数,则可以实现使用qsort函数排序。
再贴一下本次学习的qsort的测试,整形数组排序,字符数组排序,结构体姓名排序,结构体中的年龄排序,以及模拟实现的测试代码。需要的话可以自取。
头文件说明:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"qsort.h"
//测试qsort排序整形数组(升序)
void print_int_arr(int arr[], int sz)
{
int i;
for (i = 0; i < sz; i++)
printf("%d ",arr[i]);
printf("\n");
}
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1-*(int *)p2;
}
void test1()
{
int arr[10] = { 2,3,5,1,4,7,6,9,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_int_arr(arr, sz);
}
//测试排序字符数组
int cmp_char(const void* p1, const void* p2)
{
return *(char*)p1 - *(char*)p2;
}
void print_char_arr(char arr[], int len)
{
int i;
for (i = 0; i < len; i++)
printf("%c", arr[i]);
printf("\n");
}
void test2()
{
char arr[] = "acdbfe";
int len = strlen(arr);
qsort(arr, len, sizeof(arr[0]), cmp_char);
print_char_arr(arr, len);
}
//测试排序结构体的年龄大小
struct STU
{
char name[10];
int age;
};
int cmp_stu_age(const void* p1, const void* p2)
{
return ((struct STU*)p1)->age - ((struct STU*)p2)->age;
}
void test3()
{
struct STU arr[3] = { {"zhangsan",59},{"lisi",19},{"madan",70} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_age);
}
//测试排序结构体的名字
int cmp_stu_name(const void* p1, const void* p2)
{
return strcmp(((struct STU*)p1)->name, ((struct STU*)p2)->name);
}
void test4()
{
struct STU arr[3] = { {"zhangsan",59},{"lisi",19},{"madan",70} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_name);
}
//实现自己的bubble_qsort函数
void swap(char* p1, char* p2, size_t width)
{
int i;
char temp;
for (i = 0; i < width; i++)
{
temp = *(p1 + i);
*(p1 + i) = *(p2 + i);
*(p2 + i) = temp;
}
}
void bubble_myqsort(void* base, size_t sz, size_t width, int (*p)(const void* p1, const void* p2))
{
int i, j;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - i - 1; j++)
{
if (p((char*)base + width * j, (char*)base + (j + 1) * width) > 0)
{
swap((char*)base + width * j, (char*)base + (j + 1) * width, width);
}
}
}
}
//测试使用自己的qsort排序整形数据
void test5()
{
int arr[10] = { 2,3,5,1,4,7,6,9,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_myqsort(arr, sz, sizeof(arr[0]), cmp_int);
print_int_arr(arr, sz);
}
//测试使用自己的qsort排序字符数据
void test6()
{
char arr[] = "acdbfe";
int len = strlen(arr);
bubble_myqsort(arr, len, sizeof(arr[0]), cmp_char);
print_char_arr(arr, len);
}
//测试使用自己的qsort排序结构体名字数据
void test7()
{
struct STU arr[3] = { {"zhangsan",59},{"lisi",19},{"madan",70} };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_myqsort(arr, sz, sizeof(arr[0]), cmp_stu_name);
}
int main()
{
//test1();
//test2();
//test3();
//test4();
test5();
//test6();
//test7();
return 0;
}