回调函数
一个函数的地址当作另一个函数的参数。在另一个函数中用这函数的地址去调用该函数,则该函数为回调函数。
我们只需要了解下它的定义就行。不需要特别关注(毕竟我们是写代码,又不是写它的定义)。
qsort函数
qsort函数的简单介绍
qsort是一个库函数 其头文件为#include<stdlib.h>
上方该图为qsort函数的声明。
对于qsort函数 其可以实现将任意类型的数组进行升序或者降序排列。
当我们使用qsort进行排列时,其qsort函数内部是通过快速排序来实现排列(我们并没学快速排列,其涉及的知识对我来说超纲)
对于其参数四个类型中,唯独最后的函数指针接受其相同类型函数的函数名:所以其接收的函数需要自定义, 要单独拿出来。
该自定义函数是用来进行两个数比较。且降序还是升序由它来控制。当e1所指向的数组中内容大于e2所指向的数组中内容,返回值大于0(小于时,返回值小于0)为升序排列。
相反,当e1内容小于e2时,返回值却大于0。e1内容大于e2时,返回值小于0.则为降序排列。
其中不同类型进行排列,该函数就有不同的代码。一般来说比如我们下方的整形格式排列。而其他类型也一样,只要让其能两个数比较出来就行。
这里再说一点自定义函数类型必须类型为 int(const void*,const void*),因为qsort要求的函数指针类型为int (*)(const void*,const void*),所以类型必须都为这种。
对于qsort函数的排序不同类型时的不同代码格式,这里我们还展示其他几种特殊的排列。
结构体排列的自定义函数
结构体进行排列,并不能里面的数据全部同时进行排列,只能选结构体里面的一种数据进行排列
(这个道理显而易见)
其中我们用到了结构体操作访问符,之前也介绍过->左边为结构体指针,右边为变量。这里就用到了该操作符。
我们还可以用另外一个操作符。点操作符,左边就为结构体,右边为变量。所以如果用点操作符,则变为 return (int)(*(stu*)e1.weight-*(stu*)e2.weight) .只要返回值正确,用哪个都行。
字符串大小比较
在说把字符串进行排列时,我们需要提前了解字符串大小比较和字符串长度比较是有区别的
字符串长度比较是直接看谁的字符多。
而字符串大小比较是先拿第一个字符的ascall码值进行比较,谁ascall码值大谁字符串大,如果第一个相等则拿第二个进行比较,依次下去(如果到最后都相同,则相等,也就是两个字符串是一样的) 在下面这篇文章中有更多关于字符串大小比较的细节(总而言之就是比ascall码值)
https://blog.csdn.net/weixin_37962206/article/details/121655942
这篇文章有一个是错误的那就是 abcd依次往后ascall码值不断增大,并不是不断减小
该篇文章错误为这,搞反了。应该是不断增大。
下面为执行图 ,能证明
字符串大小排列的自定义函数
strcmp库函数
了解了以上这些,对于字符串大小进行比较,其实我们有一个库函数可以直接将其进行比较
那就是strcmp,其头文件为#include<string.h>, 如下是其用途
其参数类型为const char* ,可以接收char*(隐式转换)和const char*。但如果参数类型为char*,只能接受char *,不能接收const char*(不能隐式转换为char *)
所以我们只需要将整个字符串输入就行,其整个字符串代表首位字符地址类型为char *,可行。之后就通过该库函数内部的代码操作从而比较出来大小。
返回值大于0,则第一个比第二个大。同理小则小,为0则相等
其返回值跟我们自定义函数返回值一样。所以可以完美用于其自定义函数上
字符串大小排列的自定义函数
上述代码没问题,能隐式转换从而变为 const char*,库函数最终会返回出大于0或者小于0或者等于0的值,符合自定义函数的返回值。从而能比较出字符串大小
字符串长度排列的自定义函数
上方为自定义函数,下方为strlen的声明
同理其跟strcmp是一样的道理,char *和const char*都可以接受,从而返回出其长度(类型为size_t,无符号整数)。其这种类型的自定义函数代码如上较为简单,不过多叙述。
关于qsort的自定义函数的更多种类比较代码
除以上几种还有更多自定义函数种类比较代码 下面给个链接,里面文章讲述的非常详细。
https://blog.csdn.net/ZDDWLIG/article/details/120209948
模拟qsort函数的实现
由于qsort函数内部是通过快速排序实现的,快速排序的知识点对我们来说超纲了 ,而排序我们只学过冒泡排序,所以模拟实现我们只能通过冒泡排序实现。
其中的代码和qsort内部代码肯定不同。但是虽然不同,我们至少也要了解到底该如何利用这四个参数去实现任意类型(不管其用什么方法),毕竟了解的越多,更能提升我们思维能力。
void*能接收任意类型的地址,但其不能用于计算(不能用于跟类型有关的计算),所以需要强制类型转换。
所以我们强制类型转化为char*,之所以用char*,是因为用它计算时是乘1再进行计算,能完美利用参数从而得到所有地址(所有类型的)。用其他的话就有些类型不适用。
而到了交换这来,由于是任意类型都能接收,所以不能把整个类型都交换,因为如果你这次能把int给整个交换,那么它就只能交换int,其他不能整个交换。所以我们这时就想到将一个一个字节进行交换 ,其中任意类型都适用。
而对于该模拟的qsort函数内部代码,不管任意类型都能实现排序(结构体,字符串等等),且函数内部不变,而只需要变的则是外部的比较的自定义函数。
且我们还可以对该函数进行优化,提高效率,之前冒泡排序有讲到 我们只需要跟普通冒泡排序进行优化一样,跟它一样加几句代码则也可以实现,没有太复杂。
所以这就是qsort函数的模拟实现(用冒泡排序实现跟其本质快速排序实现不一样) ,这模拟实现其实没有太过复杂。我们可以了解下知道它的一些很巧妙的地方,提升下思维。
目前已知学的库函数
printf ,scanf 头文件都为 #include <stdio.h>
strlen ,strcmp 头文件都为#include <string.h>
qsort ,rand(在猜数字游戏中出现), srand(在猜数字游戏中出现)头文件为#include<stdlib.h>
time (在猜数字游戏中出现) 头文件为#include<time.h>
assert库函数所用的头文件#include<assert.h>
这就是目前已学的所有库函数
总结
这就是指针(4) 的内容,内容有点少,其中的重点是qsort函数。
所以到这就结束了,谢谢大家!