简介
函数指针是指向函数的指针变量。 因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。
函数指针的应用最多的是转移表(jump table)和回调函数(callback function)
使用转移表可以替代冗长的switch和if-else语句,分离了具体操作和选择代码,是一种良好的设计方案。和其他指针一样,对函数指针执行间接访问之前,必须把它初始化并指向某一个函数。
详解
下面举一个例子说明什么是转移表:
Q:编写一个程序,从标准输入读取一串字符,根据中函数定义的字符分类,计算各类字符所占的百分比。不能使用一系列If语句。
Solution:
事实上转移表就是一个函数指针数组,声明并初始化一个数组
确保函数原型出现在这个数组声明之前,在使用转移表这个指针数组时特别要注意下标引用不要越界,否则会引起不可预知的后果。
函数在被使用时总是由编译器把它转换为函数指针。
接着我们就能用这个转移表来替换switch和if了。
int isnotprint(int c)
{
if(isprint(c))
return FALSE;
return TRUE;
}
/*jump table of chartest,each function return none-zero or FALSE*/
int (*chartest[])( int )={
iscntrl,
isspace,
isdigit,
islower,
isupper,
ispunct,
isnotprint
};
完整的代码:
#include<>
#include<>
#include<>
#define TRUE 1
#define FALSE 0
#define MAX_LINE_LENTH 1024 /*define the max buffer*/
#define ENUMVERSION 0
int isnotprint(int c)
{
if(isprint(c))
return FALSE;
return TRUE;
}
/*jump table of chartest,each function return none-zero or FALSE*/
int (*chartest[])( int )={
iscntrl,
isspace,
isdigit,
islower,
isupper,
ispunct,
isnotprint
};
#define N_CATEGORIES\
(sizeof(chartest)/sizeof(chartest[0]))
/*使用这种宏,能使代码段更具可拓展性*/
#if ENUMVERSION
typedef enum{
cntrl,space,digit,lower,upper,punct,print
}charType;
#endif /*ENUM VERSION ONLY*/
/*printing label*/
char* label[]={
" control",
" space",
" digit",
" lower letter",
" upper letter",
" punct letter",
" unprinted letter"
};
int main()
{
char buffer[MAX_LINE_LENTH],*str;//声明缓冲区
float num[7]={0,0,0,0,0,0,0};//用于记录各种字符数量的数组
float sum=0;//字符总数
int i;
printf("Please input the string:\n");
str=fgets(buffer,MAX_LINE_LENTH,stdin);//从标准输入读取一串字符
if(str==NULL)
{
exit(1);//如果失败
}
/*判断字符种类,使用转移表和循环代替switch和if*/
while( (*str++)!='\0' )
{
++sum;
for(i=0;i<N_CATEGORIES;i++)
{
if(FALSE!=chartest[i](*str))
++num[i];
}
}
printf("the total character number:%3f\n",sum);
for(i=0;i<N_CATEGORIES;i++)
{
printf("the%s count:%3f %3f%%\n",label[i],num[i],100*num[i]/sum);
}
return 0;
}
技巧
在这里使用了一个宏:
#define N_CATEGORIES\
(sizeof(chartest)/sizeof(chartest[0]))
使用这样一个宏计算转移表的长度,在使用转移表的时候,这个宏可以作为循环条件,一定程度上,改善了代码的易扩展性
在从标准输入输入一串字符串的时候,用的是fgets而不是gets,原因是gets函数并没有指定缓冲区长度,这个函数只能用于玩具程序,使用fgets更为安全。