1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
二、qsort函数
2.1、qsort函数的定义
下面,我们学习一个新的函数:
q
s
o
r
t
qsort
q sor t 函数 。
q
s
o
r
t
qsort
q sor t 函数 的功能是对 任意类型 的数据进行排序 ,它是怎么做到的呢? 首先,让我们一起来看看他的定义 :
图片来源:cplusplus.com 参数讲解: (1)
v
o
i
d
∗
b
a
s
e
void* base
v o i d ∗ ba se
翻译 :指向数组中要排序的第一个对象的指针 ,转换为
v
o
i
d
void
v o i d 。 第一个参数
b
a
s
e
base
ba se 其实就是传递待排序数组的首元素地址
,它的类型是
v
o
i
d
void
v o i d * .为什么是
v
o
i
d
void
v o i d * 呢?因为
q
s
o
r
t
qsort
q sor t 函数要排序的是==任意类型 的数据,它 事先并不知道你要传递给它的数据 类型==,因此这里用
v
o
i
d
void
v o i d * 指针,以便能接受所有类型的数据 。
(2)
s
i
z
e
size
s i ze _
t
t
t
n
u
m
num
n u m
翻译:基数指向的数组中元素的个数 ,
s
i
z
e
size
s i ze _
t
t
t 是一个无符号整型。 第二个参数
n
u
m
num
n u m 其实就是待排序数组的元素个数 ,因为元素个数肯定是正数,所以这里用
s
i
z
e
size
s i ze _
t
t
t 类型。
(3)
s
i
z
e
size
s i ze _
t
t
t
s
i
z
e
size
s i ze
翻译:数组中每个元素的字节大小 。
s
i
z
e
size
s i ze _
t
t
t 是一个无符号整型。 第三个参数
s
i
z
e
size
s i ze 指的是待排序数组中每个元素的大小 ,单位是字节。
(4)
i
n
t
int
in t ( *
c
o
m
p
a
r
compar
co m p a r ) (
c
o
n
s
t
const
co n s t
v
o
i
d
void
v o i d * ,
c
o
n
s
t
const
co n s t
v
o
i
d
void
v o i d )
介绍之前,我们先来观察第四个元素
c
o
m
p
a
r
compar
co m p a r 的类型:
i
n
t
int
in t ( *
c
o
m
p
a
r
compar
co m p a r ) (
c
o
n
s
t
const
co n s t
v
o
i
d
void
v o i d * ,
c
o
n
s
t
const
co n s t
v
o
i
d
void
v o i d ) 。 没错,它是一个函数指针
,也就是说我们要给它传递一个函数地址
。 这是个怎样的函数呢?有什么要求呢?我们一起来看看。
先来看看函数的参数类型:
(
c
o
n
s
t
(const
( co n s t
v
o
i
d
∗
p
1
void* p1
v o i d ∗ p 1 ,
c
o
n
s
t
const
co n s t
v
o
i
d
∗
p
2
)
void* p2)
v o i d ∗ p 2 ) ,它有两个参数 ,均为
c
o
n
s
t
const
co n s t
v
o
i
d
void
v o i d * 类型,返回类型是
i
n
t
int
in t 。 接着是这个函数的具体功能:它的功能是实现两个元素的比较 。
p1 < p2
,返回 < 0
的数,相等
,返回 0
,p1 > p2
,返回 > 0
的数。
为什么要有这个参数呢,我们这里可以回顾一下上面的冒泡排序:
在内层循环中,该冒泡排序通过两两数相比较来决定是否换位,以实现最终排序,而arr[j] > arr[j + 1]
是整型的比较方式。 但是
q
s
o
r
t
qsort
q sor t 函数需要实现的是任意类型的排序 ,而每一种类型的比较方式是不同
的,一种类型一种写法。
q
s
o
r
t
qsort
q sor t 函数事先并不知道使用者要求用什么类型来排,怎么办呢?
q
s
o
r
t
qsort
q sor t 函数便让函数使用者自己写一个待排序类型的比较函数
,然后将该函数地址传给
q
s
o
r
t
qsort
q sor t ,
q
s
o
r
t
qsort
q sor t 内部再不断调用 该函数进行比较 ,以达到排序的目的。 同时,该函数仅仅是为了对两个数进行比较,并不会对两个数进行修改
,因此前面加上
c
o
n
s
t
const
co n s t 来修饰。
2.2、 qosrt函数的使用
了解了
q
s
o
r
t
qsort
q sor t 函数后,让我们一起练习它的使用吧。
(1)比较函数的写法
我们已经知道要使用
q
o
s
r
t
qosrt
q osr t 函数,需传递一个比较函数的地址的,那这个比较函数怎么写呢?接下来,让我们试着来写一写这个比较函数吧。
下面我们来举几个例子吧 整型比较:
int cmp_int ( const void * p1, const void * p2)
{
return ( * ( int * ) p1 - * ( int * ) p2) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注:运算前,要进行强制类型转换
,因为
v
o
i
d
void
v o i d * 类型并不能进行解引用和 +- 整数的运算
,应强制类型转换成要比较数据的类型
,再进行相关运算。 结构体数据比较(按字符串比较):
typedef struct stu
{
char name[ 20 ] ;
int age;
} stu;
int cmp_stu_name ( const void * p1, const void * p2)
{
return strcmp ( ( ( stu* ) p1) -> name, ( ( stu* ) p2) -> name) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注:有关
t
y
p
e
d
e
f
typedef
t y p e d e f 关键字 的用法详情请看【C语言】——指针四:字符指针与函数指针变量 。 注:
s
t
r
c
m
p
strcmp
s t rc m p 函数 是专门用来比较字符串大小的函数,在之后的学习中我们会进一步认识他。
(2)使用
q
s
o
r
t
qsort
q sor t 函数
下面,一起来尝试使用
q
s
o
r
t
qsort
q sor t 函数来排序吧 整型数组排序:
# include
int cmp_int ( const void * p1, const void * p2)
{
return ( * ( int * ) p1 - * ( int * ) p2) ;
}
int main ( )
{
int arr[ ] = { 6 , 4 , 1 , 2 , 9 , 3 , 7 , 8 , 10 , 5 } ;
int sz = sizeof ( arr) / sizeof ( arr[ 0 ] ) ;
qsort ( arr, sz, sizeof ( arr[ 0 ] ) , cmp_int) ;
int i = 0 ;
for ( i = 0 ; i < sz; i++ )
{
printf ( "%d " , * ( arr + i) ) ;
}
printf ( "\n" ) ;
return 0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
运行结果: 结构体排序(按字符串):
# include
int cmp_stu_name ( const void * p1, const void * p2)
{
return strcmp ( ( ( stu* ) p1) -> name, ( ( stu* ) p2) -> name) ;
}
int main ( )
{
stu s[ ] = { { "zhangsan" , 19 } , { "lisi" , 18 } , { "wangwu" , 20 } } ;
int sz = sizeof ( s) / sizeof ( s[ 0 ] ) ;
qsort ( & s, sz, sizeof ( s[ 0 ] ) , cmp_stu_name) ;
int i = 0 ;
for ( i = 0 ; i < sz; i++ )
{
printf ( "%s %d\n" , s[ i] . name, s[ i] . age) ;
}
return 0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
运行结果:
2.3、用冒泡排序模拟实现
q
s
o
r
t
qsort
q sor t 函数
了解了冒泡排序和
q
s
o
r
t
qsort
q sor t 函数,接下来,我们来实现用冒泡排序来模拟实现
q
s
o
r
t
qsort
q sor t 函数。
(1)函数声明
首先,一个函数声明最重要的就是函数名
;参数类型
;返回类型
。既然是模仿,我们就要模仿的像,因此,这部分我们可以直接照着官方的函数声明来。
void my_qsort ( void * base, size_t num, size_t size, int ( * compar) ( const void * , const void * ) )
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
(2)函数基本骨架
我们是用冒泡排序来实现
q
s
o
r
t
qsort
q sor t 函数,那么我们就可以以上面冒泡排序排整型数组的代码为基础,想想哪些可以保留,那些应该去除。
int * bubble ( int * arr, int sz)
{
int i = 0 ;
for ( i = 0 ; i < sz - 1 ; i++ )
{
int j = 0 ;
for ( j = 0 ; j < sz - i - 1 ; j++ )
{
if ( arr[ j] > arr[ j + 1 ] )
{
int temp = arr[ j] ;
arr[ j] = arr[ j + 1 ] ;
arr[ j + 1 ] = temp;
}
}
}
return arr;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
既然是用冒泡排序实现
q
s
o
r
t
qsort
q sor t 函数,那么有关冒泡排序核心思想的代码应该保留下来,而具体的实现方法,如:前后量元素比较大小、交换两元素,因为每种类型的具体实现方式不同 ,应该被替换掉。 这样,我们函数的大体骨干就出来啦:
void my_qsort ( void * base, size_t num, size_t size, int ( * compar) ( const void * , const void * ) )
{
int i = 0 ;
for ( i = 0 ; i < num - 1 ; i++ )
{
int flag = 1 ;
int j = 0 ;
for ( j = 0 ; j < num - 1 - i; j++ )
{
if ( )
{
flag = 0 ;
}
}
if ( 1 == flag)
{
break ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
(3)比较两个元素的大小
比较两个元素的大小的方法前面已经介绍了,既然每一种类型的比较方式不同,那么
q
s
o
r
t
qsort
q sor t 就让使用者自己写一个比较函数传进来。 这里,我们来讲讲如何在
q
s
o
r
t
qsort
q sor t 函数中使用比较函数
通过比较函数的声明我们知道,它需要我们传递要比较的两个元素的地址,那这两个元素的地址怎么找呢? 在《【C语言】—— 指针一 : 初识指针(上)》 中,我们了解到,一种类型的变量,不论该类型大小是几个字节,进行取地址操作时,取出的是它所有字节中地址最小的字节的地址
,即该元素的地址是其地址最小字节的地址 ,那么我们只要想办法将数据中各个元素地址最小的字节的地址就好了 开始时,我们传入的
b
a
s
e
base
ba se 的地址是指向首元素的最低字节的地址 ,为
v
o
i
d
void
v o i d * 类型,怎么让他指向第二个元素呢?
首先,
v
o
i
d
void
v o i d * 类型指针不能进行 ± 整数的运算,我们先进行强制类型转换,因为
c
h
a
r
char
c ha r * 一次访问权限只有一个字节,所以将其转换成
c
h
a
r
char
c ha r * 指针,以方便后面的运算。 那么转换成
c
h
a
r
char
c ha r * 后,又该跳过几个字节呢?别忘了,我们还有一个参数没用到:
s
i
z
e
size
s i ze 。该元素类型大小为几个字节,我们指针就跳过多少个字节
,这样就能指向下一个元素啦,即(
c
h
a
r
char
c ha r *)
b
a
s
e
base
ba se +
s
i
z
e
size
s i ze 。
现在找第二个元素没问题了,那么想要找到第
j
j
j 个元素怎么办呢?找第
j
j
j 个元素,即将指针从起始位置跳过
j
∗
s
i
z
e
j * size
j ∗ s i ze 个字节,即
(
(
c
h
a
r
((char
(( c ha r * )
b
a
s
e
+
j
base + j
ba se + j *
s
i
z
e
)
size)
s i ze ) 。
我们以整型数组为例:
这样,我们就可以将需要比较的元素地址准确传给比较函数 啦。 这里截取一小段:
for ( j = 0 ; j < num - 1 - i; j++ )
{
if ( compar ( ( char * ) base + j * size, ( char * ) base + ( j + 1 ) * size) > 0 )
{
flag = 0 ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
(4)交换两个元素
交换元素部分的代码我们可以直接像交换两个整型数据一样写吗?答案肯定是不可以 的,原因还是一样,每一种类型都是不一样的。 那么该怎么交换呢?我们可以 一个字节一个字节地逐一交换 。
我们可以单独封装 一个交换函数 。
void swap ( char * p1, char * p2, size_t size)
{
int i = 0 ;
for ( i = 0 ; i < size; i++ )
{
char temp = * ( p1 + i) ;
* ( p1 + i) = * ( p2 + i) ;
* ( p2 + i) = temp;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
p
1
p1
p 1 与
p
2
p2
p 2 为两个要交换元素的地址,
s
i
z
e
size
s i ze 为需要交换的字节数。在传址时,两元素的传址方法与比较函数的传址方法相同,即
(
(
c
h
a
r
((char
(( c ha r * )
b
a
s
e
+
j
base + j
ba se + j *
s
i
z
e
)
size)
s i ze ) 。 因为在传址时,传的已经是
c
h
a
r
char
c ha r * 类型的指针了,这里函数形参直接用
c
h
a
r
char
c ha r * 就行,不需要
v
o
i
d
void
v o i d * 再进行强制类型转换
(5)完整代码
下面是冒泡排序模拟实现
q
s
o
r
t
qsort
q sor t 函数的完整代码(以排序整型数组为例):
# include
int cmp_int ( const void * p1, const void * p2)
{
return ( * ( int * ) p1 - * ( int * ) p2) ;
}
void swap ( char * p1, char * p2, size_t size)
{
int i = 0 ;
for ( i = 0 ; i < size; i++ )
{
char temp = * ( p1 + i) ;
* ( p1 + i) = * ( p2 + i) ;
* ( p2 + i) = temp;
}
}
void my_qsort ( void * base, size_t num, size_t size, int ( * compar) ( const void * , const void * ) )
{
int i = 0 ;
for ( i = 0 ; i < num - 1 ; i++ )
{
int flag = 1 ;
int j = 0 ;
for ( j = 0 ; j < num - 1 - i; j++ )
{
if ( compar ( ( ( char * ) base + j * size) , ( ( char * ) base + ( j + 1 ) * size) ) > 0 )
{
swap ( ( char * ) base + j * size, ( char * ) base + ( j + 1 ) * size, size) ;
flag = 0 ;
}
}
if ( 1 == flag)
{
break ;
}
}
}
int main ( )
{
int arr[ ] = { 6 , 4 , 1 , 2 , 9 , 3 , 7 , 8 , 10 , 5 } ;
int sz = sizeof ( arr) / sizeof ( arr[ 0 ] ) ;
my_qsort ( arr, sz, sizeof ( arr[ 0 ] ) , cmp_int) ;
for ( int i = 0 ; i < sz; i++ )
{
printf ( "%d " , * ( arr + i) ) ;
}
printf ( "\n" ) ;
return 0 ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
好啦,本期关于冒泡排序与
q
s
o
r
t
qsort
q sor t 函数就介绍到这里啦,希望本期博客能对你有所帮助,同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/yusjushd/article/details/137027195","extend1":"pc","ab":"new"}">>
评论记录:
回复评论: