指针的威力(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,©,&search
};
//file operations
PF file_ops[] =
{
&open,&append,&close,&write
};
这样我们的软件上就可以有两个按扭:
PF *button1 = edit_ops;
PF *button2 = file_ops;
button1的功能是编辑操作,button2的功能是文件操作;
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,©,&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) 编辑 收藏 举报