关于函数参数个数不定的详解见连接:
http://www.cnblogs.com/VRS_technology/archive/2010/05/10/1732006.html
开篇之谈
有人曾说:正是指针使得C语言威力无穷!的确,提及指针这个充满玄机的东西,很容易令人想到很多很多。指针是C语言中一个重要的数据类型,指针和数组等复合类型的相关性更增加了其神秘色彩。这就注定了学习C语言的路上布满荆棘。
每个指针都有一个与之相关的类型,不同数据类型的指针之间的区别不是在指针的表示上,也不是在指针所持有的值(地址)上,对所有类型的指针这两方面都是相同的,不过函数指针略有区别!函数指针是一类特殊的指针,它指向函数的首地址。以下仅仅是我个人在学习过程中对函数指针的认识和体会:
“和函数指针的n次亲密接触”一节涵盖了函数指针的重要理论知识;
“众里寻她千百度”说明了函数指针在UNIX操作系统层面上的“公开”应用;
“他乡遇故知”体现了我在开发平台上发现了函数指针的应用之后的喜悦心情;
“莫愁前路无知己”从开发应用角度说明了现实生活中对函数指针的应用已经“漫山遍野”,再次说明函数指针在设计开发中的重要作用。
当然,这些只是我个人的一点看法,对于其中的谬误希望读者能够给我提出宝贵的修正意见。
和函数指针的n次“亲密接触”
和函数指针的第一次接触是大学课堂上听教师提及,当时听的很是模糊,经过课下查询资料后发现了各种资料上对函数指针也没有详细的论述,大都提供如下简要说明:
1) 函数指针的定义形式:函数的返回值类型 (*函数指针名称)(参数列表);
2) 函数指针的赋值:函数指针名称 = 函数名称;
3) 函数指针调用函数方法:只需将函数名用(*函数指针名)来代替即可,例如(*p)(a,b);其中p为函数指针,a和b是实际参数。
以上是大多资料提供的有关函数指针的论述,但是就我个人认为,单纯从这几个方面不足以说出函数指针的真正内涵。下面是我经过多次查阅资料进行的总结。
合理转换,去伪存真
首先函数指针也是指针,同样表示内存中的地址,但其声明形式(见上)却不好理解,尤其是定义函数指针数组的时候:
int (*funp) ( int, int ); /* 声明了一个函数指针 */
int (*funparray[ 10 ] )( int , int ); /* 声明了一个函数指针数组 */
以上的声明形式令人费解,可以通过如下形式进行转换:
typedef 函数的返回值类型 (*函数指针类型名)(参数列表);
例如:
typedef int (*FUNP)( int, int );
声明了一个指向返回值为整型且带有两个整型参数的函数指针类型;这样就可以像C语言提供的基本数据类型那样使用“函数指针类型”了,经过转换上面的形式可以写作:
FUNP funcaion_pointer; /* 声明了一个函数指针 */
FUNP fun_array[ 10 ]; /* 声明了一个函数指针数组 */
追根溯源,把握真谛
前面曾经提及,指针都和类型相关,那么函数指针和哪个类型相关呢?为了弄清楚这个问题,我们需要先要搞清函数和类型之间的关系。
我们知道:整型指针是指向整数类型的指针;浮点型指针是指向浮点类型的指针;函数指针是指向函数的指针;而整型,浮点型都是C语言提供的基本数据类型,若从这个角度看,函数似乎也变成了一个不折不扣的“类型”了!那么,函数究竟是一种什么样的“类型”呢?我们可以先考虑这样一个问题:不同的函数如何区分?或者说函数的“属性”有几个?如何把函数进行归类呢?这要从函数的原型说起,函数的原型由函数的返回值类型,函数名称以及函数参数类型列表来表示(当然,函数定义还要加上函数体)。函数名称本身不足为奇,因为叫张三或者叫李四都不能影响到函数的属性,所以函数也可以说成是一种特殊的“类型”,它可以通过返回值类型和参数类型列表两个属性来表示。如下:
double d_max( double, double );
int i_max( int, int );
上面的两个函数d_max和i_max属于不同 “类型”的函数。函数类型之间的差别导致函数指针的类型也会有所不同,如下:
typedef double (*DFUNPD) (double); /* 返回值为double,含有一个double参数*/
typedef double (*DFUNPN) (); /* 返回值为double,但不含参数 */
typedef char (*CFUNPC)(char); /* 返回值为char,并且含有一个char参数 */
typedef char (*CFUNPN)(); /* 返回值为char,但不含参数 */
上面所列举的四个虽然都是函数指针,但却存在差异,因为其各自所指向的函数的“类型”有所不同。
从这点看来,函数指针对类型的要求似乎显得更加细腻,它不同于普通数据类型的指针,因为不能笼统地说它是指向“函数类型”的指针,而应该说它是“指向特定函数类型”的指针。即:一个普通数据类型的指针可以指向相同类型的任何一个变量的地址,而一个函数指针却不可以指向任意一个函数的地址,如:
long l_var, l_tmp, *l_p;
l_p = &l_var;
l_p = &l_tmp;
都是合法的,而
CFUNPN funp = d_max;
却会被编译器拦住,并且给出一个警告!当然你可以通过强制类型转换来绕过它!
声东先击西,一语破天机
如前所述,利用函数指针间接调用函数的方法如下:
typedef int (*FUNP) (int, int );
int max( int a, int b )
{
return( (a)>(b)?(a)(b));
}
FUNP funp=max; /* 对函数指针的赋值 */
funp( a, b ); /* 利用函数指针调用函数 */
从上面的程序片段中我们可以看出两个不寻常的东西:
1) 对函数指针的赋值:既然funp指向函数max,那么为什么不像普通类型的指针那样赋值呢?对于普通数据类型指针,需要要把变量的地址赋值给指针,如下:
int a;
int *ap = &a;
但给函数指针的赋值怎么就变得如此“直截了当”?
2) 利用函数指针调用其指向的函数:这和利用普通类型指针取其变量内容的形式也不大一致,对普通类型指针需要用取内容操作符取其指向变量的内容,但是利用函数指针调用函数却在“偷懒”:
funp( a, b );
当然也可以很正规的写成这样:
(*funp)( a, b );
难道函数指针等同于函数指针的内容?即(*funp) == funp?
回答是肯定的!
若上面这个等式成立,那么将这个等式根据“指针内容”的理论稍加转换就可以转变成如下的等式:funp == &funp;这个东西看起来倒似曾相识:
char str[ 10 ];
str == &str;
在说明原由之前,我们有必要先统一一下术语:
1) 对数组:[ ]为下标标识符,我们可以str[ 1 ];
2) 对函数:( )为调用操作符,我们可以max( a, b );
好了,术语统一了,言归正传吧!对数组而言,因为不带下标标识符的数组名会被解释成指向该数组首元素的指针:即满足 char str[ 3 ]; str == &(str[ 0 ]);同样,当函数名称没有被调用操作符修饰时,会被解释成指向“该种类型”函数的指针,即满足:int max( int, int );max; 会被解释成 int (*)( int, int ) 类型(即返回值为int并且含有两个int型参数)的指针。和数组名的解释类似,取地址操作符作用在函数名上同样能产生指向该类函数的指针,于是有:max=&max;或者funp =&max;怎么样?上面的疑云就要随之散去了吧!
现在,让我们临时总结一下函数指针赋值和通过指针对函数的调用方法吧:
1) funp = &max;
(*funp)( a, b );
2) funp = max;
funp( a, b );
若你是个循规蹈矩,思想又有些保守的人,我猜想你会按照上述的方法1)来使用,因为你为了和普通数据类型的指针保持一致;若你是个个性十足,又有些张狂的人,方法2)则是你的选择。
为了说明函数指针的赋值和利用函数指针调用函数引到众所周知的数组和指针的解释,称之为“声东先击西”;同样和数组类似,函数指针的实质是函数指针是指向函数代码段的首地址的指针,此乃“一语破天机”也!
反客为主,小议回调
众所周知,操作系统给我们提供了好多系统调用,以简化我们的程序设计。从这个意义上来说,我们是受益者,但是操作系统同样为我们提供了展示“个性”的机会,它有时需要调用我们的函数,即所谓的“回调函数”:就是我们提供一个函数,通过一定的方法使操作系统“认识”到这个函数,必要时由操作系统来调用它。那么如何才能让操作系统认识我们的函数呢?常见的实现方法是使用函数指针作为参数传递给操作系统。在WINDOWS编程中,这种方法用的很广,因为WINDOWS程序的灵魂是“以消息为基础,以事件驱动之”。“鼠标的按下和弹起”都会被操作系统捕捉,然后发送消息到应用程序,应用程序根据用户定义的事件加以响应。当然消息的绑定和发送都不用我们担心,开发环境或者编程应用框架(在WINDDOWS编程中典型的是MFC)都为我们做好了从消息到消息函数的映射。在MFC中存在“消息映射表”,其基本雏形如下:
struct MSGMAP_ENTRY{
UINT nMessAge;
LONG (*pfn)( HWND, UINT, WPARAM, LPARAM );
};
struct MSGMAP_ENTRY _messageEntryes[] =
{
WM_CREATE OnCreate;
WM_PAINT OnPaint;
WM_SIZE OnSize;
….
/* 这是消息 这是消息处理函数 */
}
上面的代码是一些好心人煞费苦心从MFC程序中挖掘出来并加以模仿的,我在大学时曾经读到过。虽然没有必要考究其真伪,不过从中我们可以看到函数指针的应用。我由衷地感谢那些好心人,要不是他们我们没有办法获取藏于系统内部的代码,从而看到函数指针在系统中的广泛应用,但代码没有公开就不足以成为函数指针在系统应用的理论依据。
众里寻她千百度――函数指针与信号
前面曾经提到函数指针在WINDOWS编程中应用极为广泛,但是多被开发环境(IDE)所掩盖,我曾经在WINDOWS程序中苦苦寻觅回调函数的公开应用,但未果。而工作后接触UNIX操作系统后发现,这个被称为“旧时王榭堂前燕”的操作系统人性化了许多。在UNIX操作系统中,信号是一个软件形式的异常,就是我们常说的软件中断。一个信号就是一条消息,它通知进程某种类型的事件已经在系统中发生了。信号提供了一种异步事件的处理方法,所以很多比较重要的应用程序都需处理信号。其实信号的应用不仅仅在操作系统中,早在我国若干年前就有,要不怎么会有“烽火戏诸侯”的典故呢?
UNIX信号机制最简单的界面就是signal函数:
#include <signal.h>
void (*signal(int sig, void (*func)(int))) (int);
先不必理会这个函数参数的具体意义,从表面上一看这个函数,给人的感觉很不适应。不过也无妨,用前面提到的方法进行转换一下吧:
typedef void Sigfunc ( int );
然后将signal函数转换成如下的形式:
Sigfunc *signal( int signo, Sigfunc *func );
经过转换之后,本函数的原型就清晰的展现在我们面前:这是一个函数,其返回值是一个函数指针,参数列表由一个整型(信号名称)和一个函数指针构成,其中参数func的值可以是:
1) 常数SIG_IGN 表示忽略这个信号;
2) 常数SIG_DFL表示接收到此函数采用系统默认的动作;
3) 用户编写自己的信号处理函数来捕捉这个信号;
函数的返回值则是指向以前的信号处理函数的指针。
至于此函数的具体应用及其缺陷,可以参阅相关资料,不过由此我们发现函数指针不仅仅作为参数,同时也作为函数的返回值两次出现在signal函数中,函数指针在信号机制中的广泛应用体现了函数指针在操作系统中的重要地位。
他乡遇故知――函数指针在CoreBanking中的应用
如今工作已经两年有余,伴随着工作中的学习和积累,对公司的开放式金融平台(OFP)的理解也日益加深。CoreBanking作为OFP的重要组成部分,在构建核心服务时给我们提供了极大的便利性。所谓核心服务在系统里都是以Bea Tuxedo应用服务进程来体现,即我们常说的AP SERVER,每个服务完成了多种功能,或者说是多个(当然也可以是一个)交易的集合。而每支交易都有很多共同的处理逻辑,例如交易的打包和解包,各种合法性检查、组织公共信息等,区别在于不同的交易有各自的应用逻辑。可是,CoreBanking是如何将这些交易的公共处理逻辑和具体应用整合起来的呢?
让我们看一下代码吧,每个服务目录下边有一个称为主控的文件和一些具体的交易文件,从表面来看它们好像没有什么关联。在$HOME/def/下我们会看到好多以SERVER名称来命名的头文件,下面是某个头文件的片段:
…
#ifndef EXTERN
long (*AtomFunc[])()={
0,
HandAcc, /*No:0001 Name:手工会计事项 */
FHandin, /*No:0002 Name:缴款 */
CshRegReg, /*No:0003 Name:出纳现金登记簿登记 */
…
};
从上面的头文件片段中我们可以看到一个函数指针数组,而此数组中的每个成员正是SERVER服务下的源文件中的函数,也就是一个交易的应用逻辑部分。所有的公共处理逻辑都放在主控程序中。每个交易都有一个原子交易码,交易处理就是由原子交易码在函数指针数组中的位置来决定。在服务的响应过程中,主控就是根据输入的交易码,找到对应的原子交易码,再从函数指针数组中选择相应的函数进行调用来完成相应交易的处理。SYSMNG正是利用这个函数指针数组使得那些从表面看来没有关联的源文件有机结合在一起。若将一个服务展开,就变成了这个样子:
上图中的每个处理(含公共处理和交易处理)都是任何一个交易必要的组成部分:交易上来后,首先进入主控程序,主控将所有的公共处理完成后,根据交易码选择不同的原子交易来执行,原子交易执行完毕后将执行权重新交回主控。
CoreBanking中的AtomFunc和MFC中messageEntryes似乎有些不谋而合了,以前在WINDOWS编程中见过的东西,又再次出现,看起来自然亲切了许多!
莫愁前路无知己
函数指针是美丽的,它为我们提供了一种构造程序的方法:根据“实际情况”做出相应的“反应”!不管是WINDOWNS程序中的消息MAP还是UNIX系统中的信号机制,都充分体现了这种设计思想。在我们的程序中也是如此,有许多程序中都很好的利用了函数指针。如在调整交易中就有利用函数指针根据不同的交易码调用不同的调整函数以满足不同的调整逻辑。
结合前面所述,现提供一个小程序来说明这种设计思想的灵活性:
int add( int a, int b ) {
return( a + b );
}
int sub( int a, int b ) {
return( a - b );
}
int mul( int a, int b ) {
return( a * b );
}
int dev( int a, int b ) {
return( a / b );
}
enum OPER { ADD, SUB, MUL, DEV };
typedef int (*OPERFUNP)( int, int );
OPERFUNP FunArray[] = { add, sub, mul,dev };
int oper( int a, int b, enum OPER oper )
{
return( FunArray[ oper ]( a, b ) );
}
怎么样?看到这个小小的程序,或悲,或喜?皆不足道,由它去吧!■
http://hi.baidu.com/zll2117/blog/item/6a5550f2f807d3c40b46e05d.html