变量的指针指向的是一块数据,指针指向不同的变量,则取到的是不同的数据。而经过编译后的函数都是一段代码,系统随即为相应的代码分配一段存储空间,而存储这段代码的起始地址(又称为入口地址)就是这个函数的指针,即跳转到某一个地址单元的代码处去执行。函数指针指向的是一段代码(即函数),指针指向不同的函数,则具有不同的行为。
因为函数名是一个常量地址,所以只要将函数的地址赋给函数指针即可调用相应的函数。如同数组名一样,我们用的是函数本身的名字,它会返回函数的地址。当一个函数名出现在表达式中时,编译器就会将其转换为一个指针,即类似于数组变量名的行为,隐式地取出了它的地址。即函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋值给指向函数的指针。既然函数指针的值可以改变,那么就可以使用同一个函数指针指向不同的函数。如果有以下定义:
int (*pf)(int); // pf函数指针的类型是什么?
C语言的发明者K&R是这样解释的,“因为*是前置运算符,它的优先级低于(),为了让连接正确地进行,有必要加上括号。”这未免有些牵强附会了,解释来解释去反而将人搞晕了。因为声明中的*、()、[]都不是运算符,而运算符的优先顺序在语法规则中是在其它地方定义的。其详解如下:
int (*pf)(int a); // pf是指向…的指针
int (*pf)(int a); // pf是指向…的函数(参数为int)的指针
int (*pf)(int a); // pf是指向返回int的函数(参数为int)的指针
即pf是一个指向返回int的函数的指针,它所指向的函数接受一个int类型的参数。 “int (*)(int)”类型名被解释为指向返回int函数(参数为int)的指针类型。如果在该定义前添加typedef,比如:
typedef int (*pf)(int a);
未添加typedef前,pf是一个函数指针变量;而添加typedef后,pf就变成了函数指针类型,习惯的写法是类型名pf大写为PF。比如:
typedef int (*PF)(int a);
与其它类型的声明不同,函数指针的声明要求使用typedef关键字。另外,函数指针的声明与函数原型的唯一不同是函数名用(*PF)代替了,“*”在此处表示“指向类型名为PF的函数”。显然,有了PF类型即可定义函数指针变量pf1、pf2。比如:
PF pf1, pf2;
虽然此声明等价于:
int (*pf1)(int a);
int (*pf2)(int a);
但这种写法更难理解。既然函数指针变量是一个变量,那么它的值就是可以改变的,因此可以使用同一个函数指针变量指向不同的函数。使用函数指针必须完成以下工作:
● 获取函数的地址,比如,pf = add,pf = sub;
● 声明一个函数指针,比如,“int (*pf)(int, int);”;
● 使用函数指针来调用函数,比如,pf(5, 8),(*pf)(5, 8)。为何pf与(*pf)等价呢?
● 一种说法是,由于pf是函数指针,假设pf指向add()函数,则*pf就是函数add,因此使用(*pf)()调用函数。虽然这种格式不好看,但它给出了强有力的提示——代码正在使用函数指针调用函数。
● 另一种说法是,由于函数名是指向函数的指针,那么指向函数的指针的行为应该与函数名相似,因此使用pf()调用函数。因为这种调用方式既简单又优雅,所以人们更愿意选择——说明人类追随美好感受的内心是无法抗拒的。
代码演示:
#include <iostream>
using namespace std;
//函数声明
double triangle_area(double &x,double &y);//三角形面积
double rectangle_area(double &x,double &y);//矩形面积
double swap_value(double &x,double &y);//交换值
double set_value(double &x,double &y);//设定长宽(高)
// double print_area(double &x,double &y);//输出面积
double print_area(double(*p)(double&,double&), double &x,double &y);//利用函数指针输出面积
//函数定义
double triangle_area(double &x,double &y)
{
cout<<"三角形的面积为:\t"<<x*y*0.5<<endl;
return 0.0;
}
double rectangle_area(double &x,double &y)
{
cout<<"矩形的面积为:\t"<<x*y<<endl;
return 0.0;
}
double swap_value(double &x,double &y)
{
double temp;
temp=x;
x=y;
y=temp;
return 0.0;
}
double print_area(double(*p)(double &x,double &y), double &x,double &y)
{
cout<<"执行函数前:\n";
cout<<"x="<<x<<" y="<<y<<endl;
//it is coming!...
p(x,y);
cout<<"函数指针传值后:\n";
cout<<"x="<<x<<" y="<<y<<endl;
return 0.0;
}
double set_value(double &x,double &y)
//注意参数一定要定义成引用,要不是传不出去哈!
{
cout<<"自定义长宽(高)为:\n";
cout<<"长为:";
cin>>x;
cout<<"宽或者高为:";
cin>>y;
return 0.0;
}
int main()
{
bool quit=false;//初始化退出的值为否
double a=2,b=3;//初始化两个参数a和b
char choice;
//声明的p为一个函数指针,它所指向的函数带有梁个double类型的参数并且返回double
double (*p)(double &,double &);
while(quit==false)
{
cout<<"退出(q); 设定长、宽或高(1); 三角形面积(2); 矩形面积(3); 交换长宽或高(4)."<<endl;
cin>>choice;
switch(choice)
{
case 'q':
quit=true;
break;
case '1':
p=set_value;
print_area(p,a,b);
break;
case '2':
p=triangle_area;
print_area(p,a,b);
break;
case '3':
p=rectangle_area;
print_area(p,a,b);
break;
case '4':
p=swap_value;
print_area(p,a,b);
break;
default:
cout<<"请按规矩出牌!"<<endl;
}
}
return 0;
}
在上例程中,我们采用了函数指针的方式,可以看到,在case语句中只需要制定每个函数指针所指向的函数是什么,那么在print函数中,我们就可以调用这个函数了。输出如下所示:
在该程序的第61行,我们就声明了一个函数指针。可以看到,在程序的case语句中,我们只需要把这个函数指针传递到print_area()函数里面就可以了。这样十分方便。而且我们可以看到,其实只要在print_area()中,即程序的第38行加上对这个函数指针的调用,我们就可以利用指针所指向的函数了。这十分方便。但是有几个小知识点应该注意一下:
- 声明函数指针时,其返回值,参数个数,参数类型应该与需要它指向的函数保持一致;否则,编译器会报错,无法从“***”转换到“***”;
- 利用函数指针只想某个函数的时候,我们只用,也只能给出该函数的函数名,不能把参数一并给出了。比如说在上例中,如果我们把程序的第83行改成:
p=swap_value(a,b);
那么编译器会报错:
func_pointer.cpp(83) : error C2440: “=”: 无法从“double”转换为“double (__cdecl *)(double &,double &)
这个错误的原因就是因为我们忘记了在文章一开头所讲的函数指针的一句话:函数名也是指向函数第一条指令的常量指针。因为函数指针就是指向其函数的地址的,那么我们就应该利用函数指针来指向函数名就可以了。