2008年1月26日星期六

C++基础知识:函数指针

本文总结了C++中函数指针的使用方法和使用场合


声明
函数指针在C语言中已经存在,顾名思义,这是一个指向函数的指针。其声明方式如下:

int (*fp)(int); // a pointer to function that takes an int and return an int

这是一个“指向带有一个int参数,返回int值的函数的指针”。这时候可以把为该指针这样赋值。

int foo(int);
fp = foo;
fp = &foo;
fp = 0;

可以用函数名直接赋值,也可以用显式的方式用地址符取函数地址,函数指针还可以赋值为0。这和其他指针是一样的。不过直接取地址的方式是没有必要的。编译器会帮你取地址。

调用
函数指针的调用有两种方式,显式(简化)调用和隐式调用。

int k = (*fp)(100);
k = fp(100);

这两种方式是一样的。但是后面一种显然更符合使用习惯,不过请记住,这只是一种简化

使用
最常见的使用方式是回调函数。回调函数的意思是把函数指针告诉别的程序,由别的程序决定何时调用。比如windows编程常用的subclassing方式

LONG NewWindowProc(HWND,UINT,WPARAM,LPARAM); //申明了一个用来回调的函数。
SetWindowLongPtr(hWnd,DWLP_DLGPROC,NewWindowProc); // 设置了回调函数

// 当有信息过来的时候,windows 会调用NewWindowProc。
LONG NewWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// message processing logic.
}

回调函数一般在“当你不知道何时调用函数,但是知道函数该执行什么样的任务,返回什么样的结果的时候用
再来一个例子,一个书店有两种方式付款,现金和信用卡,用户可以选择使用一种方式付款,为了让程序简便,可以使用函数指针:

extern bool PayByCash(double);
bool PayByCreditCard(double){...}

bool (*Pay)(double);

if( CustomerWantByCash )
Pay = PayByCash(amount); // amount is a double
else
Pay = PayByCreditCard(amount);

....// do some prepaid check and preparation
bool success = Pay();

如果想要把函数指针用在重载函数中,函数指针本身必须提供多个重载版本。解析规则和重载函数解析规则相同。比如:

bool PayByCash(double);
bool PayByCash();
bool (*Pay)(double);
Pay = PayByCash;
Pay(1000.0); // 调用的是第一个PayByCash; 因为类型一致
Pay(); // error!
还有一种常用的方式是把函数指针传递给另外一个函数,使得那个函数可以在它自己的函数体内调用我们传入的函数指针。比如设计一个sort函数,它接受三个参数,前面两个参数是两个相同类型的对象实例,最后一个参数需要一个函数来告诉它如何比较。我们这里不讨论具体实现,仅看看其声明方式:

void sort( T, T, fp);

这里的fp将是一个函数指针,指向一个函数接受sort的前面两个参数,然后返回一个int值,来表明两个T的逻辑上的大小。因此这样的函数可以声明为:

int compare( T , T )();

因此对应的函数指针为:

int (*fpc)(T,T)();

其类型为

int (*)(T,T)

因此sort函数的原型可以声明为:

void sort( T, T , int (*)(T,T) ) ;

使用typedef,提高可读性:

typedef int (*CH)(T,T);
void sort( T,T, CH );



提升难度
现在,我们来提升一下难度!假设我在首地址为0的地方有个子函数,我该如何去调用呢?让我们想一想!首先我们已经知道了函数指针的声明方式:

void (*fp)();

在C/C++中,知道了一个类型,那么它的类型转换符也就容易得到了。只要把声明中的变量和最后的分号去掉即可,再在外面用括号括起来,也就是:

(void (*)())

这就是一个指向没有返回值的函数的指针。
这时候,我们可以把0地址强制转换成函数指针了。也就是:

(void (*)())0 ---- (1)

这就是一个指针,把他看做void (*fp)()中的fp即可。想想我们如何调用fp指向的函数的?不就是fp()么?但是这里由于是强制转换的,因此不能用这种隐式方式,要使用显式调用方式,也就是(*fp)();
我们把上面的fp替换为开头的 (1)式,既得:

(*(void (*)())0)();

但是这种方式大家都会看着头痛,因此我们可以用typedef声明。

typedef void (*fp)()
(*(fp)0)();

没有评论: