C语言K&R圣经笔记 5.11函数指针

5.11 函数指针

在 C 语言中,函数本身不是变量,但我们可以定义指向函数的指针。函数指针可以被赋值,可以放到数组中,可以传递给函数,可以被函数返回...等等。为了说明这一点,我们将本章前面写的排序过程进行改写:如果给出了可选参数 -n ,它将不再按照字典序,而是按照数值大小对行进行排序。

排序通常包含三部分:用于确定两个对象顺序的比较操作,用于交换两个对象顺序的交换操作,以及进行比较和交换直到所有对象都有序的排序算法。排序算法独立于比较和交换操作,因此通过将不同的比较和交换函数传递给它,我们就可以根据不同的标准来排序。这就是我们在新排序中采取的方法。

两行之间的字典序比较与以前一样,是使用 strcmp;我们还需要一个例程 numcmp 来对两行做基于数值的比较,并返回与 strcmp 同类的条件指示。这些函数都在 main 之前声明,而指向恰当的函数的指针被传递给 qsort。我们省去了对参数的错误处理,是为了聚焦于主要问题。

#include 
#include 

#define MAXLINES 5000        /* 待排序的最大行数 */
char *lineptr[MAXLINES];     /* 指向文本行的指针 */

int readlines(char *lineptr[], int nlines);
void writelines(char *lineptr[], int nlines);

void qsort(void *lineptr[], int left, int right,
           int (*comp)(void *, void *));
int numcmp(char *, char *);

/* 对输入行排序 */
main(int argc, char *argv[])
{
    int nlines;        /* 读入的输入行数 */
    int numeric = 0;   /* 若按数值排序则为1 */

    if (argc > 1 && strcmp(argv[1], "-n") == 0)
        numeric = 1;
    if ((nlines = readlines(lineptr, MAXLINES)) >= 0) {
        qsort((void **)lineptr, 0, nlines -1,
                (int (*)(void*, void*))(numerric ? numcmp : strcmp));
        writelines(lineptr, nlines);
        return 0;
    }
    else {
        printf("input too big to sort\n");
        return 1;
    }
}

在对 qsort 的调用中,strcmp 和 numcmp 是函数的地址。既然知道他们是函数,取值操作符 & 就不需要了,正如在数组名称之前不需要 & 一样。

我们把 qosrt 写成可以处理任意数据类型,而不仅仅是字符串。正如 qsort 的函数原型所指示的,它期望接收一个指针数组、两个整数,以及一个有两个指针参数的函数。通用指针类型 void * 被用来做指针参数。任何指针都能被强制转换为 void * 再强制转换回来,而不会丢失信息,因此我们通过把参数强制转换为 void * 来调用 qsort。qsort 的函数参数(即第四个参数)做了一个复杂的强制类型转换,是用来强制转换比较函数的两个参数。这些强制类型转换通常对实际表现没有效果,但会向编译器保证,所有代码都是没问题的。【函数的声明类型和使用类型一致,编译器才不会告警】

/* qsort:将v[left]...v[right]按升序排列 */
void qsort(void *v[], int left, inr right,
           int (*comp)(void *, void *))
{
    int i, last;
    void swap(void *v[], int left, int right);

    if (left <= right)    /* 若数组元素少于两个,则什么都不用做  */
        return;
    swap(v, left, (left + right)/2);
    last = left;
    for (i = left+1; i <= right; i++)
        if ((*comp)(v[i], v[left]) < 0)
            swap(v, ++last, i);
    swap(v, left, last);
    qsort(v, left, last-1, comp);
    qsort(v, last+1, right, comp);
}

这些声明需要仔细研究。qsort 的第四个参数是

int (*comp)(void *, void *)

说明 comp 是一个函数指针,所指向的函数有两个 void * 参数。

下行中对 comp 的使用

if ((*comp)(v[i], v[left]) < 0)

与声明是一致的:comp 是函数的指针,而 *comp 是函数,而

(*comp)(v[i], v[left])

是对该函数的调用。括号是必须的,这样声明的各部分才能正确地关联在一起;若没有括号

int *comp(v[i], v[left])    /* 错误 */

则说明 comp 是一个返回 int 指针的函数,意思差得远了。

我们已经给出过用来比较两个字符串的 strcmp。这里给出 numcmp,以字符串首部的数值顺序来比较两个字符串,其数值是通过调用 atof 得到的【即字符串如果不是纯数字,则由开头的数字部分决定其数值,比如 123a的值是123。而a123的值是0。】

#include 

/* numcmp:按数值比较s1和s2 */
int numcmp(char *s1, char *s2)
{
    double v1, v2;

    v1 = atof(v1);
    v2 = atof(v2);
    if (v1 < v2)
        return -1;
    else if (v1 > v2)
        return 1;
    else
        return 0;
}

用来交换两个指针的 swap 函数,除了把声明改为 void * 之外,其他部分和我们本章之前给出的是一样的。

void swap(void *v[], int i, int j)
{
    void *temp;

    temp = v[i];
    v[i] = v[j];
    v[j] = temp;
}

很多其他选项可以加到排序程序中;有些实现起来很有挑战性,见课后练习。

练习5-14、修改排序程序以支持 -n 选项,表示按逆序(倒序)排列。保证 -r 和 -n 可以同时使用。

练习5-15、增加 -f 选项把大小写折叠(fold)在一起,使排序过程不区分大小写;例如 a 和 A 相等。

练习5-16、增加 -d (“目录顺序”)选项,只对字母、数字和空格排序。保证它能和 -f 一起使用。

练习5-17、增加域处理能力,根据行中的域(列)进行排序,每个域的排序选项是独立的。(本书索引就是如此:索引类别用 -df 选项,索引页码数用 -n 选项。)

你可能感兴趣的:(C语言,c语言,笔记,开发语言)