指针的威力(1)--奇妙的函数指针

<<The C programming language>>中指针的定义:
A pointer is a variable that contains the address of a variable.
一针见血的道出了指针的含义,指针也是变量,不过它保存的是其他变量的地址而已.至于这个其他变量是什么,
可能是一个普通build_in类型的变量,可能是一个用户自定义类型的变量,还有,还有...
但一条原则是不会变的:指针指向的实体要么确确实实在内存中存在,要么是NULL.

函数是变量吗?答案否定的.你见过函数被当作一个赋值运算的左值吗?但是函数确确实实是在内存中存在的,不然,我们
通常所说的函数调用是从哪儿调用的呢?函数调用显然是通过找到一个函数的"入口地址"然后在通过这个"入口地址"
进入函数的代码段,执行函数中的指令,然后返回...
当然这里涉及到更多的计算机系统结构方面的知识,这里不再深入.
所以:函数虽然不是变量,但可以定义指向函数的指针,这个指针保存的就是函数的"入口地址".这个"函数的指针"可以
像其他指针一样,被赋值,被放置到数组中,被传递给函数,从函数中返回.

下面就让我们来见证"函数指针"的威力吧:

1.简单示例:

#include <stdio.h>

void func(void)
{
     printf("Test function pointer!\n");
};

typedef void (*PF)(void);

int main(void)
{
    func();
    PF funcp = func;
    funcp();
    return 0;
}

这里使用了一个小技巧:
typedef void (*PF)(void);
在程序中凡是遇到PF,表示的都是一个指向 void (*)(void)类型函数的指针.

显然,上面程序中func() 和 funcp() 的输出结果是相同的.

2.进阶应用--将函数指针传递给函数:
将函数指针传递给另外一个函数,以实现一个在这个"另外函数"中实现对这个函数指针所指向
的函数的调用,这听起来是不是有些绕口?不就是一个函数调用另外一个函数吗?
确实,传递给函数的函数指针确实是用来函数调用的,但这里的函数调用却和普通的函数调用
存在着一些差别.废话少说,上代码:

下面是<<The C programming language>>中通过函数指针传递来实现的对一组记录进行排序的代码:

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

using namespace std;

#define MAXLINE 1000       /* 用户输入的每行最多有1000个字符 */
#define MAXLINES 5000      /* 用户最大的输入行数  */

char *lineptr[MAXLINES];   /* the pointers point to all lines */

int mygetline(char *line,int max);
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 *);
int mystrcmp(char *,char *);
void swap(void *v[],int i,int j);

/* sort the input lines */
int main(int argc,char *argv[])
{
    int nlines;
    int numeric = 0;  /* 1 when use numeric sort */

    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 *))(numeric ? numcmp : mystrcmp));
            writelines(lineptr,nlines);
    }
    else
        {
            printf("input too big to sort!\n");
        return 1;
    }
        return 0;
}

void qsort(void *v[],int left,int right,int (*cmp)(void *,void *))
{
        int i,last;
        void swap(void *v[],int ,int);

            if(left >= right)
           return;
        swap(v,left,(left+right)/2);
            last = left;

            for(i=left+1;i<=right;i++)
        if((*cmp)(v[i],v[left]) < 0)
            swap(v,++last,i);
                swap(v,left,last);
        qsort(v,left,last-1,cmp);
        qsort(v,last+1,right,cmp);
}

int readlines(char *lineptr[],int maxlines)
{
        int len,nlines;
        char *p,line[MAXLINE];
            nlines = 0;

            while((len = mygetline(line,MAXLINE)) > 0)
           if(nlines >= maxlines || (p=(char *)malloc(sizeof(char)*len)) == NULL)
               return -1;
           else
               {
               line[len-1] = '\0';
               strcpy(p,line);
               lineptr[nlines++] = p;
               return nlines;
           }
}

void writelines(char *lineptr[],int nlines)
{
        int i;
        for(i=0;i<nlines;i++)
            printf("%s\n",lineptr[i]);
}

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

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

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;
}

int mystrcmp(char *s1,char *s2)
{
     return strcmp(s1,s2);
}

int mygetline(char *line,int max)
{
    int c,i;

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

这段代码看起来有些冗长,初看确实是毫无头绪.但只要抓住一点:
这段代码通过函数指针实现了两种不同的排序策略:
     1.根据字母表的顺序排列
     2.根据数值顺序排列
这两种排序策略是怎么实现的呢?

请看qsort(快速排序)
void qsort(void *lineptr[],int left,int right,int (*comp)(void *,void *));
最后一个形参被声明为一个函数指针,main函数中对qsort调用的语句:
qsort((void **)lineptr,0,nlines-1,(int (*)(void *,void *))(numeric ? numcmp : mystrcmp));
当numeric==1时,将函数numcmp传递给qsort,并执行必要的类型转换,因为qsort只接受
int (*)(void *,void *)类型的参数,这是就实现了按数值顺序排序;
当numeric==0时,将函数mystrcmp传递给qsort,实现按字母表顺序的排序;

那么有人会问,这样做有何意义?还不如写两个不同的qsort,一个实现按字母表顺序排序,一个实现按数值顺序
排列.当然,当排序策略很少时,这样做也未尝不可.当有很多种排序策略时,我们难道还要为每一种排序策略
写一个排序函数吗?不要重复发明轮子,记住这一点.一个函数,多种策略.这是函数指针带来的优势.
看到这里,你会想到什么?是不是和C++中的泛型有些相似?模板函数和模板类,一个定义适用与不同的类型.
对,我们后面再讨论函数指针另外一些功用.

再来看一个函数指针的应用--链表的定义:

#include <stdlib.h>

/* Define a struct for linked list elements */
typedef struct ListElmt_
{
     void *data;
     struct ListElmt_ *next;
}ListElmt;

/* Define a structure for linked lists */
typedef struct List_
{
     int size;
     int (*match)(const void *key1,const void *key2);
     void (*destroy)(void *data);
     ListElmt *head;
     ListElmt *tail;
}List;

/* Public Interface */
void list_init(List *list,void (*destroy)(void *data));
void list_destroy(List *list);
int list_ins_next(List *list,ListElmt *element,const void *data);
int list_rem_next(List *list,ListElmt *element,void **data);

#define list_size(list) ((list)->size)

#define list_head(list) ((list)->head)
#define list_tail(list) ((list)->tail)
#define list_is_head(list,element) ((element) == (list)->head ? 1 : 0)
#define list_is_tail(list,element) ((element)->next == NULL ? 1 : 0)
#define list_data(element) ((element)->data)
#define list_next(element) ((element)->next)

上面这段代码是一个链表定义的头文件,注意表示链表的结构体中:
int (*match)(const void *key1,const void *key2);
void (*destroy)(void *data);
match指针指向一个int (*)(const void *,const void *)类型的函数,这个函数用来实现
链表中任意两个元素的比较,可以根据链表元素的类型来定义不同的比较函数.

比如说一个链表中的元素的类型是int型的,即ListElmt结构体中的void *data实际上指向的是一个int
类型变量的地址,那么我们就可以定义一个函数:
int intcmpare(const void *key1,const void *key2)
{
    if (*(const int *)key1 > *(const int*)key2)
            return 1;
    else if(*(const int *)key1 < *(const int*)key2)
        return -1;
    else
            return 0;
}

在定义链表时:
  List mylist;
  mylist.match = intcmpare;
这样mylist这个链表在对元素的比较时就可以实现intcmpare中定义的比较;

另外List结构中还有一个函数指针:destroy.
这个指针可以让我们自定义一个销毁链表元素的函数,是不是和C++类中的析构函数有些相似;
至于C++中类的实现策略和析构函数的实现方法,这不再本文的讨论范围.

3.真正见识函数指针的威力: 函数指针数组
所谓指针数组,就是数组元素是指针的数组.函数指针也是指针,那么当然就存在函数指针数组.
这样的数组中每个元素都指向一个特定的函数,可以将这个数组当成一个函数跳转表.当我们想
跳转到某个特定的函数,去执行某个特定的功能时,使用这个数组是不是特别方便.

试想一下,如果我们的程序的某一个模块下有一系列的功能函数,每个函数虽然功能不同,但接口相似.
我们就可以将这一系列函数的地址放入一个函数指针数组中,这样是不是大大方便了我们的管理呢?

下面请看一个实例,from :<<The C++ programming language>>
假设现在要实现一个编辑器软件,系统的一些功能可以通过函数指针数组来组织:
typedef void(*PF)();

//edit operations
PF edit_ops[] =
{
    &cut,&paste,&copy,&search
};

//file operations
PF file_ops[] =
{
    &open,&append,&close,&write
};

这样我们的软件上就可以有两个按扭:
PF *button1 = edit_ops;
PF *button2 = file_ops;
button1的功能是编辑操作,button2的功能是文件操作;

当然,函数指针的用处还很多,上面只是列举了一些常见的应用.

至于一些更加巧妙的应用, 以后慢慢道来.


   

posted on 2013-12-21 11:43  Dream Catcher(DC)  阅读(395)  评论(0编辑  收藏  举报

导航