每日壁纸分享(出处: 极简壁纸_海量电脑桌面壁纸美图_4K超高清_最潮壁纸网站)
前言
qsort 是C语言中的一个库函数,它可以对任意类型的数据进行排序,而它的排序思想是快速排序,今天我将使用冒泡排序的思想来尝试实现。
目录
一,qsort函数
在开始之前,先介绍qsort函数,以便后续使用冒泡排序实现其功能。
1,qsort使用格式和传参要求
qsort函数是C语言中的一个库函数,使用该函数需要包含头文件 <stdlib.h>
在www.cplusplus.com 官网中我们可以查阅到该函数的传参格式如下:
void qsort (
void* base,
size_t num,
size_t size,
int (*compar)(const void*,const void*)
);
可以清楚地看到,使用qsort函数需要传递四个参数:
第一个:void *base,即是首元素地址,需要使用者将待比较排序数组地第一个元素地址传递过来。使用viod* 接收是因为无法确定使用者会传递什么类型的地址,故使用通用指针类型,可以接收到任意数据类型的地址。
第二个:size_t num,意为元素个数,需要使用这传递带比较数组的元素个数,建议不要写死元素个数,可以使用 sizeof(arr)/sizeof(arr[0]) 进行传递。
第三个:size_t size ,意为单个元素所占内存的大小,一般使用sizeof()来求取。第二个和第三个的类型之所以使用size_t 类型,是因为无论是元素个数还是单个元素所占内存大小,这都不可能是负数,故应当使用 unsigned int 类型更为合适。
第四个:需要传递一个函数用与两个数的比较(就是给出一种比较大小的方式),函数返回类型和参数类型及其个数如下:
int (*compar)(const void*, const void*)
需要对两个数进行比较:
当a>b时返回大于零的数字
当a<b时返回小于零的数字
当a=b时返回零
2,qsort函数的使用示例
理论是较为抽象的,接下来我将使用qsort函数对一组整型数组进行排序,以演示该函数的实现。
#include <stdio.h>
#include <stdlib.h>
int compare(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
//首先将要比较的数放进一个数组中,已乱序
int arr[] = { 8,9,0,1,2,5,3,4,7,6 };
//计算元素个数
int sz = sizeof(arr) / sizeof(arr[0]);
//排序前打印
printf("排序前\n");
print_arr(arr, sz);
printf("\n");
//调用qsort函数
//传参: 首元素地址 元素个数 单个元素所占内存空间大小 比较方式函数
qsort(arr, sz, sizeof(arr[0]), compare);
//排序后打印
printf("排序后\n");
print_arr(arr, sz);
return 0;
}
这里稍微提讲一下 compare函数,我们只需要其提供一种比较方式,使得当a>b时返回大于零的数字,当a<b时返回小于零的数字,当a=b时返回零。
在上述代码中,因为 compare 函数是以void*的指针类型接收参数,这是无法进行操作的,故我将其强制类型转换为 int*类型并解引用(因为我已知它是对一个整型数组进行排序),这样它就是一个int类型的数,此时使用e1 - e2,即可满足compare函数所需要的比较方式。
输出结果:
二,冒泡排序思想
冒泡排序的核⼼思想就是:两两相邻的元素进⾏⽐较。
代码如下:
#include <stdio.h>
void sort(int* p, int sz)
{
int i = 0;
//比较轮次
for (i = 0; i < sz - 1; i++)//sz是数组元素的个数,10个元素需要比较9次,即是sz-1
{
int j = 0;
int flag = 1;//假定排序完毕
for (j = 0; j < sz - 1 - i; j++)
//第一轮将第一个元素排好需要sz-1次,而每一轮会减少一个,故j<sz-1-i
{
if (*(p + j) > *(p + j + 1))
//当a1>a2就交换,因为前小后大
{
int tmp = 0;
tmp = *(p + j);
*(p + j) = *(p + j + 1);
*(p + j + 1) = tmp;
flag = 0;
}
}
if (flag == 1)
break;
}
return;
}
void print(int* pp, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",*(pp+i));
}
return;
}
int main()
{
int arr[15] = {0};
int i = 0;
int n = 0;
for (i = 0; i < 15; i++)
{
scanf("%d", &arr[i]);
}
int sz = &arr[15] - &arr[0];
sort(arr,sz);
print(arr,sz);
return 0;
}
由上我们可得到冒泡排序的思想:两层循环嵌套,外层循环为排序n个元素需要循环n-1次;内层循环为两个元素依次比较,若是满足条件便交换,从而实现排序。
三,使用冒泡排序思想模拟实现qsort函数
前文中我用整型数组演示了qsort函数的使用方式,那么在该函数中我便将字符数组假定为排序对象进行实现。
1,strcmp函数
若是要对字符进行排序,则需要引入strcmp函数
strcmp函数是C语言中的库函数,使用该函数需要包含头文件: <string.h>。strcmp函数是专门用来比较字符串大小的,比较方式是按照a,b,c,d...x,y,z 的前后位置来确定。
strcmp函数格式:
在www.cplusplus.com 官网中我们可以查阅到该函数的传参格式如下:
int strcmp ( const char * str1, const char * str2 );
返回类型如下:
即是 当a>b时返回大于零的数字,当a<b时返回小于零的数字,当a=b时返回零。这刚好契合compare函数所需要的返回参数。
2,函数构想
以下为该函数的设计思路:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//排序
void bubble_sort(void* base, size_t sz, size_t width, int compare(const void* e1, const void* e2))
{
size_t i = 0;
for (i = 0; i < sz - 1; i++)
{
size_t j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (compare() > 0)//通过比较方式得到两个元素的大小关系
{
swap();//交换
}
}
}
}
//比较
int compare(const void* e1, const void* e2)
{
//..
}
//交换
void swap()
{
//..
}
int main()
{
//将所需比较的元素放入数组
char* ch1 = "department";
char* ch2 = "complicated";
char* ch3 = "boom";
char* ch4= "astronomer";
char* arrp[4] = { ch1,ch2,ch3,ch4 };
int sz = sizeof(arrp) / sizeof(arrp[0]);
//将冒泡排序实现的函数命名为bubble_sort
bubble_sort(arrp,sz,sizeof(arrp[0]),compare);
return 0;
}
思路说明:
1,因为是需要使用冒泡排序的思想实现qsort函数,所以对 bubble_sort函数 的传参便需要数组元素个数 sz,以此来实现两两相邻元素依次比较。
2,因为该函数需要对任意数据类型的排序都能实现,换句话说就是我们不知道使用该函数的人会传递什么数据给 bubble_sort函数 ,所以函数无法使用特定的数据类型去接收,故只能使用void*。而一旦是传递地址给 void* 类型接收,由于void* 无类型大小,由此会导致在后续两个元素交换无法实现,故需要传递单个元素大小 width。
3,同样的,因为该函数需要对任意数据类型的排序都能实现,而有排序就一定有比较,故比较方式需要使用者自行传入,以 compare 函数接收。
3,函数实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//函数声明
void swap(char* e1, char* e2, int width);
void bubble_sort(void* base, size_t sz, size_t width, int compare(const void* e1, const void* e2));
//排序
void bubble_sort(void* base, size_t sz, size_t width, int compare(const void* e1, const void* e2))
{
size_t i = 0;
for (i = 0; i < sz - 1; i++)
{
size_t j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (compare((char*)base + (j * width), ((char*)base + ((j + 1) * width))) > 0)
{
//当满足条件是调用swap函数进行交换
swap(((char*)base + (j*width)), ((char*)base + ((j + 1)*width)),width);//交换
}
}
}
}
//比较
int compare(const void* e1, const void* e2)
{
return strcmp(*(char**)e1, *(char**)e2);
}
//交换
void swap (char* e1, char* e2,int width)
{
//一个字符一个字符进行交换
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}
void print_arrp(char** p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s\n", *(p + i));
}
}
int main()
{
//将所需比较的元素放入数组
char* ch1 = "department";
char* ch2 = "complicated";
char* ch3 = "boom";
char* ch4= "astronomer";
char* arrp[4] = { ch1,ch2,ch3,ch4 };
int sz = sizeof(arrp) / sizeof(arrp[0]);
//将冒泡排序实现的函数命名为bubble_sort
printf("排序前\n");
print_arrp(arrp, sz);
printf("\n");
bubble_sort(arrp,sz,sizeof(arrp[0]),compare);
printf("排序后\n");
print_arrp(arrp, sz);
return 0;
}
函数解析:
一,bubble_sort函数
通过一个嵌套循环实现冒泡排序的两两相邻元素依次比较,元素以compare函数进行比较大小,当满足条件时调用swap函数交换内存。
这里需要着重讲一下对compare函数与swap函数的传参:
因为bubble_sort函数接收的是数据类型为 void* 的数组首元素的地址,所以首先要对其进行强制类型转换,这里我们转换的是char*类型,主要的目的是想要使得base加减整数时所操作的内存为一字节。
我们已经有了base(数组首元素地址),也有width(单个元素所占内存空间大小),同时也设定了 j 来表示数组下标。我们将其强制类型转换为 char* 类型指针,这样就保证其每次加减整数都是操作1字节的空间,我们只需要 (char*)base + j(元素下标)* width(单个元素所占内存空间大小)== 首地址向前推进 j 个 元素所占内存大小空间,也就是指向&arrp[j]的地址!
当我开始调试后得到base == 0x0055FE04 == &arrp[0] 。
已知 sizeof(arrp[0]) == 4,即是当j=0时,base向前跳过0字节到达 0x0055FE04 == &arrp[0];当j=1时,base向前跳过4字节到达 0x0055FE08 == &arrp[1];当j=2时,base向前跳过8字节到达 0x0055FE0C == &arrp[2]...
易知,当我们跳过 j*width 个字节后,地址刚好就指向&arrp[j]的地址,这便是我所这样写的用意。
二, compare函数
已知我们传参过来的是&arrp[j],这是一个char**类型的指针,但是却使用的void* 类型去接收,故我们需要将其强转为char**类型,又因为strcmp函数需要的的参数是:(const char * str1, const char * str2) ,故对其解引用得:*(char**)e1 == *(&arrp[j]) == arrp[j] == *(arrp + j) == 指针变量ch。
而strcmp函数的返回值与我们所需的返回值契合,所以该函数直接返回strcmp函数的返回值。
三,swap函数
当函数满足判断条件后,便会调用该函数以达到交换的目的。
已知swap函数所接收的地址是&arrp[j]和&arrp[j+1]这两个地址,因为在传参时我已将这两个指针强转为char*来保证每次加减整数只操作一个字节,同样的在swap函数中我也有同样的需求,所以便直接以char*类型接收。
又由于我只知道交换字符的起始地址,于是便将width(单个元素所占字节的个数)传递过来,那么swap函数便是从这个地址开始往后交换,一共交换width个字节便结束,此时恰好是一个元素的大小。
如上图所示,所交换的是该地址所指向的地址,也就是所指向的字符串,以此来达到对const类型排序的目的。
本次知识分享到此结束,谢谢大家观看!