您的位置:首页 > 其它

指向函数的指针

2011-03-18 17:44 176 查看
指向函数的指针
假定我们被要求提供一个如下形式的排序函数:
sort( start, end, compare );
start 和end 是指向字符串数组中元素的指针。函数sort()对于start 和end 之间的数组元素进行排序。compare 定义了比较数组中两个字符串的比较操作。
该怎样实现compare 呢?我们或许想按字典顺序排序数组内的字符串,或许想按长度排序它们,以便将最短的字符串放在前面,而长的放在后面。解决这种需求的一种策略是将第三个参数compare 设为函数指针,并由它指定要使用的比较函数。
为简化sort()的用法而又不限制它的灵活性,我们可能希望指定一个缺省的比较函数,以用于大多数的情况。让我们假设最常见的以字典序排列字符串的情况,缺省实参将指定一个比较操作,它用到了字符串的compare()函数。我们将考虑怎样用函数指针来实现我们的sort()函数。

指向函数的指针的类型
怎样声明指向函数的指针呢?用函数指针作为实参的参数会是什么样呢?下面是函数lexicoCompare()的定义,它按字典序比较两个字符串:
#include <string>
int lexicoCompare( const string &s1, const string &s2 ) {
return s1.compare(s2);
}
如果字符串s1 和s2 中的所有字符都相等,则lexicoCompare()返回0;否则,如果第一个参数表示的字符串小于第二个参数表示的字符串,则返回一个负数;如果大于,则返回一个正数。
函数名不是其类型的一部分,函数的类型只由它的返回值和参数表决定。指向lexicoCompare()的指针必须指向与lexicoCompare()相同类型的函数(带有相同的返回类型和相同的参数表)。让我们试一下:
int *pf( const string &, const string & ); // 喔! 差一点
这几乎是正确的。问题是编译器把该语句解释成名为pf 的函数的声明,它有两个参数,并且返回一个int*型的指针。参数表是正确的,但是返回值不是我们所希望的。解引用操作符* 应与返回类型关联,所以在这种情况下,是与类型名int 关联,而不是pf。要想让解引用操作符与pf 关联,括号是必需的:
int (*pf)( const string &, const string & ); // ok: 正确
这个语句声明了pf 是一个指向函数的指针,该函数有两个参数和int 型的返回值。即指向函数的指针,它与lexicoCompare()的类型相同。下列函数与lexicoCompare()类型相同,都可以用pf 来指向:
int sizeCompare( const string &, const string & );
但是,calc()和gcd()与前面两个函数的类型不同,不能用Pf 来指:
int calc( int , int );
int gcd( int , int );
可以如下定义pfi,它能够指向这两个函数:
int (*pfi)( int, int );

初始化和赋值
我们知道,不带下标操作符的数组名会被解释成指向首元素的指针。当一个函数名没有被调用操作符修饰时,会被解释成指向该类型函数的指针。例如,表达式
lexicoCompare;
被解释成类型:
int (*)( const string &, const string & );
的指针。
将取地址操作符作用在函数名上也能产生指向该函数类型的指针。因此,lexicoCompare和&lexioCompare 类型相同。指向函数的指针可如下被初始化:
int (*pfi)( const string &, const string & ) = lexicoCompare;
int (*pfi2)( const string &, const string & ) = &lexicoCompare;
指向函数的指针可以如下被赋值:
pfi = lexicoCompare;
pfi2 = pfi;
只有当赋值操作符左边指针的参数表和返回类型与右边函数或指针的参数表和返回类型完全匹配时,初始化和赋值才是正确的。如果不匹配,则将产生编译错误消息。在指向函数类型的指针之间不存在隐式类型转换。例如:
int calc( int, int );
int (*pfi2s)( const string &, const string & ) = 0;
int (*pfi2i)( int, int ) = 0;
int main() {
pfi2i = calc; // ok
pfi2s = calc; // 错误: 类型不匹配
pfi2s = pfi2i; // 错误: 类型不匹配
return 0;
}
函数指针可以用0 来初始化或赋值,以表示该指针不指向任何函数。

调用
指向函数的指针可以被用来调用它所指向的函数。调用函数时,不需要解引用操作符。无论是用函数名直接调用函数,还是用指针间接调用函数,两者的写法是一样的。例如:
#include <iostream>
int min( int*, int );
int (*pf)( int*, int ) = min;
const int iaSize = 5;
int ia[ iaSize ] = { 7, 4, 9, 2, 5 };

int main() {
cout << "Direct call: min: "
<< min( ia, iaSize ) << endl;
cout << "Indirect call: min: "
<< pf( ia, iaSize ) << endl;
return 0;
}
int min( int* ia, int sz ) {
int minVal = ia[ 0 ];
for ( int ix = 1; ix < sz; ++ix )
if ( minVal > ia[ ix ] )
minVal = ia[ ix ];
return minVal;
}
调用
pf( ia, iaSize );
也可以用显式的指针符号写出:
(*pf)( ia, iaSize );
这两种形式产生相同的结果,但是第二种形式让读者更清楚该调用是通过函数指针执行的。
当然,如果函数指针的值为0,则两个调用都将导致运行时刻错误。只有已经被初始化或赋值的指针(引用到一个函数)才可以被安全地用来调用一个函数。

函数指针的数组
我们可以声明一个函数指针的数组。例如:
int (*testCases[10])();
将testCases 声明为一个拥有10 个元素的数组。每个元素都是一个指向函数的函数指针,该函数没有参数,返回类型为int。
像数组testCases 这样的声明非常难读,因为很难分析出函数类型与声明的哪部分相关。在这种情况下,使用typedef 名字可以使声明更为易读。例如:
// typedefs 使声明更易读
typedef int (*PFV)(); // 定义函数类型指针的typedef
PFV testCases[10];
testCases 的这个声明与前面的等价。
由testCases 的一个元素引用的函数调用如下:
const int size = 10;
PFV testCases[size];
int testResults[size];
void runtests() {
for ( int i = 0; i < size; ++i )
//调用一个数组元素
testResults[ i ] = testCases[ i ]();
}
函数指针的数组可以用一个初始化列表来初始化,该表中每个初始值都代表了一个与数组元素类型相同的函数。例如:
int lexicoCompare( const string &, const string & );
int sizeCompare( const string &, const string & );
typedef int ( *PFI2S )( const string &, const string & );
PFI2S compareFuncs[2] = {
lexicoCompare,
sizeCompare
};
我们也可以声明指向compareFuncs 的指针。这种指针的类型是“指向函数指针数组的指针”。声明如下:
PFI2S (*pfCompare)[2] = &compareFuncs;
声明可以分解为:
(*pfCompare)
解引用操作符* 把pfCompare 声明为指针,后面的[2]表示pfCompare 是指向两个元素数组的指针:
(*pfCompare)[2]
typedef PFI2S 表示数组元素的类型,它是指向函数的指针,该函数返回int,有两个const string&型的参数。数组元素的类型与表达式&lexicoCompare 的类型相同,也与compareFuncs的第一个元素的类型相同。此外,它还可以通过下列语句之一获得:
compareFuncs[ 0 ];
(*pfCompare)[ 0 ];
要通过pfCompare 调用lexicoCompare,程序员可用下列语句之一:
// 两个等价的调用
pfCompare[ 0 ]( string1, string2 ); // 编写
((*pfCompare)[ 0 ])( string1, string2 ); // 显式

参数和返回类型
现在我们回头看一下本节开始提出的问题,在那里给出的任务要求我们写一个排序函数,怎样用函数指针写这个函数呢?因为函数参数可以是函数指针,所以我们把表示所用比较操作的函数指针作为参数传递给排序函数:
int sort( string*, string*,
int (*)( const string &, const string & ) );
我们再次用typedef 名字使sort()的声明更易读:
// typedef 使 sort() 的声明更易读
typedef int ( *PFI2S )( const string &, const string & );
int sort( string*, string*, PFI2S );
因为在多数情况下使用的函数是lexicoCompare(),所以我们让它成为缺省的函数指针参数:
// 提供缺省参数作为第三个参数
int lexicoCompare( const string &, const string & );
int sort( string*, string*, PFI2S = lexicoCompare );
sort()函数的定义可能像这样:
void sort( string *s1, string *s2, PFI2S compare = lexicoCompare )
{
// 递归的停止条件
if ( s1 < s2 ) {
string elem = *s1;
string *low = s1;
string *high = s2 + 1;

for (;;) {
while ( compare( *++low, elem ) < 0 && low < s2) ;
while ( compare( elem, *--high ) < 0 && high > s1) ;
if ( low < high )
low->swap(*high);
else break;
} // end, for(;;)

s1->swap(*high);
sort( s1, high - 1, compare );
sort( high + 1, s2, compare );
} // end, if ( s1 < s2 )
}
sort()是C.A.R.Hoare 的快速排序算法的一个实现。让我们详细查看该函数的定义。该函数对s1 和s2 之间的数组元素进行排序。sort()是一个递归函数,它将自己逐步地应用在较小的子数组上。停止条件是当s1 指向与s2 相同的元素时或指向s2 所指元素之后的元素(第5 行)。
elem(第6 行)被称作分割元素。所有按字典序小于elem 的元素部会被移到elem 的左边,而所有大于的都被移到右边。现在,数组被分成若干个子数组,sort()被递归地应用在它们之上。
for(;;)循环的目的是完成分割。在循环的每次迭代中,low 首先被向前移动到第一个大于等于elem 的数组元素的索引上。类似地,high 一直被递减,直到移动到小于等于elem 的数组最右元素的索引上。如果low 不再小于high,则表示元素已经分隔完毕,循环结束。否则,这两个元素被交换,下一次迭代开始。虽然数组已经被分隔,但elem 仍然是数组的第一个元素。在sort()被应用到两个子数组之前,第19 行的swap()把elem 放到它在数组中最终正确的位置上。
数组元素的比较通过调用compare 指向的函数来完成。swap()字符串操作被调用,以便交换数组元素所指的字符串。
下面main()的实现用到了我们的排序函数:
#include <iostream>
#include <string>
// 这些通常应该在头文件中
int lexicoCompare( const string &, const string & );
int sizeCompare( const string &, const string & );
typedef int (*PFI)( const string &, const string & );
void sort( string *, string *, PFI=lexicoCompare );
string as[10] = { "a", "light", "drizzle", "was", "falling","when", "they", "left", "the", "museum" };
int main() {
// 调用 sort(), 使用缺省实参作比较操作
sort( as, as + sizeof(as)/sizeof(as[0]) - 1 );
// 显示排序之后的数组的结果
for ( int i = 0; i < sizeof(as)/sizeof(as[0]); ++i )
cout << as[ i ].c_str() << "/n/t";
}
编译并执行程序,生成下列输出:
"a"
"drizzle"
"falling"
"left"
"light"
"museum"
"the"
"they"
"was"
"when"
函数参数的类型不能是函数类型,函数类型的参数将被自动转换成该函数类型的指针。例如:
// typedef 表示一个函数类型
typedef int functype( const string &, const string & );
void sort( string *, string *, functype );
编译器把sort()当作已经声明为:
void sort( string *, string *, int (*)( const string &, const string & ) );
上面这两个sort()的声明是等价的。
注意,除了用作参数类型之外,函数指针也可以被用作函数返回值的类型。例如:
int (*ff( int ))( int*, int );
该声明将ff()声明为一个函数,它有一个int 型的参数,返回一个指向函数的指针,类型为:
int (*) ( int*, int );
同样,使用typedef 名字可以使声明更容易读懂。例如,下面的typedef PF 使得我们能更容易地分解出ff()的返回类型是函数指针:
// typedef 使声明更易读
typedef int (*PF)( int*, int );
PF ff( int );
函数不能声明返回一个函数类型。如果是,则产生编译错误。例如,函数ff()不能如下声明:
// typedef 表示一个函数类型
typedef int func( int*, int );
func ff( int ); // 错误: ff()的返同类型为函数类型

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/wuliming_sc/archive/2009/01/31/3855682.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: