C语言通用工具库的4个函数


C语言通用工具库的4个函数

通用工具库包含各种函数,包括随机数生成器、查找和排序函数、转换函数和内存管理函数。在ANSI-C标准中,这些函数的原型都在stdlib.h头文件中。附录B参考资料V列出了该系列的所有函数。现在,我们来进一步讨论其中的几个函数。

1 exit()和atexit()函数

在前面的章节中我们已经在程序示例中用过exit()函数。而且,在main()返回系统时将自动调用exit()函数。ANSI标准还新增了一些不错的功能,其中最重要的是可以指定在执行exit()时调用的特定函数。atexit()通过注册要在退出时调用的函数来提供这一特性,atexit()函数接受一个函数指针作为参数。程序byebye.c演示了它的用法。

Listing 16.16 The byebye.c Program

/* byebye.c -- atexit() example */
#include 
#include 
void sign_off(void);
void too_bad(void);

int main(void)
{
    int n;

    atexit(sign_off);    /* register the sign_off() function */
    puts("Enter an integer:");
    if (scanf("%d",&n) != 1)
    {
        puts("That's no integer!");
        atexit(too_bad); /* register the too_bad()  function */
        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!

如果在IDE中运行,可能看不到最后两行。下面是另一个运行示例:

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中运行,可能看不到最后4行。接下来,我们讨论atexit()和exit()的参数。

1.1 atexit()函数的用法

这个函数使用函数指针。要使用atexit()函数,只需把退出时要调用的函数地址传递给atexit()即可。函数名作为函数参数时相当于该函数的地址,所以该程序中把signoff或toobad作为参数。然后,atexit()注册函数列表中的函数,当调用exit()时就会执行这些函数。ANSI保证,在这个列表中至少可以放32个函数。最后调用exit()函数时,exit()会执行这些函数(执行顺序与列表中的函数顺序相反,即最后添加的函数最先执行)。 注意,输入失败时,会调用signoff()和toobad()函数;但是输入成功时只会调用signoff()。因为只有输入失败时,才会进入if语句中注册toobad()。另外还要注意,最先调用的是最后一个被注册的函数。 atexit()注册的函数(如signoff()和toobad())应该不带任何参数且返回类型为void。通常,这些函数会执行一些清理任务,例如更新监视程序的文件或重置环境变量。 注意,即使没有显式调用exit(),还是会调用signoff(),因为main()结束时会隐式调用exit()。

1.2 exit()函数的用法

exit()执行完atexit()指定的函数后,会完成一些清理工作:刷新所有输出流、关闭所有打开的流和关闭由标准I/O函数tmpfile()创建的临时文件。然后exit()把控制权返回主机环境,如果可能的话,向主机环境报告终止状态。通常,UNIX程序使用0表示成功终止,用非零值表示终止失败。UNIX返回的代码并不适用于所有的系统,所以ANSI-C为了可移植性的要求,定义了一个名为EXITFAILURE的宏表示终止失败。类似地,ANSIC还定义了EXITSUCCESS表示成功终止。不过,exit()函数也接受0表示成功终止。在ANSI C中,在非递归的main()中使用exit()函数等价于使用关键字return。尽管如此,在main()以外的函数中使用exit()也会终止整个程序。

2 qsort()函数

对较大型的数组而言,“快速排序”方法是最有效的排序算法之一。该算法由C.A.R.Hoare于1962年开发。它把数组不断分成更小的数组,直到变成单元素数组。首先,把数组分成两部分,一部分的值都小于另一部分的值。这个过程一直持续到数组完全排序好为止。 快速排序算法在C实现中的名称是qsort()。qsort()函数排序数组的数据对象,其原型如下:

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

第1个参数是指针,指向待排序数组的首元素。ANSI C允许把指向任何数据类型的指针强制转换成指向void的指针,因此,qsort()的第1个实际参数可以引用任何类型的数组。 第2个参数是待排序项的数量。函数原型把该值转换为sizet类型。前面提到过,sizet定义在标准头文件中,是sizeof运算符返回的整数类型。 由于qsort()把第1个参数转换为void指针,所以qsort()不知道数组中每个元素的大小。为此,函数原型用第3个参数补偿这一信息,显式指明待排序数组中每个元素的大小。例如,如果排序double类型的数组,那么第3个参数应该是sizeof(double)。 最后,qsort()还需要一个指向函数的指针,这个被指针指向的比较函数用于确定排序的顺序。该函数应接受两个参数:分别指向待比较两项的指针。如果第1项的值大于第2项,比较函数则返回正数;如果两项相同,则返回0;如果第1项的值小于第2项,则返回负数。qsort()根据给定的其他信息计算出两个指针的值,然后把它们传递给比较函数。qsort()原型中的第4个参数确定了比较函数的形式:

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

这表明qsort()最后一个参数是一个指向函数的指针,该函数返回int类型的值且接受两个指向const void的指针作为参数,这两个指针指向待比较项。 程序qsorter.c和后面的讨论解释了如何定义一个比较函数,以及如何使用qsort()。该程序创建了一个内含随机浮点值的数组,并排序了这个数组。

The qsorter.c Program

/* qsorter.c -- using qsort to sort groups of numbers */
#include 
#include 
​
#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');
}
​
/* sort by increasing value */
int mycomp(const void * p1, const void * p2)
{
    /* need to use pointers to double to access values   */
    const double * a1 = (const double *) p1;
    const double * a2 = (const double *) p2;
​
    if (*a1 < *a2)
        return -1;
    else if (*a1 == *a2)
        return 0;
    else
        return 1;
}
​

下面是该程序的运行示例:

Random list:
   0.0001    1.6475    2.4332    0.0693    0.7268    0.7383
  24.0357    0.1009   87.1828    5.7361    0.6079    0.6330
   1.6058    0.1406    0.5933    1.1943    5.5295    2.2426
   0.8364    2.7127    0.2514    0.9593    8.9635    0.7139
   0.6249    1.6044    0.8649    2.1577    0.5420   15.0123
   1.7931    1.6183    1.9973    2.9333   12.8512    1.3034
   0.3032    1.1406   18.7880    0.9887
​
Sorted list:
   0.0001    0.0693    0.1009    0.1406    0.2514    0.3032
   0.5420    0.5933    0.6079    0.6249    0.6330    0.7139
   0.7268    0.7383    0.8364    0.8649    0.9593    0.9887
   1.1406    1.1943    1.3034    1.6044    1.6058    1.6183
   1.6475    1.7931    1.9973    2.1577    2.2426    2.4332
   2.7127    2.9333    5.5295    5.7361    8.9635   12.8512
  15.0123   18.7880   24.0357   87.1828

接下来分析两点:qsort()的用法和mycomp()的定义。

2.1 qsort()的用法

qsort()函数排序数组的数据对象。该函数的ANSI原型如下:

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

第1个参数值指向待排序数组首元素的指针。在该程序中,实际参数是double类型的数组名vals,因此指针指向该数组的首元素。根据该函数的原型,参数vals会被强制转换成指向void的指针。由于ANSI C允许把指向任何数据类型的指针强制转换成指向void的指针,所以qsort()的第1个实际参数可以引用任何类型的数组。 第2个参数是待排序项的数量。在程序清单16.17中是NUM,即数组元素的数量。函数原型把该值转换为sizet类型。 第3个参数是数组中每个元素占用的空间大小,本例中为sizeof(double)。最后一个参数是mycomp,这里函数名即是函数的地址,该函数用于比较元素。

2.2 mycomp()的定义

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

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

这表明qsort()最后一个参数是一个指向函数的指针,该函数返回int类型的值且接受两个指向const void的指针作为参数。程序中mycomp()使用的就是这个原型:

int mycomp(const void * p1, const void * p2);

记住,函数名作为参数时即是指向该函数的指针。因此,mycomp与compar原型相匹配。 qsort()函数把两个待比较元素的地址传递给比较函数。在该程序中,把待比较的两个double类型值的地址赋给p1和p2。注意,qsort()的第1个参数引用整个数组,比较函数中的两个参数引用数组中的两个元素。这里存在一个问题。为了比较指针所指向的值,必须解引用指针。因为值是double类型,所以要把指针解引用为double类型的值。然而,qsort()要求指针指向void。要解决这个问题,必须在比较函数的内部声明两个类型正确的指针,并初始化它们分别指向作为参数传入的值:

/* sort by increasing value */
int mycomp(const void * p1, const void * p2)
{
    /* need to use pointers to double to access values   */
    const double * a1 = (const double *) p1;
    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*C和C++对待指向void的指针有所不同。在这两种语言中,都可以把任何类型的指针赋给void类型的指针。例如,程序清单16.17中,qsort()的函数调用中把double*指针赋给void*指针。但是,C++要求在把void*指针赋给任何类型的指针时必须进行强制类型转换。而C没有这样的要求。例如,程序清单16.17中的mycomp()函数,就使用了这样的强制类型转换:

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

如何调用qsort()?模仿程序清单16.17中qsort()的函数调用,应该是这样:

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

这里comp是比较函数的函数名。那么,应如何编写这个函数?假设要先按姓排序,如果同姓再按名排序,可以这样编写该函数:

#include 
int comp(const void * p1, const void * p2)   /* mandatory form */
{
    /* get right type of pointer */
    const struct names *ps1 = (const struct names *) p1;
    const struct names *ps2 = (const struct names *) p2;
    int res;

    res = strcmp(ps1->last, ps2->last);  /* compare last names */
    if (res != 0)
        return res;
    else       /* last names identical, so compare first names */
        return strcmp(ps1->first, ps2->first);
}

该函数使用strcmp()函数进行比较。strcmp()的返回值与比较函数的要求相匹配。注意,通过指针访问结构成员时必须使用->运算符。

展开阅读全文

页面更新:2024-05-19

标签:函数   时调   数组   原型   示例   指针   算法   顺序   元素   定义   参数   两个   类型   语言   工具

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top