• 指针的威力(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的功能是文件操作;

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

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


       
  • 相关阅读:
    vue学习第四天 ------ 临时笔记
    vue学习第三天 ------ 临时笔记
    vue学习第二天 ------ 临时笔记
    vue学习第一天 ------ 临时笔记
    vue ------ 安装和引入
    swagger-tools QuickStart
    build-your-microservices-api-with-swagger
    test-doubles-fakes-mocks-and-stubs
    swaggerhub 文档
    React Server Side Rendering
  • 原文地址:https://www.cnblogs.com/jiangheng/p/3484945.html
Copyright © 2020-2023  润新知