函数指针的使用:
与分层设计有关。分层设计早就不是什么新的概念,分层的好处是众所周知的,比较明显好处就是简化复杂度、隔离变化。采用分层设计,每层都只需关心自己的东西,这减小了系统的复杂度,层与层之间的交互仅限于一个很窄的接口,只要接口不变,某一层的变化不会影响其它层,这隔离了变化。
分层的一般原则是,上层可以直接调用下层的函数,下层则不能直接调用上层的函数。这句话说来简单,在现实中,下层常常要反过来调用上层的函数。比如你在拷贝文件时,在界面层调用一个拷贝文件函数。界面层是上层,拷贝文件函数是下层,上层调用下层,理所当然。但是如果你想在拷贝文件时还要更新进度条,问题就来了。一方面,只有拷贝文件函数才知道拷贝的进度,但它不能去更新界面的进度条。另外一方面,界面知道如何去更新进度条,但它又不知道拷贝的进度。怎么办?常见的做法,就是界面设置一个回调函数给拷贝文件函数,拷贝文件函数在适当的时候调用这个回调函数来通知界面更新状态。
与抽象有关。抽象是面向对象中最重要的概念之一,也是面向对象威力强大之处。面向对象只是一种思想,大家都知道,用C语言一样可以实现面向对象的编程。这可不是为了赶时髦,而是一种实用的方法。如果你对此表示怀疑,可以去看看GTK+、linux kernel等开源代码。
接口是最高级的抽象。在linux kernel里面,接口的概念无处不在,像虚拟文件系统(VFS),它定义一个文件系统的接口,只要按照这种接口的规范,你可以自己开发一个文件系统挂上去。设备驱动程序更是如此,不同的设备驱动程序有自己一套不同的接口规范。在自己开发设备开发驱动程序时,只要遵循相应的接口规范就行了。接口在C语言中如何表示?很简单,就是一组函数指针。
与接口与实现分开有关。针对接口编程,而不是针对实现编程,此为《设计模式》的第一条设计准则。分开接口与实现的目标是要隔离变化。软件是变化的,如果不能把变化的东西隔离开来,导致牵一发而动全身,代价是巨大的。这是大家所不愿看到的。
C语言既然可以实现面向对象的编程,自然可以利用设计模式来分离接口与实现。像桥接模式、策略模式、状态模式、代理模式等等,在C语言中,无一不需要利用函数指针来实现。
与松耦合原则有关。面向过程与面向对象相比,之所以显得苍白无力,原因之一就是它不像面向对象一样,可以直观的把现实模型映射到计算机中。面向过程讲的是层层控制,而面向对象更强调的对象间的分工合作。现实世界中的对象处于层次关系的较少,处于对等关系的居多。也就是说,对象间的交互往往是双向的。这会加强对象间的耦合性。
耦合本身没有错,实际上耦合是必不可少的,没有耦合就没有协作,对象之间无法形成一个整体,什么事也做不了。关键在于耦合要恰当,在实现预定功能的前提下,耦合要尽可能的松散。这样,系统的一部分变化对其它部分的影响会很少。
函数指针是解耦对象关系的最佳利器。Signal(如boost的signal和glib中的signal)机制是一个典型的例子,一个对象自身的状态可能是在变化的(或者会触发一些事件),而其它对象关心它的变化。一旦该对象有变化发生,其它对象要执行相应的操作。
如果该对象直接去调用其它对象的函数,功能是完成了,但对象之间的耦合太紧了。如何把这种耦合降到最低呢,signal机制是很好的办法。它的原理大致如下:其它关注该对象变化的对象主动注册一个回调函数到该对象中。一旦该对象有变化发生,就调用这些回调函数通知其它对象。功能同样实现了,但它们之间的耦合度降低了。
1、定义函数指针类型
// 定义一个原型为int Fun( int a );的函数指针
typedef int (*PTRFUN) ( int aPara );
2、函数指针变量的定义
PTRFUN pFun; // pFun 为函数指针变量名
int (*pFun2) ( int a ); // pFun2也是函数指针变量名
3、函数指针作为函数的参数传递
// 定义回调函数
int CallBack( int a ){
return ++a;
}
// 定义回调者函数
void Caller( PTRFUN cb )
// void Caller( int (*cb) ( int ) ) // 也可这样申明
{
int nPara = 1;
int nRet = cb( nPara );
}
// 使用回调
void Test(){
Caller( CallBack ); // 直接使用回调函数
PTRFUN cb = CallBack; // int (*cb) ( int ); cb = CallBack;
int nRet1 = cb( 99 ); // nRet1 = 100;
}
4、函数指针的指针使用
// 定义函数指针的指针
typedef int (**PTRPTRFUN) ( int aPara );
// 函数指针的指针作为参数
void PtrCaller( PTRPTRFUN cb )
// void PtrCaller( PTRFUN* cb ) // 指针申明
// void PtrCaller( int (**cb) ( int ) ) // 原型申明
{
int nRet = (*cb)(999); // nRet = 1000;
}
// 使用函数指针的指针
void Test(){
PTRFUN cb = CallBack;
PtrCaller( &cb );
}
5、函数指针数组的使用
// 函数指针数组的定义
PTRFUN fArray[10];
// int (*fArray[10]) ( int ); // 原型定义
for ( int i = 0; i < 10; i++ ){
fArray[i] = CallBack;
int nRet = fArray[i](i); // nRet = i+1;
}
6、函数指针的大小
// 既然叫指针,所以跟普通的指针一样在32位系统下大小都为4
int nSz1 = sizeof(PTRFUN); // nSz1 = 4;
int nSz2 = sizeof(PTRPTRFUN); // nSz2 = 4;
注意:
编译器存在多种种调用规范,如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
来源: http://www.cnblogs.com/alon/archive/2009/04/24/1442696.html