目录
1、自定义冒泡函数时遇到的问题
2、qsort函数的作用
(1)int整形数组排序(2)浮点型数组排序(3)字符数组排序
(4)结构体排序
1、对qsort定义的函数参数类型拆分理解
2、qsort的原理
写在前言之前: 本人是大一新生,写的东西难免有不足之处(比如不会写目录)。
如有错误,欢迎指出。
前言:大家学习时遇到不懂的地方,可以先问问度娘
如果你觉得查百度会拉低程序员的逼格的话,可以用下面这个网站
在"Search"里输入想要查找的函数,就可以看到它的定义和使用案例了。
1、自定义冒泡排序函数时遇到的问题:
在正式介绍qsort函数之前,我先说一下我自己写代码时遇到的问题
void bubble_sort(int arr[], int sz)
// int arr[]把类型写死了,只能排int类型的数
{
//数组有i个元素,总共要进行i-1趟排序
int i = 0;
for (i = 0; i < sz - 1;i++) {
//第一趟比较i-1次
//第二趟比较i-2次……
int j = 0;
for (j = 0; j < sz - 1 - i;j++) {
if (arr[j]>arr[j+1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main() {
int arr1[10] = { 2,1,6,3,4,5,8,7,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
bubble_sort(arr1, sz);
}
很明显:我写的bubble_sort函数只能排序int类型的数组
c语言中难道就没有一个可以排序大部分类型的库函数吗?
答案是有的,就是qsort函数
位置:qsort函数位于头文件<stdlib.h>里
2、qsort函数作用
如何实现升序或者降序呢?答案是通过改变自己写的compar函数实现
//升序
int compar_float(const void* p1, const void* p2) {
return (int)*(float*)p1 - (int)*(float*)p2;
}
//降序
int compar_float(const void* p1, const void* p2) {
return (int)*(float*)p2 - (int)*(float*)p1;
}
(1)int整形数组排序
打印数组的函数
int compar_int(const void* p1, const void* p2) {
return *(int*)p1 - *(int*)p2;
//强制类型转换
}
qsort函数和它指针指向的函数
#include<stdio.h>
#include<stdlib.h>
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
}
int main() {
int arr[10] = { 2,1,6,3,4,5,8,7,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
// 用数组的字节大小/数组中每个元素的大小计算元素个数
qsort(arr,10,sizeof(arr[0]), compar_int);
print(arr, sz);//这是我写的打印数组的函数
}
这样,arr数组就成功按照升序排序了
(2)浮点型数组排序
int compar_float(const void* p1, const void* p2) {
return (int)*(float*)p1 - (int)*(float*)p2;
}
int main() {
float arr[] = { 5.0f,3.0f,4.0f,3.5f };
int sz = strlen(arr);
qsort(arr, sz, sizeof(arr[0]), compar_float);
}
理论上可以,但不知道为什么运行不了
(3)字符数组排序
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
int compar_char(const void* p1,const void* p2) {
return strcmp((char*) p1,(char*)p2);
}
int main() {
char arr[] = "hello";
//排列字符串的顺序,本质上是排列它们的asc2码值
int sz = strlen(arr);
//strlen计算长度,找到'\0'就停止
//如果用sizeof计算arr的长度,就要-1
//因为sizeof计算是包括了'\0'
qsort(arr,sz,sizeof(arr[0]), compar_char);
printf("%s\n",arr);
}
大致介绍一下strcpm函数
作用:用于比较两个字符串是否相等
还是用例子说明
#include<stdio.h>
#include<string.h>
int main() {
char arr1[10] = "abbcd";
char arr2[10] = "abcd";
int ret = strcmp(arr1,arr2);
printf("%d\n",ret); //结果为-1
}
原理:
如果 str1>str2 返回正数 如果 str1<str2 返回负数
如果 str1==str2 &&str1=="\0"&&str2=="\0" 返回0
注:在vs中,这个函数只会返回1,-1,0
顺便附上一张asc2码值表
(4)结构体排序
struct student {
char name[10];
int age;
};
//比较的是结构体变量的年龄
int compar_age(const void* p1, const void* p2) {
return ((struct student*)p1)->age - ((struct student*)p2)->age;
}
int main() {
struct student s[3] = { {"xiaoming",10},{"zhangsan",16},{"kangkang",14} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), compar_age);
}
排序成功
美中不足的是,我在改变排序对象的类型时,还是要更改compar函数,不能实现“一劳永逸”的目标
二、qsort函数的原理解析
1、对qsort定义的函数参数类型拆分理解
首先,我们先来了解qsort函数的定义
void*是空指针,如果暂时不知道要用什么类型存放数据,可以先用void*,再强制类型转换。
优点:可以存放多种数据
缺点:不能解引用,不能+数字
还是用代码举例
int main() {
int a = 1;
void* pa = &a;
pa+1; //error
*pa = 2; //error
}
size_t 本质上是一种无符号整形
int (*compar)(const void*,const void*) 表示一个函数指针。
这个参数的作用是将compar函数的地址传给qsort函数。
这也就是为什么我们在使用qsort函数时还要自己写一个比较函数
2、qsort函数的原理
首先,我们先来认识下qsort函数定义的中文意思
qsort(数组首元素的指针,数组的元素个数,每个数组元素所占的字节大小,自己设置的比较函数)
在解释原理之前,我们需要先了解一种排序方法:冒泡排序
假设我们要升序下面的数组
int arr = {9,8,7,6,5,4,3,2,1};
第一趟我们有9个数字参与排序,一共比较了8次
以此类推:第二次 有8个数字进行了排序 一共比较了7次
……
总结:若有i个数字参与排序,总共要排序i-1趟,每一趟比较的次数都会递减,
次数从i-1开始
关于升序或者降序的解释:
将棕色方框内的第一个元素减去第二个元素,得到的结果>0,这时,compar函数的返回值就会是正数。qsort函数接受到这个返回值时,会判断它的正负性。如果它是正数,就会交换棕色方框内数字的位置。这就是升序的原理。
如果要是排序方式变为降序,那就要使返回值变为负数,具体实现方式就是将第一个元素减去第二个元素改为第二个元素减去第一个元素,也就是更换减数和被减数的位置。
不难发现:compar函数的一个重要作用,就是决定qsort函数是升序还是降序,具体通过返回值的正负来实现
qsort函数排序的原理,就是将输入的数组进行冒泡排序。
有的人可能会疑惑:qsort函数为什么要有“元素的字节大小”这一个参数呢?
在使用冒泡排序时,我们需要找到某个数组元素及其下一个数组元素的地址
但是,在设计qsort函数时,设计者不知道使用者会排序什么类型的元素。
再次回看定义,我们发现,设计者也想到了这一点:
base元素类型为void*,这意味着我们可以把base的类型设置为int* ,char*,long* 等等。
但是,问题就出现了
int main() { int arr[5] = { 1,2,3,4,5 }; void* parr = &arr; *(parr + 1) = 5; //这么写报错了 }
虽然void*就像垃圾桶一样,可以存放不同类型的数组,但却很难直接找到数组对应的元素。
借助强制类型转换,可以解决这个问题。我们先来看一看下面的例子
注:数组名一般情况下表示数组首元素的地址,但有两种情况下不是:
1、sizeof(数组名) //这表示数组所有元素的字节大小
2、&数组名 //这表示整个数组的地址
对上面图片的解释:
不难发现:上图中红色的数字正好对应这个元素在数组中的下标,而蓝色的数组正好对应这个元素的字节大小。
在模拟实现的过程中,我们能不能用一个变量表示红色的数字,用另一个变量表示蓝色的数字呢?
三、模拟实现qsort函数
我们发现,模拟qsort函数的需要实现的功能有:1、对数组进行冒泡排序 2接受compar函数的返回值,以实现升序和降序。难点有:模拟的函数需要兼容各个类型的元素。
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++;
}
}
int sort_int(const void* p1, const void* p2) {
return *(int*)p1 - *(int*)p2; //这是我们自己写的compar函数
}
void my_qsort(void* base, int sz, //这是模拟的qsort函数
int width,//一个元素所占的字节大小
int (*compar)(const void* p1, const void* p2))
{
//如果数组有i个数字,共排序i-1趟
int i = 0;
for (i = 0; i < sz - 1; i++) {
int j = 0;
for (j = 0; j < sz - 1 - i; j++) {
//这里在比较相邻数字的大小,以决定要不要交换
if (compar((char*)base + j * width, (char*)base + (j + 1) * width) > 0) {
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
//这里是整个程序的入口
int main() {
int arr[10] = {8,2,4,3,7,9,1,10,5,6};
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr,sz,sizeof(arr[0]),sort_int);
有了上面的铺垫,相信理解起这一段代码也就不难了