文档章节

第16章 C预处理器和C库 16.10 通用工具库

idreamo
 idreamo
发布于 2017/07/30 07:45
字数 2838
阅读 8
收藏 0

通用工具库包含各种函数,其中包括随机数产生函数、搜索和排序函数、转换函数和内存管理函数 。ANSI C 中,这些函数的原型在头文件stdlib.h中。

16.10.1  exit()函数和atexit()函数

我们在一些示例中已经显式的使用了exit()函数。另外,从main( )返回时自动调用exit( )函数。 ANSI C标准还增加了一些我们还未使用过的很好的功能。最重要的新增功能为:可以指定执行exit( )时调用的特定函数 ;通过对退出时调用的函数进行注册,atexit()函数也提供这项功能;atexit()函数使用函数指针作为参数。程序清单16.14说明了这个工作机制。

程序清单 16.14  byebye.c程序

/*byebye.c   --atexit()示例程序*/
#include <stdio.h>
#include <stdlib.h>
void sign_off (void);
void too_bad (void);

int main(void)
{
    int n;
    
    atexit(sign_off);  /*注册sign_off()函数*/
    puts("Enter an integer: ");
    if(scanf("%d",&n)!=1))
    {
        put("That's no integer!");
        atexit(too_bad);  /*注册too_bad()函数*/
        exit(EXIT_FAILURE);
    }
    printf("%d is %s.\n",n,(n%2==0)?"even":"odd");
    return 0;
}

void sign_off(void)
{
    puts("Thus terminates another magnificent program from ");
    puts("SeeSaw Software!");
}

void too_bad(void)
{
    puts("SeeSaw software extends its heartfelt condolences");
    puts("to you upon the failure of your program.");
}

下面是一个运行示例:

Enter an integer:
212
212 is even.
Thus terminates another magnificent program from 
SeeSaw Software!

下面是另外一个运行示例

Enter an integer:
what?
That's no integer!
SeeSaw software extends its heartfelt condolences
to you upon the failure of your program.
Thus terminates another magnificent program from 
SeeSaw Software!

在IDE运行时,可能看不到最后两行(上个示例)或最后四行的输出。

一、使用atexit()

该函数使用函数指针!要使用atexit()函数,只需把退出时要调用的函数地址传递给atexit()函数。因为作为函数参数时,函数名代表地址,所以使用sign_off或too_bad 作为参数。于是atexit()把作为其参数的函数在调用exit()时执行的函数列表中进行注册。ANSI 保证在这个列表中至少可以放置32个函数。通过使用一个单独的atexit()调用把每个函数添加到列表中最后,调用exit()函数时,按先进后出(先执行最后添加的函数)的顺序执行这些函数。

由atexit()注册的函数的类型应该为不接受任何参数的void函数。通常它们执行内部处理任务,如更新程序监视文件或重置环境变量。

注意,main()终止时会隐式地调用exit();因此,即使未显式的调用exit(),也会调用sign_off()。

二、使用exit()

exit()执行了atexit()指定的函数后,将做一些自身清理工作。

它会刷新所有输出流关闭所有打开的流,并关闭通过调用标准I/O函数tmpfile()创建的临时文件。然后,exit()把控制返回给主机环境(如果可能,还向主机环境报告终止状态)。

习惯上,UNIX程序用0表示成功终止,用非零值表示失败。UNIX返回的代码并不适用于所有系统,因此ANSI C 定义了可移植的表示失败的宏EXIT_FAILURE。与之类似,ANSI C 定义EXIT_SUCESS表示成功,但是exit()也接受用0代表成功。ANSI C 中,在非递归的main()函数中使用exit()函数等价于使用关键字return。但是在main()以外的函数中使用exit()也会终止程序。

16.10.2  qsort( )函数

快速排序(quick sort)法是最有效的排序算法之一,对大型数组而言理是如此。首先,将数组分成两部分,其中一部分的值都小于另一部分的值。继续这个过程,直到数组完全排好序为止。

C实现的快速排序的算法的函数名为qsort( )。 qsort()函数对数据对象数组进行排序,其ANSI 原型为:

void qsort(void *base,size_t nmemb,size_t size,
int (*compar)(const void *,const void *));

第一参数为指向要排序的 数组头部的指针 ANSI C 允许将任何数据类型的指针转换为void类型指针,因而qsort()的第一个实际参数可以指向任何类型的数组

第二个参数为需要排序的 项目数量函数原型将该值转换为size_t类型。回忆一下,size_t是由运算符sizeof返回,并在标准头文件中定义的整数类型。

因为qsort()将第一个参数转换为void指针,所以会失去每个数组元素的大小信息。为补充该信息,必须把数组对象的大小告诉qsort()。这就是第三个参数的作用。 例如,如果对double数组排序,可使用sizeof(double)作为qsort()的第三个参数。

最后,qsort()还需要一个指向函数的指针,被指向的函数用于确定排序顺序。这个比较函数应该接受两个参数,即分别指向进行比较的两个项目的指针。如果第一个项目的值大于第二个项目的值,那么比较函数返回正数;如果两个值相等,那么返回0;如果第一个项目的值小于第二个项目,那么返回负数。qsort()根据给定的其他信息计算出两个指针值,然后把它们传递给该比较函数。

比较函数采用的形式在qsort()原型最后的参数中声明:

int (*compar)(const void *,const void *)

这表示最后的参数是个指向函数的指针,该函数返回INT值并接受两个参数。而每个参数均为指向const void类型的指针。这两个指针指向需要比较的项目。

程序清单16.15以及后面的讨论举例说明了定义比较函数和使用qsort()的方法。程序清单中的程序创建了一个由随机浮点数组成的数组,并对该数组进行排序。

程序清单16.15  qsorter.c程序

/*qsorter.c  --使用qsort()对一组数字排序*/
#include <stdio.h>
#include <stdlib.h>

#define NUM 40
void fillarray(double ar[],int n);
void showarray(const double ar[],int n);
int mycomp(const void *p1,const void *p2);

int main(void)
{
    double vals[NUM];
    fillarray(vals,NUM);
    puts("Random list: ");
    showarray(vals,NUM);
    qsort(vals,NUM,sizeof(double),mycomp);
    puts("\nSorted list: ");
    showarray(vals,NUM);
    return 0;
}

void fillarray(double ar[],int n)
{
    int index;
    for(index=0;index<n;index++)
        ar[index]=(double)rand()/((double)rand()+0.1);
}

void showarray(const double ar[],int n)
{
    int index;
    for(index=0;index<n;index++)
    {
        printf("%9.4f ",ar[index]);
        if(index % 6 == 5)
            putchar('\n');
    }
    if(index % 6 != 0)
        putchar('\n');
}
/*按从小到大的顺序排序*/
int mycomp(const void *p1,const void *p2)
{
    /*需要使用指向double的指针访问值*/
    const double *a1 = (const double *) p1;  /*a1是合适的指针类型*/
    const double *a2 = (const double *) p2;

    if(*a1<*a2)
        return -1;
    else if(*a1==*a2)
        return 0;
    else
        return 1;
}

下面是一个运行示例:

Random list:
   0.0022    0.2390    1.2191    0.3910    1.1021    0.2027
   1.3835   20.2830    0.2508    0.8880    2.2179   25.4866
   0.0236    0.9308    0.9911    0.2507    1.2802    0.0939
   0.9760    1.7217    1.2054    1.0326    3.7892    1.9635
   4.1137    0.9241    0.9971    1.5582    0.8955   35.3798
   4.0579   12.0460    0.0096    1.0109    0.8506    1.1529
   2.3614    1.5876    0.4825    6.8749

Sorted list:
   0.0022    0.0096    0.0236    0.0939    0.2027    0.2390
   0.2507    0.2508    0.3910    0.4825    0.8506    0.8880
   0.8955    0.9241    0.9308    0.9760    0.9911    0.9971
   1.0109    1.0326    1.1021    1.1529    1.2054    1.2191
   1.2802    1.3835    1.5582    1.5876    1.7217    1.9635
   2.2179    2.3614    3.7892    4.0579    4.1137    6.8749
  12.0460   20.2830   25.4866   35.3798

我们考虑两个主要方面:qsort()的使用和mycomp()的定义。

一、使用qsort( )

qsort( )函数对一个数据对象数组进行排序。我们再次给出它的ANSI 原型:

void qsort(void *base,size_t nmemb,size_t size,
int (*compar)(const void *,const void *));

第一个参数为指向要排序的数组头部的指针。本程序的实际参数为vals。vals是一个double数组名,因此是指向数组 的第一个元素的指针。这个ANSI原型把参数vals类型指派为void指针。这是因为ANSI C允许把任何数据类型指针类型指派为void指针,从而允许qsort()的第一个实际参数指向任何类型的数组 。

第二个参数为需要排序的项目数量。程序清单16.15中为NUM,即数组元素的个数。函数原型将该值转换为size_t类型。

第三个参数为每个元素的大小。本例中为sizeof(double)。

最后的一个参数为mycomp,即对元素进行比较的函数的地址。

二、定义mycomp( )

前面提到,qsort()原型规定了比较函数的形式:

int (*compar)(const void *,const void *)

在程序中,我们使mycomp()函数原型与这个原型保持一致:

int mycomp(const void *,const void *)

需要记住的是函数名作为参数时是指向该函数的指针。   因此,mycomp与compar原型匹配。

qsort()函数把进行比较的两个元素的地址传递给比较函数。本程序中,p1和p2为进行比较的两个double型数的地址。注意qsort()第一个参数指整个数组,比较函数的两个参数指数组中的两个元素。这里存在一个问题:要比较指针型值,需对指针进行取值运算。因要比较的值为double类型,所以应该对double类型的指针进行取值运算。但是,qsort()要求void型指针。解决这个问题的方法是:在函数内部声明两个正确类型的指针,并把它们初始化为传递进来的参数的值:

/*按从小到大的顺序排序*/
int mycomp(const void *p1,const void *p2)
{
    /*需要使用指向double的指针访问值*/
    const double *a1 = (const double *) p1;  /*a1是合适的指针类型*/
    const double *a2 = (const double *) p2;

    if(*a1<*a2)
        return -1;
    else if(*a1==*a2)
        return 0;
    else
        return 1;
}

简而言之,为了通用性,qsort()和比较函数使用void指针。因此,必须把数组中每个元素的大小明确的告诉qsort( )并且在比较函数的定义中,需要把指针参数转换为对具体应用而言类型正确的指针

C和C++对待void类型的指针是不同的。在把void*指针赋给一个指针或另一个不同类型的时候,C++需要一次强制类型转换。而C并没有这个需要。例如,程序清单16.15中的mycomp()函数对void*指针p1强制转型。

const double * a1 = (const double *) p1;

在C中这种强制类型转换是可选的,在C++中则是必须的。因为强制类型转换在这两种语言中都有作用,因此,使用它比较有意义。

考虑另外一个比较函数的示例。假设有这些声明:

struct names {
    char first[40];
    char last[40];
};
struct names staff[100];

如何调用qsort()呢?模仿程序清单16.15中对qsort()的调用,可以使用以下调用形式:

qsort(staff,100,sizeof(struct names),comp);

其中,comp是比较函数名。应该如何编写这个函数呢?假设先根据姓,再根据名排序,可以这样编写该函数:

#include <stdio.h>
int comp(const void *p1,const void *p2)  /*必须的形式*/
{
/*得到正确类型的指针*/
const struct names *ps1 = (const struct names *) p1;
const struct names *ps2 = (const struct names *) p2;
int res;

res=strcmp(ps1->last,ps2->last);  /*比较姓*/
if(res != 0)
    return res;
else                              /*姓相同的情况,比较名字*/
    return strcmp(ps1->first,ps2->first);
}

该函数使用strcmp( )函数进行比较。strcmp( )函数的可能返回值与比较函数的要求匹配。注意:对某结构使用->运算符时,需要指向该结构的指针。

© 著作权归作者所有

idreamo
粉丝 18
博文 139
码字总数 224743
作品 0
青岛
产品经理
私信 提问
C Primer Plus 第11章 11.7 ctype.h字符函数和字符串

第7章“C控制语句 分支和跳转”介绍了ctype.h系列字符相关的函数。这些函数不能被 应用于整个字符串,但是可以被应用于字符串中的个别字符。程序清单11.26定义了一个函数,它把toupper( )函数...

idreamo
2016/08/27
29
0
[编程语言]C陷阱与缺陷

内容摘要 作者以自己1985年在Bell实验室时发表的一篇论文为基础,结合自己的工作经验扩展成为这本对C程序员具有珍贵价值的经典著作。写作本书的出发点不是要批判C语言,而是要帮助C程序员绕过...

21gprs
2014/05/23
0
0
#define 中的“ # 运算符”和“ ## 运算符”

利用宏参数创建字符串:# 运算符 在类函数宏(function-like macro)的替换部分中,“#”符号用作一个预处理运算符,它可以把语言符号(token)转化为字符串。例如,如果 x 是一个宏参量,那...

TMDJoJo
2012/07/07
0
0
warning C4273: ****.dll链接不一致

方法1: 选择项目->属性->预处理器->预处理定义, 增加:HYCOMMONWINAPIEXPORTS 方法2: 就是在 #ifdef HYCOMMONWINAPIEXPORTS #define HYCOMMONWINAPIAPI declspec(dllexport) #else #define H......

我是一只鱼
2011/12/02
0
0
最优秀的9本开源 C 语言书籍

最优秀的9本开源 C 语言书籍 程序师2017-02-1484 阅读 C语言业界观察 书是非常私人的东西,编程类书籍也不例外。我们都是通过阅读书籍来帮助掌握某种语言的基本知识。随后又通过阅读书籍来全...

程序师
2017/02/14
0
0

没有更多内容

加载失败,请刷新页面

加载更多

构造函数

1:基本概念 提起构造函数,我们需要从JS的创建对象开始,JS的创建对象有两种方式,一种是对象字面量法(把一个对象的属性和方法一一罗列出来),对象字面量法有一个明显的不足在于它不适合批...

凌兮洛
13分钟前
0
0
防抖

export default function debounce(fn, wait) { var timeout; return function() { var ctx = this, args = arguments; clearTimeout(timeout); timeout = setTimeout(funct......

Js_Mei
13分钟前
0
0
RSS 阅读器及源

RSS 阅读器及源 万物皆可 RSS RSS源 1. go2think 2. cnblogs 3. justdojava 4. diyijc

近在咫尺远在天涯
18分钟前
2
0
一文了解金融行业服务治理

转载本文需注明出处:微信公众号EAWorld,违者必究。 引言: 微服务等新技术在解决系统敏捷性的同时,也带来了新的问题,众多的服务被识别出来后需要有效的管理起来,内部系统与外部系统通过...

EAWorld
22分钟前
0
0
Retrofit2应对各种奇葩接口的方法

最近这一年半,实在是大开眼界了,面对各种奇葩的接口,在紧凑的开发周期下,没有时间细想如何去面对,好在最近稍微清闲了,就把遇到的各种奇葩接口整理了一下,自己手写Spring去模拟这些接口...

猴亮屏
32分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部