预计阅读本页时间:-
通用工具库包含各种函数,其中包括随机数产生函数、搜索和排序函数、转换函数和内存管理函数。在第12章“存储类、链接和内存管理”中您已经见到过rand()、srand()、malloc()和free()。ANSI C中,这些函数的原型在头文件stdlib.h中。参考资料5中列出了该系列的所有函数。现在我们对其中几个函数作进一步讨论。
16.10.1 exit( )和atexit( )函数
我们在一些示例程序中已经显式地使用了exit()函数。另外,从main()返回时自动调用exit()函数。ANSI标准还增加了一些我们还未使用过的很好的功能。最重要的新增功能为:可以指定执行exit()时调用的特定函数。通过对退出时调用的函数进行注册,atexit()函数也提供这项功能;atexit()函数使用函数指针作为参数。程序清单16.14说明了这个工作机制。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
程序清单16.14 byebye.c程序
下面是一个运行示例:
在IDE中运行该程序时,可能看不到最后2行输出。
下面是另一个运行示例:
在IDE中运行该程序时,可能看不到最后4行输出。
接下来我们讨论两个主要方面:atexit()和exit()的参数的使用。
一、使用atexit( )
该函数使用函数指针!要使用atexit()函数,只需把退出时要调用的函数地址传递给atexit()。因为作为函数参数时,函数名代表地址,所以使用sign_off或too_bad作为参数。于是atexit()把作为其参数的函数在调用exit()时执行的函数列表中进行注册。ANSI保证在这个列表中至少可放置32个函数。通过使用一个单独的atexit()调用把每个函数添加到列表中。最后,调用exit()函数时,按先进后出(先执行最后添加的函数)的顺序执行这些函数。
注意,输入失败时既调用了sign_off(),也调用了too_bad();而输入成功时只调用了sign_off()。这是因为只有在输入失败时,才通过if语句注册too_bad()。还需注意,先调用最后注册的函数。
由atexit()注册的函数(如sign_off()和too_bad())的类型应该为不接受任何参数的void函数。通常它们执行内部处理任务,如更新程序监视文件或重置环境变量。
注意,main()终止时会隐式地调用exit();因此,即使未显式地调用exit(),也会调用sign_off()。
二、使用exit( )
exit()执行了atexit()指定的函数后,将做一些自身清理工作。它会刷新所有输出流、关闭所有打开的流,并关闭通过调用标准I/O函数tmpfile()创建的临时文件。然后,exit()把控制返回给主机环境(如果可能,还向主机环境报告终止状态)。习惯上,UNIX程序用0表示成功终止,用非零值表示失败。UNIX返回的代码并不适用于所有系统,因此ANSI C定义了可移植的表示失败的宏EXIT_FAILURE。与之类似,ANSI C定义EXIT_SUCCESS表示成功,但是exit()也接受用0代表成功。ANSI C中,在非递归的main()函数中使用exit()函数等价于使用关键字return。但是,在main()以外的函数中使用exit()也会终止程序。
16.10.2 qsort( )函数
快速排序(quick sort)法是最有效的排序算法之一,对大型数组而言更是如此。该算法在1962年由C.A.R.Hoare开发。它把数组不断分成更小的数组,直到变成单元素数组。首先,将数组分成两部分,其中一部分的值都小于另一部分的值。继续这个过程,直至数组完全排好序为止。
C实现的快速排序算法的函数名为qsort()。qsort()函数对数据对象数组进行排序,其ANSI原型为:
第一个参数为指向要排序的数组头部的指针。ANSI C允许将任何数据类型的指针转换为void类型指针,因而qsort()的第一个实际参数可以指向任何类型的数组。
第二个参数为需要排序的项目数量。函数原型将该值转换为size_t类型。回忆一下前面的多次说明,size_t是由运算符sizeof返回,并在标准头文件中定义的整数类型。
因为qsort()将第一个参数转换为void指针,所以会失去每个数组元素的大小信息。为补充该信息,必须把数据对象的大小明确地告诉qsort()。这就是第三个参数的作用。例如,如果对double数组排序,可使用sizeof(double)作为qsort()的第三个参数。
最后,qsort()还需要一个指向函数的指针,被指向的函数用于确定排序顺序。这个比较函数应该接受两个参数,即分别指向进行比较的两个项目的指针。如果第一个项目的值大于第二个项目的值,那么比较函数返回正数;如果两个项目的值相等,那么返回0;如果第一个项目的值小于第二个项目的值,那么返回负数。qsort()根据给定的其他信息计算出两个指针值,然后把它们传递给该比较函数。
比较函数采用的形式在qsort()原型最后的参数中声明:
这表示最后的参数是个指向函数的指针,该函数返回int值并接受两个参数,而每个参数均为指向const void类型的指针。这两个指针指向需要比较的项目。
程序清单16.15以及后面的讨论举例说明了定义比较函数和使用qsort()的方法。程序清单中的程序创建了 一个由随机浮点数组成的数组,并对该数组进行排序。
程序清单16.15 qsorter.c程序
下面是一个运行示例:
我们考虑两个主要方面:qsort()的使用和mycomp()的定义。
一、使用qsort( )
qsort()函数对一个数据对象数组进行排序。我们再次给出它的ANSI原型:
第一个参数为指向要排序的数组头部的指针。本程序的实际参数为vals。vals是一个double数组名,因此是指向数组第一个元素的指针。这个ANSI原型把参数vals类型指派为void指针。这是因为ANSI C允许把任何数据类型指针类型指派为void指针,从而允许qsort()的第一个实际参数指向任何类型的数组。
第二个参数为需要排序的项目数量。程序清单16.15中为Num,即数组元素的个数。函数原型将该值转换为size_t类型。
第三个参数为每个元素的大小。本例中为sizeof(double)。
最后的参数为mycomp,即对元素进行比较的函数的地址。
二、定义mycomp( )
前面提到,qsort()原型规定了比较函数的形式:
这表示这个最后的参数是个指向函数的指针,该函数返回int值并接受两个参数,而每个参数均为指向const void类型的指针。在程序中我们使mycomp()函数的原型与这个原型保持一致:
需要记住,函数名作参数时是指向该函数的指针。因此,mycomp与compar原型相匹配。
qsort()函数把进行比较的两个元素的地址传递给比较函数。本程序中,p1和p2为进行比较的两个doble型数的地址。注意qsort()的第一个参数指整个数组,比较函数的两个参数指数组中的两个元素。这里存在一个问题:要比较指针型值,需对指针进行取值运算。因为要比较的值为double类型,所以应当对double类型的指针进行取值运算。但是,qsort()要求void型指针。解决这个问题的方法是:在函数内部声明两个正确类型的指针,并把它们初始化为传递进来的参数的值:
简而言之,为了通用性,qsort()和比较函数使用void指针。因此,必须把数组中每个元素的大小明确地告诉qsort();并且在比较函数的定义中,需要把指针参数转换为对具体应用而言类型正确的指针。