qsort 的使用(回调函数结构体指针的总和运用)
qsort的作用
qsort--用来排序的
库函数,直接可是用来排序数据
底层使用的是快速排序的方式
复习冒泡排序
排序分为几种方式
复习冒泡排序
冒泡排序的思想
冒泡排序
每次循环的次数减少
打印
图解
这个代码存在问题 因为只能排序整形 字符等等是不能排序是 也就是说
有局限性 所以有点问题
指针篇章-(冒泡排序详解)-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/136581549
这里是写过的冒泡排序的详解 想要详细代码的同学可以去看一下
qsort存在的意义
qsort是C语言中的一个标准库函数,用于对数组进行快速排序。
它的存在有以下几个意义:
-
提供高效的排序算法:qsort使用快速排序算法,这是一种高效的排序算法,平均时间复杂度为O(nlogn),能够在大多数情况下快速地对数组进行排序。
-
通用性:qsort是一个通用的排序函数,可以用于对各种类型的数组进行排序,只需要提供相应的比较函数即可。这使得它在处理不同类型的数据时非常方便。
-
灵活性:通过提供自定义的比较函数,可以根据不同的需求对数组进行排序。比如可以按照升序或降序排列,也可以按照自定义的规则进行排序。
-
标准化:qsort是C语言标准库中的函数之一,它的存在使得程序员可以直接使用标准库提供的排序功能,而无需自己实现排序算法。这样可以提高代码的可读性和可维护性。
qsort的语法格式
void qsort(void *base, size_t num, size_t width, int (*compar)(const void *, const void *));
void *base:指向要排序的数组的指针。数组的元素类型必须是可以通过指针进行比较的。
size_t num:要排序的元素数量。
size_t width:每个元素的大小(以字节为单位)。
int (*compar)(const void *, const void *):比较函数,用于指定如何比较两个元素。这个函数应该返回以下值之一:
如果第一个参数应该排在第二个参数前面,则返回一个小于零的值。
如果两个参数相等,则返回零。
如果第一个参数应该排在第二个参数后面,则返回一个大于零的值。
比较函数的类型是 int (*)(const void *, const void *),这意味着它是一个接受两个指向要比较的元素的指针,并返回整数的函数。
下面是一个使用 qsort 的简单例子,展示了如何对一个整数数组进行排序:
#include <stdio.h>
#include <stdlib.h>
// 比较函数
int compare(const void *a, const void *b) {
return (*(int *)a - *(int *)b); // 升序排序
}
int main() {
int arr[] = {3, 1, 4, 1, 5, 9};
int n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), compare);
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
在上述代码中,我们定义了一个比较函数 compare,它根据整数值进行升序排序。然后我们调用 qsort 函数,使用我们的比较函数对数组 arr 进行排序。最后,我们遍历排序后的数组并打印出来。
qsort函数的原型 (语法格式详解)
需要查找函数的,这两个网站很好用
C library - C++ Reference (cplusplus.com)
四个参数 base num size compar 所以这个是一个函数指针
四个参数
base(基础(基础地质))是一个指针 指向的是待排序的数组的第一个元素
num(元素的个数)base指向数组待排序的个数
size(大小)base指向元素待排序的大小
compar()计算函数->计算函数里面 还有一个交换函数
如果要进行排序优化那什么是修改的什么不是修改的
也就是 还是冒泡排序的话 比较方式会进行改变
所以由此得知qsort就是两个函数的比较函数
我们是qsor的使用者
qsort的使用(整形的使用)
e1 e2 分别是 第一个需要比较元素的地址和第二个需要比较元素的地址
之前讲到过 void*类型的指针是没有类型的指针 这种类型的指针是不能直接解引用 也不能进行加减整数的运算
所以是不对的
但是我们可以强制类型转化为整形
因为我们明确的知道是两个整形
完整代码
计算的逻辑
//这里首先进行了强制类型转换,将传入的void指针转换为int指针。
// 因为在C语言中,函数的参数是通过值传递的方式传递的,
// 这里传递的是指针的值(即指针的地址),而不是指针所指向的数据。
// 因此,需要对传入的指针进行解引用(通过类型转换为int*),以获取它们所指向的实际整数值。
The element pointed to by p1 goes before the element pointed to by p2
The element pointed to by p1 is equivalent to the element pointed to by p2
The element pointed to by p1 goes after the element pointed to by p2
p1所指向的元素在p2所指向的元素之前
p1所指向的元素等价于p2所指向的元素
p1所指向的元素在p2所指向的元素之后
qsort
包含的头文件
代码简化
这里采取指针-指针的运算 之前讲过指针-指针 在指针(1)里面
这里是在同一个地址空间
如果是倒着排序
就反过来
所以回调函数再次产生
整形函数的代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
//打印函数
//打印整形函数
void Printint(int arr[], size_t sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
//1 整形数组的使用 qsort函数的使用,整形的计算(回调函数的使用)
int compute1(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
//这里解释一下 首先这里是强制类型转化成指针类型
//
//这里首先进行了强制类型转换,将传入的void指针转换为int指针。
// 因为在C语言中,函数的参数是通过值传递的方式传递的,
// 这里传递的是指针的值(即指针的地址),而不是指针所指向的数据。
// 因此,需要对传入的指针进行解引用(通过类型转换为int*),以获取它们所指向的实际整数值。
//
// 对于计算方式是有隐含的计算方式的 图片已经进行说明,这里不进行多余的解释
//如果返回值小于0,则表示e1应该排在e2之前。
//如果返回值等于0,则表示e1和e2的顺序不变。
//如果返回值大于0,则表示e1应该排在e2之后。
}
void test1()
{
printf("qsort函数的使用,整形的计算(回调函数的使用)>\n");
int arrint[] = { 1,3,7,3,5,9,0,2,5,7,0,1,4,6 };
int sz = sizeof(arrint) / sizeof(arrint[0]);
qsort(arrint, sz, sizeof(arrint[0]), compute1);
Printint(arrint, sz);
printf("\n\n");
}
//主函数 负责函数的调用
int main()
{
test1();
}
qsort的使用 (字符)
字符的使用和整形的大致是一样的 这里不做图解了
直接代码解释
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
//打印字符函数
void Printchar(char *arr, size_t sz)
{
for (int i = 0; i < sz; i++)
{
printf("%c ", arr[i]);
}
}
2 字符数组的使用 qsort函数的使用,字符的计算(回调函数的使用)
char compute2(const void* e1, const void* e2)
{
return *(char*)e1 - *(char*)e2;//和整形同理
//这里解释一下 首先这里是强制类型转化成指针类型
//
//这里首先进行了强制类型转换,将传入的void指针转换为char指针。
// 因为在C语言中,函数的参数是通过值传递的方式传递的,
// 这里传递的是指针的值(即指针的地址),而不是指针所指向的数据。
// 因此,需要对传入的指针进行解引用(通过类型转换为char*),以获取它们所指向的实际整数值。
//
// 对于计算方式是有隐含的计算方式的 图片已经进行说明,这里不进行多余的解释
//如果返回值小于0,则表示e1应该排在e2之前。
//如果返回值等于0,则表示e1和e2的顺序不变。
//如果返回值大于0,则表示e1应该排在e2之后。
}
void test2()
{
printf("qsort函数的使用,字符的计算(回调函数的使用)>\n");
char arrchar[] = "sdhngkfuhsah";
size_t sz = strlen(arrchar);
qsort(arrchar, sz, sizeof(arrchar[0]), compute2);
Printchar(arrchar, sz);
printf("\n\n");
}
//主函数 负责函数的调用
int main()
{
test1();
test2();
}
qsort的使用(结构体)
结构体不明白的同学,可以看一下这一篇文章
结构体详解-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/136477956
20数组二十个元素
这里的问题是怎么样进行排序
是年龄还是名字 肯定是年龄
那么继续是qsort 和回调函数
但是进行计算的时候他不知道这个是结构体
所以需要强制类型转化
结构体里面 需要知道不管是名字 还是字母,本质就是字符串
两个字符串的比较需要strcmp
strcmp需要头文件 string.h
强制类型转换之后 再进行这个strcmp进行比较
这个强制类型转化是临时的
不理解强制类型转化的 可以去看看这个文章 C语言------强制类型转化-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/136583214
strcmp的使用规则
需要知道
首先我们创建结构体
这里需要注意 这个结构体 要么进行声明 要么放在使用函数的的上面 不然这个是会显示找不到变量
3 结构体的使用 qsort函数的使用,结构体的计算(回调函数的使用)
struct MyStruct
{
char name[100];
int age;
//这里的结构体需要放在使用函数的上面 算是进行函数声明 只有进行函数的声明之后 才可以进行打印 才可以进行计算
};
其次创建调用的函数
void test3()
{
struct MyStruct arrstu[5] = { {"张三",12},{"李四",1} ,{"王五",2} ,{"二狗",90}, {"大炮",5} };//结构体初始化
int sz = sizeof(arrstu) / sizeof(arrstu[0]);
qsort(arrstu, sz, sizeof(arrstu[0]), compute_nmae);
}
然后是计算函数 当然需要知道
这里的计算方式有两种 一种是按照名字进行计算 一种是按照年龄进行计算
//结构体函数的计算 年龄的计算排序
int compute_age(const void* e1, const void* e2)
{
return ((struct MyStruct*)e1)->age - ((struct MyStruct*)e2)->age;
//这里是计算年龄的结构体进行打印的
//年龄的计算方式还是采取-+的方式进行对比 不采取库函数的方式进行对比
}
//结构体函数的计算 名字的计算排序
int compute_nmae(const void* e1, const void* e2)
{
return strcmp(((struct MyStruct*)e1)->name , ((struct MyStruct*)e2)->name);
//这里是计算名字的结构体进行打印的 也就是名字字符的字母转换成ASCII码值进行一对一对照 从而进行排序
//这里的计算方式是采取strcmp的一对一对照的方式进行计算的
}
写出打印函数
//打印结构体函数
void Printstu(struct MyStruct* arr)
{
printf("%s %d\n", arr->name, arr->age);
//这里是打印结构体的函数 打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
}
调用函数的完成
void test3()
{
struct MyStruct arrstu[5] = { {"张三",12},{"李四",1} ,{"王五",2} ,{"二狗",90}, {"大炮",5} };//结构体初始化
int sz = sizeof(arrstu) / sizeof(arrstu[0]);
qsort(arrstu, sz, sizeof(arrstu[0]), compute_nmae);
printf("结构体按照名字顺序进行排序>\n");
for (int i = 0; i < sz; i++)
{
Printstu(&arrstu[i]);
//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
//这里是【i】是外部数组的循环
}
printf("结构体按照年龄顺序进行排序>\n");
qsort(arrstu, sz, sizeof(arrstu[0]), compute_age);
for (int i = 0; i < sz; i++)
{
Printstu(&arrstu[i]);//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
}
printf("\n\n");
}
图解
按照年龄来排序
两个整形排序进行做差就好 年龄的排序和字符的排序逻辑是一样的 只是写的东西不一样
代码总结
3 结构体的使用 qsort函数的使用,结构体的计算(回调函数的使用)
struct MyStruct
{
char name[100];
int age;
//这里的结构体需要放在使用函数的上面 算是进行函数声明 只有进行函数的声明之后 才可以进行打印 才可以进行计算
};
//打印结构体函数
void Printstu(struct MyStruct* arr)
{
printf("%s %d\n", arr->name, arr->age);
//这里是打印结构体的函数 打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
}
//结构体函数的计算 名字的计算排序
int compute_nmae(const void* e1, const void* e2)
{
return strcmp(((struct MyStruct*)e1)->name , ((struct MyStruct*)e2)->name);
//这里是计算名字的结构体进行打印的 也就是名字字符的字母转换成ASCII码值进行一对一对照 从而进行排序
//这里的计算方式是采取strcmp的一对一对照的方式进行计算的
}
//结构体函数的计算 年龄的计算排序
int compute_age(const void* e1, const void* e2)
{
return ((struct MyStruct*)e1)->age - ((struct MyStruct*)e2)->age;
//这里是计算年龄的结构体进行打印的
//年龄的计算方式还是采取-+的方式进行对比 不采取库函数的方式进行对比
}
void test3()
{
struct MyStruct arrstu[5] = { {"张三",12},{"李四",1} ,{"王五",2} ,{"二狗",90}, {"大炮",5} };//结构体初始化
int sz = sizeof(arrstu) / sizeof(arrstu[0]);
qsort(arrstu, sz, sizeof(arrstu[0]), compute_nmae);
printf("结构体按照名字顺序进行排序>\n");
for (int i = 0; i < sz; i++)
{
Printstu(&arrstu[i]);
//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
//这里是【i】是外部数组的循环
}
printf("结构体按照年龄顺序进行排序>\n");
qsort(arrstu, sz, sizeof(arrstu[0]), compute_age);
for (int i = 0; i < sz; i++)
{
Printstu(&arrstu[i]);//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
}
printf("\n\n");
}
//主函数 负责函数的调用
int main()
{
test1();
test2();
test3();
}
模拟qsort函数(冒泡排序)
模拟的逻辑和图片详解
前面讲解了冒泡排序 这里不过多赘述
此时就不能写成整形数组了
需要用void进行参数的接收 因为既然是模仿
要是有人用你的函数,那么不一定只是整形类型的
这里是冒泡排序的逻辑代码
函数指针指向两个参数
因为不知道指向什么元素 所以只能const
最后通过返回值告诉我
怎么算出arr的地址
base不能+1 因为是void类型
如果不是整形 那就不具备通用性 int类型
因为void是不能进行计算 并且让代码具备通用性
所以只能使用void
所以进行强制类型转换 转化成char*类型
那就是短整型首元素地址+循环的次数*宽度也就是字节的长度
也就是传参的时候还需要吧宽度再次传过去
这里图解一下交换函数的逻辑
还是那句话 你不确定用你函数的人是用int类型 还是char类型
但是char类型是 最小的 可以最大限度的满足所有的需求
如果没有那么大的空间 那就用小空间进行交换
也就是说 这里是int类型 int类型是实际是4个char
那么也就是这4个char进行交换
交换完成后然后进行数值的返回
这里是一对字节进行交换
大块空间不方便进行交换 就小块空间进行交换
数据梳理1
数据梳理
运行的步骤
宽度就是字节长度
不满足返回2
此时进行交换
这里进行一个字节一个字节进行交换 交换四次
函数指针这里是非常重要的 不同类型的比较方式不一样 所以根据不同的类型 进行比较
每次交换一个字节 如果是整形此时交换四次 也就是交换四次 然后每次左边和右边进行缩小
从而完成数值的交换
qsort函数模拟的具体代码分析
首先就是函数的构建
void test4()//模拟qsort函数的主函数
{
int arrqsort[] = { 1,3,7,3,5,9,0,2,5,7,0,1,4,6 };
int sz = sizeof(arrqsort) / sizeof(arrqsort[0]);
my_qsort(arrqsort, sz, sizeof(arrqsort[0]), cmp);//这里的意思是让mt_qsort函数充当qsort函数 进行模拟
//arrqsort函数充当的是数组首元素地址
//sz代表的是整个数组的长度
//sizeof(arrqsort[0])这里充当的是数组里面一个元素的大小 占据的是多大的宽度
//my_qsort进行这个函数的调用 当然 这也是一个回调函数 间接调用cnp
//cmp这里的函数的计算函数 也就是两个字符进行计算的函数
}
模拟函数的构建
void my_qsort(void* base, size_t sz, size_t width, int(*cmp)(const void* e1, const void* e2))//模拟qsort的库函数
{
//这里需要知道 这里需要用void类型 不能用int类型的 因为我们是进行qsort的模拟 并且我们并不知道传参过来的时候是int类型还是char类型的 要模拟的全面
//void* base,
//size_t sz,这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组长度是size_t类型的
//size_t width, 这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组元素单个的宽度是size_t类型的
//int(*cmp)(const void* e1, const void* e2)这里是一个函数指针变量 这里是int类型的原因是 需要接受返回值进行计算 大于1进行交换 等于0 不交换 小于0
//这里补充一下 void类型虽然是可以接受任何的参数,但是void类型是不能参与计算的 所以在计算的时候需要进行强制类型转化,这也就是为什么在之前进行计算的时候 是需要进行强制类型转化的
//这里采取的是冒泡排序的方式进行举例 所以 依旧是两个循环遍历数组 sz-1 的目的是 防止最后一次交换的时候发生越界行为
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - i - 1; j++)//这里sz-i-1的目的是 减少循环的次数 如果不理解 可以理解为sz-1也是没问题的 只是那样遍历的次数多了
{
//这里是进行一个数值的对比 也就是qsort函数里面的运算逻辑
//首先我们知道,这里的接受参数是void类型的 void的类型是无法参与计算的 所以在这里需要进行强制类型的转化 来确保传参过去后可以进行计算
// 其次关于这个的计算问题j * width,(j + 1) * width
// 首先我们需要知道 这里我们为了让不仅int类型的可以计算 字符也是可以计算的 我们采取的是void类型的 可以接受任何的参数
// 其次才是在内部需要进行计算 的时候再强制类型转化为char类型 这里的char类型一次是有一个字节 也就是sizeof的大小是一个字节
// 但是这里我们具体问题具体分析一下就是 如果是int类型的情况下 那就是四个字节 所以数组元素每个元素占据的是四个字节
// 那么 这里我们采取的是char一次跳过一个字节 如果是int类型的情况下 如何才能进行两个地址的对比
// 那么 就是 首元素的地址+当前字符所在位置*字符的字节大小 和 首元素的地址+(当前字符所在位置+1)*字符的字节大小
//进行对比 所以此时也就是 前后两个元素进行对比 不会出现说 对比半天还是这个元素进行对比 或者 这个元素第一个字节和第二个字节对比的尴尬情况
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
//这里是交换函数 也就是如果满足
//如果返回值小于0,则表示e1应该排在e2之前。
//如果返回值等于0,则表示e1和e2的顺序不变。
//如果返回值大于0,则表示e1应该排在e2之后。
//则进行交换
//不满足不进行交换 因为这里是指针 是直接对地址进行操作的 所以这里是只要操作 也就是会直接地址就改变排序的顺序
// 满足交换的条件之后 传递参数到swap交换函数里面
// 把满足条件的参数直接拿过来传过去就可以
// 这里再次说明一下 void强制转化为char的原因
// 因为 void虽然是很好的 可以接受各种类型 但是是不参与计算的
// 所以需要强制转化为其他类型
// 为了让代码的通用性更强 转化为char类型是最好的选择
// 但是这里是指针
// 所以理所应当也就是char*类型了
}
}
}
}
计算函数的构建
//qsort函数的模拟
int cmp(const void* e1, const void* e2)//模拟qsort函数的计算函数
{
return *(int*)e1 - *(int*)e2;//这里没有什么好说的 这里之前讲很多次了 一笔带过
}
交换函数的构建
void Swap(char* e1, char* e2, size_t width)//模拟qsort函数的交换函数
{
for (int i = 0; i < width; i++)
{
char tmp = *e1;//这里注意需要是char类型 千万要写成int类型 因为你传参过来的是char* 所以 你就算创建一个空的 也得类型一样
*e1 = *e2;//这里就是进行交换
*e2 = tmp;//进行交换
e1++; //这里是每次进行交换完毕之后 左边指针指向的1字节进行++,不然会有很多重复的运算 或者错误的运算
e2++;//这里是每次进行交换完毕之后 右边指针指向的1字节进行++
}
}
模拟函数的总结代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
//打印函数
//打印整形函数
void Printint(int arr[], size_t sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
//qsort函数的模拟
int cmp(const void* e1, const void* e2)//模拟qsort函数的计算函数
{
return *(int*)e1 - *(int*)e2;//这里没有什么好说的 这里之前讲很多次了 一笔带过
}
void Swap(char* e1, char* e2, size_t width)//模拟qsort函数的交换函数
{
for (int i = 0; i < width; i++)
{
char tmp = *e1;//这里注意需要是char类型 千万要写成int类型 因为你传参过来的是char* 所以 你就算创建一个空的 也得类型一样
*e1 = *e2;//这里就是进行交换
*e2 = tmp;//进行交换
e1++; //这里是每次进行交换完毕之后 左边指针指向的1字节进行++,不然会有很多重复的运算 或者错误的运算
e2++;//这里是每次进行交换完毕之后 右边指针指向的1字节进行++
}
}
void my_qsort(void* base, size_t sz, size_t width, int(*cmp)(const void* e1, const void* e2))//模拟qsort的库函数
{
//这里需要知道 这里需要用void类型 不能用int类型的 因为我们是进行qsort的模拟 并且我们并不知道传参过来的时候是int类型还是char类型的 要模拟的全面
//void* base,
//size_t sz,这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组长度是size_t类型的
//size_t width, 这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组元素单个的宽度是size_t类型的
//int(*cmp)(const void* e1, const void* e2)这里是一个函数指针变量 这里是int类型的原因是 需要接受返回值进行计算 大于1进行交换 等于0 不交换 小于0
//这里补充一下 void类型虽然是可以接受任何的参数,但是void类型是不能参与计算的 所以在计算的时候需要进行强制类型转化,这也就是为什么在之前进行计算的时候 是需要进行强制类型转化的
//这里采取的是冒泡排序的方式进行举例 所以 依旧是两个循环遍历数组 sz-1 的目的是 防止最后一次交换的时候发生越界行为
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - i - 1; j++)//这里sz-i-1的目的是 减少循环的次数 如果不理解 可以理解为sz-1也是没问题的 只是那样遍历的次数多了
{
//这里是进行一个数值的对比 也就是qsort函数里面的运算逻辑
//首先我们知道,这里的接受参数是void类型的 void的类型是无法参与计算的 所以在这里需要进行强制类型的转化 来确保传参过去后可以进行计算
// 其次关于这个的计算问题j * width,(j + 1) * width
// 首先我们需要知道 这里我们为了让不仅int类型的可以计算 字符也是可以计算的 我们采取的是void类型的 可以接受任何的参数
// 其次才是在内部需要进行计算 的时候再强制类型转化为char类型 这里的char类型一次是有一个字节 也就是sizeof的大小是一个字节
// 但是这里我们具体问题具体分析一下就是 如果是int类型的情况下 那就是四个字节 所以数组元素每个元素占据的是四个字节
// 那么 这里我们采取的是char一次跳过一个字节 如果是int类型的情况下 如何才能进行两个地址的对比
// 那么 就是 首元素的地址+当前字符所在位置*字符的字节大小 和 首元素的地址+(当前字符所在位置+1)*字符的字节大小
//进行对比 所以此时也就是 前后两个元素进行对比 不会出现说 对比半天还是这个元素进行对比 或者 这个元素第一个字节和第二个字节对比的尴尬情况
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
//这里是交换函数 也就是如果满足
//如果返回值小于0,则表示e1应该排在e2之前。
//如果返回值等于0,则表示e1和e2的顺序不变。
//如果返回值大于0,则表示e1应该排在e2之后。
//则进行交换
//不满足不进行交换 因为这里是指针 是直接对地址进行操作的 所以这里是只要操作 也就是会直接地址就改变排序的顺序
// 满足交换的条件之后 传递参数到swap交换函数里面
// 把满足条件的参数直接拿过来传过去就可以
// 这里再次说明一下 void强制转化为char的原因
// 因为 void虽然是很好的 可以接受各种类型 但是是不参与计算的
// 所以需要强制转化为其他类型
// 为了让代码的通用性更强 转化为char类型是最好的选择
// 但是这里是指针
// 所以理所应当也就是char*类型了
}
}
}
}
void test4()//模拟qsort函数的主函数
{
int arrqsort[] = { 1,3,7,3,5,9,0,2,5,7,0,1,4,6 };
int sz = sizeof(arrqsort) / sizeof(arrqsort[0]);
my_qsort(arrqsort, sz, sizeof(arrqsort[0]), cmp);//这里的意思是让mt_qsort函数充当qsort函数 进行模拟
//arrqsort函数充当的是数组首元素地址
//sz代表的是整个数组的长度
//sizeof(arrqsort[0])这里充当的是数组里面一个元素的大小 占据的是多大的宽度
//my_qsort进行这个函数的调用 当然 这也是一个回调函数 间接调用cnp
//cmp这里的函数的计算函数 也就是两个字符进行计算的函数
Printint(arrqsort,sz);
}
//主函数 负责函数的调用
int main()
{
test1();
test2();
test3();
test4();
}
总的代码
这里强调一下
结构体方面 需要把struct放到调用函数的上面,防止找不到变量
从而产生错误
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
//打印函数
//打印整形函数
void Printint(int arr[], size_t sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
//打印字符函数
void Printchar(char *arr, size_t sz)
{
for (int i = 0; i < sz; i++)
{
printf("%c ", arr[i]);
}
}
3 结构体的使用 qsort函数的使用,结构体的计算(回调函数的使用)
struct MyStruct
{
char name[100];
int age;
//这里的结构体需要放在使用函数的上面 算是进行函数声明 只有进行函数的声明之后 才可以进行打印 才可以进行计算
};
//打印结构体函数
void Printstu(struct MyStruct* arr)
{
printf("%s %d\n", arr->name, arr->age);
//这里是打印结构体的函数 打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
}
//结构体函数的计算 名字的计算排序
int compute_nmae(const void* e1, const void* e2)
{
return strcmp(((struct MyStruct*)e1)->name , ((struct MyStruct*)e2)->name);
//这里是计算名字的结构体进行打印的 也就是名字字符的字母转换成ASCII码值进行一对一对照 从而进行排序
//这里的计算方式是采取strcmp的一对一对照的方式进行计算的
}
//结构体函数的计算 年龄的计算排序
int compute_age(const void* e1, const void* e2)
{
return ((struct MyStruct*)e1)->age - ((struct MyStruct*)e2)->age;
//这里是计算年龄的结构体进行打印的
//年龄的计算方式还是采取-+的方式进行对比 不采取库函数的方式进行对比
}
void test3()
{
struct MyStruct arrstu[5] = { {"张三",12},{"李四",1} ,{"王五",2} ,{"二狗",90}, {"大炮",5} };//结构体初始化
int sz = sizeof(arrstu) / sizeof(arrstu[0]);
qsort(arrstu, sz, sizeof(arrstu[0]), compute_nmae);
printf("结构体按照名字顺序进行排序>\n");
for (int i = 0; i < sz; i++)
{
Printstu(&arrstu[i]);
//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
//这里是【i】是外部数组的循环
}
printf("结构体按照年龄顺序进行排序>\n");
qsort(arrstu, sz, sizeof(arrstu[0]), compute_age);
for (int i = 0; i < sz; i++)
{
Printstu(&arrstu[i]);//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
}
printf("\n\n");
}
2 字符数组的使用 qsort函数的使用,字符的计算(回调函数的使用)
char compute2(const void* e1, const void* e2)
{
return *(char*)e1 - *(char*)e2;//和整形同理
//这里解释一下 首先这里是强制类型转化成指针类型
//
//这里首先进行了强制类型转换,将传入的void指针转换为char指针。
// 因为在C语言中,函数的参数是通过值传递的方式传递的,
// 这里传递的是指针的值(即指针的地址),而不是指针所指向的数据。
// 因此,需要对传入的指针进行解引用(通过类型转换为char*),以获取它们所指向的实际整数值。
//
// 对于计算方式是有隐含的计算方式的 图片已经进行说明,这里不进行多余的解释
//如果返回值小于0,则表示e1应该排在e2之前。
//如果返回值等于0,则表示e1和e2的顺序不变。
//如果返回值大于0,则表示e1应该排在e2之后。
}
void test2()
{
printf("qsort函数的使用,字符的计算(回调函数的使用)>\n");
char arrchar[] = "sdhngkfuhsah";
size_t sz = strlen(arrchar);
qsort(arrchar, sz, sizeof(arrchar[0]), compute2);
Printchar(arrchar, sz);
printf("\n\n");
}
//1 整形数组的使用 qsort函数的使用,整形的计算(回调函数的使用)
int compute1(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
//这里解释一下 首先这里是强制类型转化成指针类型
//
//这里首先进行了强制类型转换,将传入的void指针转换为int指针。
// 因为在C语言中,函数的参数是通过值传递的方式传递的,
// 这里传递的是指针的值(即指针的地址),而不是指针所指向的数据。
// 因此,需要对传入的指针进行解引用(通过类型转换为int*),以获取它们所指向的实际整数值。
//
// 对于计算方式是有隐含的计算方式的 图片已经进行说明,这里不进行多余的解释
//如果返回值小于0,则表示e1应该排在e2之前。
//如果返回值等于0,则表示e1和e2的顺序不变。
//如果返回值大于0,则表示e1应该排在e2之后。
}
void test1()
{
printf("qsort函数的使用,整形的计算(回调函数的使用)>\n");
int arrint[] = { 1,3,7,3,5,9,0,2,5,7,0,1,4,6 };
int sz = sizeof(arrint) / sizeof(arrint[0]);
qsort(arrint, sz, sizeof(arrint[0]), compute1);
Printint(arrint, sz);
printf("\n\n");
}
//qsort函数的模拟
int cmp(const void* e1, const void* e2)//模拟qsort函数的计算函数
{
return *(int*)e1 - *(int*)e2;//这里没有什么好说的 这里之前讲很多次了 一笔带过
}
void Swap(char* e1, char* e2, size_t width)//模拟qsort函数的交换函数
{
for (int i = 0; i < width; i++)
{
char tmp = *e1;//这里注意需要是char类型 千万要写成int类型 因为你传参过来的是char* 所以 你就算创建一个空的 也得类型一样
*e1 = *e2;//这里就是进行交换
*e2 = tmp;//进行交换
e1++; //这里是每次进行交换完毕之后 左边指针指向的1字节进行++,不然会有很多重复的运算 或者错误的运算
e2++;//这里是每次进行交换完毕之后 右边指针指向的1字节进行++
}
}
void my_qsort(void* base, size_t sz, size_t width, int(*cmp)(const void* e1, const void* e2))//模拟qsort的库函数
{
//这里需要知道 这里需要用void类型 不能用int类型的 因为我们是进行qsort的模拟 并且我们并不知道传参过来的时候是int类型还是char类型的 要模拟的全面
//void* base,
//size_t sz,这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组长度是size_t类型的
//size_t width, 这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组元素单个的宽度是size_t类型的
//int(*cmp)(const void* e1, const void* e2)这里是一个函数指针变量 这里是int类型的原因是 需要接受返回值进行计算 大于1进行交换 等于0 不交换 小于0
//这里补充一下 void类型虽然是可以接受任何的参数,但是void类型是不能参与计算的 所以在计算的时候需要进行强制类型转化,这也就是为什么在之前进行计算的时候 是需要进行强制类型转化的
//这里采取的是冒泡排序的方式进行举例 所以 依旧是两个循环遍历数组 sz-1 的目的是 防止最后一次交换的时候发生越界行为
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - i - 1; j++)//这里sz-i-1的目的是 减少循环的次数 如果不理解 可以理解为sz-1也是没问题的 只是那样遍历的次数多了
{
//这里是进行一个数值的对比 也就是qsort函数里面的运算逻辑
//首先我们知道,这里的接受参数是void类型的 void的类型是无法参与计算的 所以在这里需要进行强制类型的转化 来确保传参过去后可以进行计算
// 其次关于这个的计算问题j * width,(j + 1) * width
// 首先我们需要知道 这里我们为了让不仅int类型的可以计算 字符也是可以计算的 我们采取的是void类型的 可以接受任何的参数
// 其次才是在内部需要进行计算 的时候再强制类型转化为char类型 这里的char类型一次是有一个字节 也就是sizeof的大小是一个字节
// 但是这里我们具体问题具体分析一下就是 如果是int类型的情况下 那就是四个字节 所以数组元素每个元素占据的是四个字节
// 那么 这里我们采取的是char一次跳过一个字节 如果是int类型的情况下 如何才能进行两个地址的对比
// 那么 就是 首元素的地址+当前字符所在位置*字符的字节大小 和 首元素的地址+(当前字符所在位置+1)*字符的字节大小
//进行对比 所以此时也就是 前后两个元素进行对比 不会出现说 对比半天还是这个元素进行对比 或者 这个元素第一个字节和第二个字节对比的尴尬情况
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
//这里是交换函数 也就是如果满足
//如果返回值小于0,则表示e1应该排在e2之前。
//如果返回值等于0,则表示e1和e2的顺序不变。
//如果返回值大于0,则表示e1应该排在e2之后。
//则进行交换
//不满足不进行交换 因为这里是指针 是直接对地址进行操作的 所以这里是只要操作 也就是会直接地址就改变排序的顺序
// 满足交换的条件之后 传递参数到swap交换函数里面
// 把满足条件的参数直接拿过来传过去就可以
// 这里再次说明一下 void强制转化为char的原因
// 因为 void虽然是很好的 可以接受各种类型 但是是不参与计算的
// 所以需要强制转化为其他类型
// 为了让代码的通用性更强 转化为char类型是最好的选择
// 但是这里是指针
// 所以理所应当也就是char*类型了
}
}
}
}
void test4()//模拟qsort函数的主函数
{
int arrqsort[] = { 1,3,7,3,5,9,0,2,5,7,0,1,4,6 };
int sz = sizeof(arrqsort) / sizeof(arrqsort[0]);
my_qsort(arrqsort, sz, sizeof(arrqsort[0]), cmp);//这里的意思是让mt_qsort函数充当qsort函数 进行模拟
//arrqsort函数充当的是数组首元素地址
//sz代表的是整个数组的长度
//sizeof(arrqsort[0])这里充当的是数组里面一个元素的大小 占据的是多大的宽度
//my_qsort进行这个函数的调用 当然 这也是一个回调函数 间接调用cnp
//cmp这里的函数的计算函数 也就是两个字符进行计算的函数
Printint(arrqsort,sz);
}
//主函数 负责函数的调用
int main()
{
test1();
test2();
test3();
test4();
}