指针篇章-(4)+qsort函数的模拟

学习目录 

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

回调函数 

回调函数是一种机制

计算器的代码举例

有问题:过于冗余

对代码进行简化 就是吧代码抽象成函数

所以就变成通过参数的不同进行指针的调用

calc函数

对比和详解

通过函数指针调用函数 也就是通过clac调用函数

这里插入回调函数的篇章

忘记的同学可以再去看一眼

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 补充 

结构体详解-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/136477956

之前在指针(2)篇章系统的说过结构体,这里补充复习一下,结构体在指针里的使用

在结构体中使用指针可以通过以下两种方式找到结构体的成员:
1. 使用箭头运算符(->):当结构体是通过指针进行引用时,可以使用箭头运算符来访问结构体的成员。例如,如果有一个指向结构体的指针ptr,可以使用ptr->member来访问结构体的成员。
2. 使用间接引用运算符(*):如果有一个指向结构体的指针ptr,可以使用*ptr来获取指针所指向的结构体对象,然后再使用点运算符(.)来访问结构体的成员。例如,(*ptr).member。

*(指针).+结构体

也可以是

指针名称->结构体名称

需要用指针进行接收,所以传参的时候需要取地址,因为指针指向的是地址 

———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— 

qsort 的使用(回调函数结构体指针的总和运用)

qsort的作用 

qsort--用来排序的

库函数,直接可是用来排序数据

底层使用的是快速排序的方式 

—————————————————————————————————————————————————————————————————————————————————————— 

复习冒泡排序

排序分为几种方式

复习冒泡排序

冒泡排序的思想

冒泡排序

每次循环的次数减少

打印

图解

这个代码存在问题 因为只能排序整形 字符等等是不能排序是 也就是说

有局限性 所以有点问题

指针篇章-(冒泡排序详解)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/136581549

这里是写过的冒泡排序的详解 想要详细代码的同学可以去看一下

—————————————————————————————————————————————————————————————————————————————————————— 

qsort存在的意义

qsort是C语言中的一个标准库函数,用于对数组进行快速排序。

它的存在有以下几个意义:

1. 提供高效的排序算法:qsort使用快速排序算法,这是一种高效的排序算法,平均时间复杂度为O(nlogn),能够在大多数情况下快速地对数组进行排序。

2. 通用性:qsort是一个通用的排序函数,可以用于对各种类型的数组进行排序,只需要提供相应的比较函数即可。这使得它在处理不同类型的数据时非常方便。

3. 灵活性:通过提供自定义的比较函数,可以根据不同的需求对数组进行排序。比如可以按照升序或降序排列,也可以按照自定义的规则进行排序。

4. 标准化: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 标准库头文件 - cppreference.com

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博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/136477956

20数组二十个元素

这里的问题是怎么样进行排序

是年龄还是名字 肯定是年龄

那么继续是qsort 和回调函数

但是进行计算的时候他不知道这个是结构体

所以需要强制类型转化

结构体里面 需要知道不管是名字 还是字母,本质就是字符串

两个字符串的比较需要strcmp

strcmp需要头文件 string.h

强制类型转换之后 再进行这个strcmp进行比较

 这个强制类型转化是临时的

不理解强制类型转化的 可以去看看这个文章C语言——强制类型转化-CSDN博客icon-default.png?t=N7T8https://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();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/739055.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

​知识图谱:基于嵌入的模型(TransE 、TransH、TransR和TransD)

(一)TransE: Translating Embeddings for Modeling Multi-relational Data. Antoine Bordes, Nicolas Usunier, Alberto Garcia-Duran, Jason Weston, Oksana Yakhnenko. NIPS 2013. 论文地址:http://papers.nips.cc/paper/5071-translating-embeddings-for-modeling-multi-…

[论文笔记]跨语言摘要最新综述:典型挑战及解决方案

https://arxiv.org/abs/2203.12515 跨语言摘要是指为给定的一种语言(例如中文)的文档生成另一种语言(例如英文)的摘要。 图1:四个端到端框架的概述。XLS:跨语言摘要;MT:机器翻译;MS:单语摘要。虚线箭头表示监督信号。无框彩色方块表示相应任务的输入或输出…

Deep Learning for Detecting Robotic Grasps

链接&#xff1a;1301.3592.pdf (arxiv.org) 这个用于从单一RGB-D视图进行机器人抓取检测的算法包括以下步骤&#xff1a; 图像获取&#xff1a; 机器人获取包含待抓取对象的场景的RGB-D图像。 抓取评分&#xff1a; 使用小型深度网络对RGB-D图像中的潜在抓取进行评分。抓取以在…

如何才能做一名渗透测试人员?

学习实践&#xff0c;目前只有这路子&#xff0c;自学9月&#xff0c;成功入圈。下面说一下自己的学习路径&#xff0c;都是摸爬滚打&#xff0c;交了N份钱才学会的。 切记一定要先了解整个渗透测试的流程&#xff0c;记住整个流程口诀&#xff1a;信息收集&打点&#xff…

Linux:进程

进程 知识铺垫冯诺依曼体系结构操作系统&#xff08;OS&#xff09; 进程概念进程的查看ps 命令获取进程 pid文件内查看进程终止进程的方式kill命令快捷键 进程的创建 forkfork 返回值问题 进程状态运行状态 &#xff1a;R休眠状态&#xff1a;S &#xff08;可中断&#xff09…

Python实用工具:三维坐标点的键值对数组的值替换功能

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 代码: # 定义数据列表 data [{x: 9000.00, y: 0.00, z: 28209.83},{x: 8950.70, y: 940.76, z: 28209.83},{x: 8803.33, y: 1871.21, z: 28209.83},{x: 8559.51, y: 2781.15, z: 28209.83},{x: 8221.91, y: …

Vue源码系列讲解——内置组件篇【一】(keep-alive)

目录 1. 前言 2 用法回顾 3. 实现原理 props created destroyed mounted render 4. 生命周期钩子 5. 总结 1. 前言 <keep-alive> 是 Vue 实现的一个内置组件&#xff0c;也就是说 Vue 源码不仅实现了一套组件化的机制&#xff0c;也实现了一些内置组件&#xf…

数据集生成 YOLOV5 可训练的数据目录、并且可视化

1、前言 YOLOV5 训练数据的目录结构如下&#xff1a; 如果有测试集的话&#xff0c;也按照下面目录摆放即可 注意&#xff1a;这里的图片和标签文件名要严格对应&#xff01;&#xff01;后缀除外 关于YOLOv5介绍或者yolo格式的介绍参考之前专栏&#xff0c; 2、划分数据生成…

SpringMVC04、Controller 及 RestFul

4、Controller 及 RestFul 4.1、控制器Controller 控制器复杂提供访问应用程序的行为&#xff0c;通常通过接口定义或注解定义两种方法实现。控制器负责解析用户的请求并将其转换为一个模型。在Spring MVC中一个控制器类可以包含多个方法在Spring MVC中&#xff0c;对于Contr…

http协议中的强缓存与协商缓存,带图详解

此篇抽自本人之前的文章&#xff1a;http面试题整理 。 别急着跳转&#xff0c;先把缓存知识学会了~ http中的缓存分为两种&#xff1a;强缓存、协商缓存。 强缓存 响应头中的 status 是 200&#xff0c;相关字段有expires&#xff08;http1.0&#xff09;,cache-control&…

C++ :内存管理 newdelete

目录 内存区域划分 C的动态内存的管理方式 new new的基本使用方法 【注意事项】 delete 【注意】 new和delete操作自定义类型 operator new 和 operator delete 【关于自定义类型new申请内存】 【原理】 【调用顺序】 【连续开辟空间问题】 malloc/free和…

基于UC3842的开关电源设计

基于UC3842的开关电源设计 摘要 在现代经济和科技的飞速发展的时代&#xff0c;电源的运用已经变得非常常见。目前&#xff0c;各种科技手段正在推动着电力电子技术和电源设备研发技术的快速发展&#xff0c;同时也促使电源设备向着高频化集成化方向发展。电源供电设备的开发和…

动态规划课堂5-----子序列问题(动态规划 + 哈希表)

目录 引言&#xff1a; 例题1&#xff1a;最长递增子序列 例题2&#xff1a;最长定差子序列 例题3&#xff1a;最长的斐波那契子序列的长度 例题4&#xff1a;最长等差数列 例题5&#xff1a;等差数列划分II-子序列 结语&#xff1a; 引言&#xff1a; 要想解决子序列问…

亚信安慧AntDB的超融合之路

在面对较大用户规模的系统建设需求时&#xff0c;传统数据库往往面临诸多挑战。为了满足多样化的技术要求&#xff0c;项目通常需要倚赖多套技术体系&#xff0c;例如&#xff0c;一套关系型数据库用于元数据管理和标签化管理&#xff0c;另一套则专注于非结构化文件的处理。这…

OWASP Top 10 网络安全10大漏洞——A03:2021-注入

10大Web应用程序安全风险 2021年top10中有三个新类别、四个类别的命名和范围变化&#xff0c;以及一些合并。 A03:2021-注入 Injection从第一的位置滑落至第三位置。94% 的应用程序针对某种形式的注入进行了测试&#xff0c;最大发生率为 19%&#xff0c;平均发生率为 3%&a…

STM32CubeMX学习笔记19——SD卡(SDIO接口)

1、简介 1.1 SD卡简介 很多单片机系统都需要大容量存储设备&#xff0c;以存储数据&#xff08;常用的有U盘、FLASH芯片、SD卡等&#xff09;&#xff0c;比较而言SD卡是单片机大容量外部存储的首选&#xff0c;只需要少数几个IO口即可外扩一个容量从几十M到几十G的&#xff…

力扣--动态规划/深度优先算法/回溯算法93.复原IP地址

这题主要用了动态规划和回溯算法。 动态规划数组初始化&#xff08;DP数组&#xff09;: 首先&#xff0c;创建一个二维数组dp&#xff0c;用于记录字符串中哪些部分是合法的IP地址。对字符串进行遍历&#xff0c;同时考虑每个可能的IP地址部分&#xff08;每部分由1到3个字符组…

共同聚焦空气污染治理,打造可持续发展未来|中联环保圈

在2024年全国生态环境保护工作会议上&#xff0c;我国生态环境部明确提出&#xff0c;“加强重点区域空气质量改善的监督帮扶和统筹强化监督”将成为未来生态环境保护工作的重中之重。这一战略方向的转变&#xff0c;清晰地展现了我国在空气污染治理政策上的优化和深化。 回顾2…

antd vue Tabs控件的使用

Ant Design Vue-------Tabs标签页 今天就讲讲Ant Design Vue下的控件----tabs 标签页 结合项目中的需求&#xff0c;讲一下该控件如何使用&#xff0c;需求&#xff1a; &#xff08;1&#xff09;竖排样式 &#xff08;2&#xff09;如何使用v-for绑定数据源 &#xff08;3…

当HR问你为什么申请这个职业,你该怎么回答?【文章底部添加进大学生就业交流群】

目录 强调对公司的了解&#xff1a; 突出你的技能和经验&#xff1a; 表达对行业的热情&#xff1a; 谈论个人发展&#xff1a; 对公司的价值观的契合&#xff1a; 当HR问你为什么申请这个职业时&#xff0c;你可以通过以下方式回答&#xff1a; 强调对公司的了解&#xf…