1. qsort函数的作用
C语言中可以将任何类型的数组进行排序
2. 分析C语言中qsort函数的参数
我们在使用qsort函数的时候,都是直接调用函数,但该函数的内部到底是怎么运转的,我们并不知道,所以我们从该函数的参数下手
qsort(void* base, int nitems, int siz, int (*compar)(const void*, const void*))
2.1 void* base
第一个传入的参数是需要排列的数组的首元素地址,所以开发者为了可以适应各种数组类型,这里给的是void*的类型,应为void*类型的指针变量可以存放任何类型的指针地址
这个在我们后续的模拟实现中是可以接着利用开发者的这个方法,因为这大大提高了qsort函数的适用范围
2.2 int nitems
第二传入的参数是数组的元素个数,这个也是我们需要接着利用的方法,应为这是确定元素需要比较次数的关键
2.3 int siz
第三个参数是数组中一个元素的大小,这是后面确定指指针变量类型的必要条件,刚刚我提到了void*类型的指针变量可以存放任何类型的地址,但是void*类型的指针变量是不能直接解引用的,而是要先确定它的指针类型,应为这会影响到指针在做加法或者减法的时候,跨度的大小,所以这个时候数组中的单个元素大小,就成为了qued指针类型的必要条件
2.4 int (*compar)(const void*, conts void*)
第四个参数compar是一个函数指针变量,用来存放使用者自己,去定义的,用于比较的,函数的,地址
const这里就是防止通过*p(p这里代表指针变量)来改变他的值
3. 分析模拟实现qsort函数需要哪些部分组成
以下所有情况为了方便演示和讲解,均已int类型来实现
3.1 main函数部分
int main()
{
int arr[] = { 7,4,1,0,8,5,2,3,6,9 };
int nitems = sizeof(arr) / sizeof(arr[0]);
qsort_s(arr, nitems, sizeof(arr[0]), compar);
//qsort_s为模拟qsort函数功能的函数
//nitems数组元素个数
//sizeof(arr[0])单个元素大小
//compar是一个比较函数
return 0;
}
3.2 compar函数部分
int compar(const void* p1, const void* p2)
{
return *((int*)p1) - *((int*)p2);
//这里是使用者的部分
//强制转换类型转换成对应的数组元素类型
}
将 *((int*)p1) - *((int*)p2)的值返还回去,用来后面的判断
不知道p1和p2哪来的?别急往后看
3.3 qsort_s函数的部分
void qsort_s(void* arr, int nitems, int siz, int (*compar)(const void*, const void*))
{
for (int i = 0; i < nitems; i++)//移动好所有元素的次数
{
for (int j = 0; j < nitems - 1 - i; j++)//每个元素必要比较的次数
{
//用if来判断传入compar函数的值,是不是符合>0这个条件
if (compar((char*)arr + j * siz, (char*)arr + (j + 1) * siz) > 0)
{
vary((char*)arr + j * siz, (char*)arr + (j + 1) * siz, siz);
//这里是一个交换值的函数
}
}
}
}
刚刚我们创建了compar函数,我们知道这个函数使是用来判断俩个元素的大小关系的,所以我们首先把数组的前两个元素传入,判断返回值是不是>0
之前compar函数参数里面的p1和p2就是用来接受现在的元素地址的
我们可以注意到在if里面我们传入的地址是不是有点看不懂,没关系我们慢慢来解读
一开始我们就说了void*类型的指针变量是不可以直接解引用的,他需要有确定的类型才可以被解引用
指针类型 | 指针变量 | ++/-- | 访问字节 |
---|---|---|---|
int* | p | ++ | 4 |
short* | p | ++ | 2 |
char* | p | ++ | 1 |
flort* | p | ++ | 4 |
double* | p | ++ | 8 |
long* | p | ++ | 8(64位)/4(32位) |
那为什么我这里使用char*类型呢,因为char*的访问大小是1个字节,而我们c语言中的整形、浮点型、双精度浮点型类型的访问字节数都是1的倍数,也就是说这个时候我们在有一个该数组元素的大小的情况下,就可以算出在强制转换成char*类型后,需要加siz就可以定位到下个数组元素的地址
例:
我这边的数组类型是int类型,那么如果想在只有首元素地址的情况下它访问到下一个元素,那就需要地址加1对不对,加1的话int类型的指针访问四个字节,然后定位到下一个元素。而char类型的指针就需要加4,这个4也就是int类型里面那个单个元素内存大小(siz),才可以访问到int类型指针加1才能访问到的位置
因为第一次比较就是将接受的首元素地址和它下一位元素的地址传入compar函数,但是应为第一次比较除了下表为1的元素需要进行加siz,来定位到下表为1的元素,所以这里利用了变量j来进行参与运算
3.4 vary函数部分
//因为刚刚传过来的就是char*类型的,所以这边直接用char*来接收
//因为char*类型的,所以需要把元素大小也传过来,确定交换次数
void vary(char* p1, char* p2, int siz)
{
for (int i = 0; i < siz; i++)
{
int tem = *p1;
*p1 = *p2;
*p2 = tem;
p1++;
p2++;
}
}
这边就是一个字节一个字节的进行交换,char*类型访问内存是一个字节,int*类型访问内存是四个字节
4. 代码整合
#include<stdio.h>
int compar(const void* p1, const void* p2)
{
return *((int*)p1) - *((int*)p2);
//这里是使用者的部分
//强制转换类型转换成对应的数组元素类型
}
//因为刚刚传过来的就是char*类型的,所以这边直接用char*来接收
//因为char*类型的,所以需要把元素大小也传过来,确定交换次数
void vary(char* p1, char* p2, int siz)
{
for (int i = 0; i < siz; i++)
{
int tem = *p1;
*p1 = *p2;
*p2 = tem;
p1++;
p2++;
}
}
void qsort_s(void* arr, int nitems, int siz, int (*compar)(const void*, const void*))
{
for (int i = 0; i < nitems; i++)//移动好所有元素的次数
{
for (int j = 0; j < nitems - 1 - i; j++)//每个元素必要比较的次数
{
//用if来判断传入compar函数的值,是不是符合>0这个条件
if (compar((char*)arr + j * siz, (char*)arr + (j + 1) * siz) > 0)
{
vary((char*)arr + j * siz, (char*)arr + (j + 1) * siz, siz);
//这里是一个交换值的函数
}
}
}
}
int main()
{
int arr[] = { 7,4,1,0,8,5,2,3,6,9 };
int nitems = sizeof(arr) / sizeof(arr[0]);
qsort_s(arr, nitems, sizeof(arr[0]), compar);
//qsort_s为模拟qsort函数功能的函数
//nitems数组元素个数
//sizeof(arr[0])单个元素大小
//compar是一个比较函数
return 0;
}