从头学C(53)指向函数的指针

0

在 C 语言中,函数本身不是变量,但函数也是有地址的,可以定义指向函数的指针。这种类型的指针可以被赋值、存放在数组中、传递给函数以及作为函数的返回值等等。

第五章 指针与数组 >> 5.11 指向函数的指针

通过修改之前的 qsort 排序函数,来看下指向函数的指针的用法。在给定可选参数 -n 的情况下,程序将按照数值大小而非字典顺序对输入行进行排序。

排序的过程不外乎以下 3 个部分:

  1. 比较函数:判断任意两个对象之间的次序;
  2. 交换函数:颠倒两个对象的次序;
  3. 排序算法:保证所有对象都按正确次序排列;

排序算法和比较函数、交换函数、具体对象都无关,所以我们可以在排序算法中调用不同的比较和交换函数,实现不同的排序方式。

strcmp 函数是按字典顺序比较两个输入行,我们还需要一个按数值大小比较两个输入行,并返回和 strcmp 相同的比较结果的函数—— numcmp 函数。

最后,我们需要将指向对应比较函数的指针传递给 qsort 函数。

参考之前的代码,新的程序如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAXLINES 1000     /* max lines to be sorted */
char *lineptr[MAXLINES];  /* pointers to next lines */

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

void my_qsort(void *lineptr[], int left, int right, int(*comp)(void *, void *));

int numcmp(char *, char *); 

/* sort input lines */
int main(int argc, char *argv[])
{
        int nlines;     /* number of input lines read */
        int numeric = 0; /* 1 if numeric sort */

        if(argc > 1 && my_strcmp(argv[1], "-n") == 0)
                numeric = 1;
        if((nlines = readlines(lineptr, MAXLINES)) >= 0){ 
                printf("**********  before sort ********** \n");
                writelines(lineptr, nlines);
                printf("********************************** \n");

                my_qsort((void**) lineptr, 0, nlines -1, (int (*)(void*,void*))(numeric ? numcmp : my_strcmp));

                printf("**********  after  sort ********** \n");
                writelines(lineptr, nlines);
                printf("********************************** \n");
                return 0;
        } else {
                printf("input too big to sort\n");
                return 1;
        }
}

#define MAXLEN 1000     /* max length of any input line */

/* my_getline: read a line into s, return length */
int my_getline(char s[], int lim)
{
        int c, i = 0;

        while(--lim > 0 && (c=getchar()) != EOF && c != '\n')
                s[i++] = c;
        if(c == '\n')
                s[i++] = c;
        s[i]= '\0';
        return i;
}

#define ALLOCSIZE (MAXLEN * MAXLINES)
static char allocbuf[ALLOCSIZE];  /* storage for alloc */
static char *allocp = allocbuf;   /* next free position */
/* alloc: return pointer to n characters */
char *alloc(int n)      /* return pointer to n characters */
{
        if( allocbuf + ALLOCSIZE - allocp >= n) { /* it fits */
                allocp += n;
                return allocp - n; /* old p */
        } else
                return 0;
}

/* my_strcpy: copy t to s; pointer version 3 */
void my_strcpy(char *s, char *t)
{
        while(*s++ = *t++)
                ;
}

/* readlines: read input lines */
int readlines(char *lineptr[], int maxlines)
{
        int len, nlines;
        char *p, line[MAXLEN];

        nlines = 0;
        while((len = my_getline(line, MAXLEN)) > 0) {
                if(nlines >= maxlines || (p = alloc(len)) == NULL)
                        return -1;
                else {
                        line[len-1] = '\0';     /* delete the '\n' */
                        my_strcpy(p, line);
                        lineptr[nlines++] = p;
                }
        }
        return nlines;
}

/* writelines: write output lines */
void writelines(char *lineptr[], int nlines)
{
        while(nlines-- > 0)
                printf("%s\n", *lineptr++);
}

/* swap: interchange v[i]and v[j]*/
void swap(void *v[], int i, int j)
{
        void *temp;

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

/* numcmp: compare s1 and s2 numerically */
int numcmp(char *s1, char *s2)
{
        double v1, v2;

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

/* my_strcmp: retrun <0 if s<t, 0 if s==t, >0 if s>t*/
int my_strcmp(char *s, char *t)
{
        for( ; *s == *t; s++, t++)
                if(*s == '\0')
                        return 0;
        return *s - *t;
}

/* my_qsort: sort v[left]...v[right]into increasing order */
void my_qsort(void *v[], int left, int right, int (*comp)(void *, void *))
{
        int i, last;

        if(left >= right)       /* do nothing if array contains */
                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);
        my_qsort(v, left, last-1, comp);
        my_qsort(v, last+1, right, comp);
}

备注:由于 qsort 函数与 stdlib.h 中的标准库函数 qsort 冲突,因此这里改成了 my_qsort 函数名。

在程序第 28 行调用 qsort 函数的语句中,strcmp 和 numcmp 是函数地址,所以不需要在他们前面加上取地址运算符 “&” (PS:数组名前面也不需要加 & 是同样的道理)。

程序第 11 行是 qsort 函数的原型,从声明中我们可以看到它的参数列表包含:一个指针数组、两个整型数、一个带有两个指针参数的函数。其中指针数组参数的类型是通用指针类型 void *。由于任何类型的的指针都可以转换为 void * 类型,并且在将它转换回原来的类型时不会丢失信息,因此调用 qsort 函数时可以将参数强制转换为 void * 类型。

仔细看下 qsort 函数的第四个参数声明:

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

程序第 145 行使用如下语句调用 comp 函数:

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

comp 的使用和其声明是一致的,comp 是一个指向函数的指针, *comp 代表一个函数,下列语句是对该函数进行调用:

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

其中的圆括号不能省略,如果省略了圆括号,则变成了:

int *comp(void *, void *)    /* 错误 */

这里表明 comp 是一个函数,该函数返回一个指向 int 类型的指针,这显然不是我们原本设计该函数的初衷。

程序第 112 行的 numcmp 函数中,通过调用 atof 计算字符串对应的数值,再进行数值大小的比较。

程序第 102 行的 swap 函数和之前所编写的 swap 函数有个不一样的地方是,它的参数声明为 void * 类型,保持与 qsort 函数中的参数类型一致。

Leave A Reply