我们都使用过数组,都知道数组名就是指向数组第一个元素的常量指针。所以同理可知道,对于一个函数而言,
函数名也是指向函数第一条指令的常量指针。而编译器需要做的就是在程序编译之后,为每个函数分配一个首地址,即函数第一条指令的地址。一般情况下,我们可以用一个指针来保存这个地址,而这个指针就是函数指针,该指针可以看作是它指向函数的别名,所以我们可以用该指针来调用这个函数。
(1)函数指针的声明方法
int (*fun)(int &,int &);
上面语句声明了一个指针fun,它指向了一个函数,这个函数带有2个int类型参数参数并返回一个int类型的值。
(2)函数指针使用注意事项
①一个指向函数的指针必须确保该函数被定义且分配了内存,否则它将指向一个空的地址,这是使用时一定要注意的。
②特别注意第一个括号的位置,如果我不写括号,如:
int *fun(int &,int &);
这就不是一个指向函数的指针,而是声明了一个函数,该函数返回一个int类型的指针,即我们也常用到的
指针函数,就是返回一个地址给调用者,用于需要返回地址的情况。
(3)函数指针使用示例
我们以这样一个程序为例:该程序的功能是计算两个数的和或差,其中,两个需要相加的数由用户自己输入。
①普通函数示例
-
#include <iostream>
-
-
using namespace std;
-
-
//function declaration
-
int do_input(int &a,int &b);//set two numbers value from inputting
-
int do_print_sum(int &a,int &b);//print the sum of two numbers
-
int do_print_difference(int &a,int &b);//print the difference of two numbers
-
-
//function definition
-
int do_input(int &a,int &b)
-
{
-
cout<<"please input two int numbers:\n";
-
cin>>a>>b;
-
return 0;
-
}
-
-
int do_print_sum(int &a,int &b)
-
{
-
cout<<"sum = a + b:\n";
-
cout<<a<<" + "<<b<<" = "<<(a + b)<<endl;
-
return 0;
-
}
-
int do_print_difference(int &a,int &b)
-
{
-
cout<<"sum = a - b:\n";
-
cout<<a<<" - "<<b<<" = "<<(a - b)<<endl;
-
return 0;
-
}
-
-
int main(int argc, const char *argv[])
-
{
-
bool quit = false;//init the value of quit
-
int a = 5,b = 10;
-
char choice;
-
while(false == quit)
-
{
-
cout<<"quit(q);sum(s);difference(d)."<<endl;
-
cin>>choice;
-
switch(choice)
-
{
-
case 'q':
-
quit = true;
-
break;
-
case 's':
-
do_input(a,b);
-
do_print_sum(a,b);
-
break;
-
case 'd':
-
do_input(a,b);
-
do_print_difference(a,b);
-
break;
-
default:
-
break;
-
}
-
}
-
return 0;
-
}
上述例子中,使用普通函数的方法,运行和输出的结果为:
下面,我们来看看如果采用函数指针,效果是怎么样?我们在前面分析过,函数指针就是一个指向函数的指针。那么我们在调用函数的时候,就可以运用指针来调用这个函数。而且,众所周知,指针可以作为一个函数的参数,那么函数指针也不例外。好了,下面讲一下函数的指针作为函数参数来调用。对于一个普通要调用指针的函数来说,声明应该是这样的:
int fun (int *,int,int);
那么由上面的语句可以看出,这个函数fun的第一个参数就是一个指向int类型的指针,而后面的两个参数就是两个类型为int形式的参数。根据博文一开始所列的函数参数的声明格式,那么函数指针作为函数参数的一般形式就是:
int fun (int (*p)(int &,int &),int &,int &);
该函数fun有3个参数,第一个参数为
int (*p)(int &,int &),这就是一个函数指针,它指向一个带有两个int类型的参数并且返回int类型值的函数,另外两个参数都是int类型的引用。
这样一来,我们就可以利用函数指针把函数作为另一个函数的参数调入到那个函数中使用了。对于上面的这个例子,我故意把do_input函数声明为带两个int类型的变量,且返回值也为int。这样做是为了让后面我们在利用函数指针调用的时候方便一些。因为我们只需要将函数指针声明成与之相匹配的函数就行。从上面这个例子看出,它麻烦在每次输出的时候都需要在case语句中进行操作,那么我们能利用函数指针一并简化到print函数中,如:
②指针函数示例
-
#include <iostream>
-
-
using namespace std;
-
-
//function declaration
-
int do_input(int &a,int &b);//set two numbers value from inputting
-
int do_print_sum(int &a,int &b);//print the sum of two numbers
-
int do_print_difference(int &a,int &b);//print the difference of two numbers
-
-
//function definition
-
int do_input(int &a,int &b)
-
{
-
cout<<"please input two int numbers:\n";
-
cin>>a>>b;
-
return 0;
-
}
-
-
int do_print_sum(int (*p)(int &a,int &b),int &a,int &b)
-
{
-
p(a,b);
-
cout<<"sum = a + b:\n";
-
cout<<a<<" + "<<b<<" = "<<(a + b)<<endl;
-
return 0;
-
}
-
int do_print_difference(int (*p)(int &a,int &b),int &a,int &b)
-
{
-
p(a,b);
-
cout<<"sum = a - b:\n";
-
cout<<a<<" - "<<b<<" = "<<(a - b)<<endl;
-
return 0;
-
}
-
-
int main(int argc, const char *argv[])
-
{
-
bool quit = false;//init the value of quit
-
int a = 5,b = 10;
-
char choice;
-
//declare p as a function pointer
-
int (*p)(int &,int&);
-
while(false == quit)
-
{
-
cout<<"quit(q);sum(s);difference(d)."<<endl;
-
cin>>choice;
-
switch(choice)
-
{
-
case 'q':
-
quit = true;
-
break;
-
case 's':
-
p = do_input;
-
do_print_sum(p,a,b);
-
break;
-
case 'd':
-
p = do_input;
-
do_print_difference(p,a,b);
-
break;
-
default:
-
break;
-
}
-
}
-
return 0;
-
}
上述例子中,使用函数指针的方式,可以看到在case语句中只需要制定每个函数指针所指向的函数是什么,那么在print函数中,我们就可以调用这个函数了,运行和输出的结果为:
在程序的第39行,我们声明了一个函数指针。可以看到在case语句中,我们只需要把这个函数指针传递给do_print_sum()或do_print_difference()函数里面就可以了。这样十分方便,而且我们可以看到,其实只要在
do_print_sum()中,即程序的第20行加上对这个函数指针的调用,我们就可以利用指针所指向的函数了。这样十分方便,但是有几个小知识点应该注意一下:
①声明函数指针时,其返回值,参数个数,参数类型应该与需要它指向的函数保持一致;否则,编译器会报错,无法从“***”转换到“***”;
②利用函数指针指向某个函数的时候,我们只用也只能给出该函数的函数名,不能把参数一并给出了。比如说在上例中,如果我们把程序的第50行改成:
p = do_input(a,b);
那么编译器会报错:
func_pointer.cpp(84) : error C2440: “=”: 无法从“double”转换为“double (__cdecl *)(double &,double &)
这个错误的原因就是因为我们忘记了在博文开头所讲的函数指针的一句话:函数名也是指向函数第一条指令的常量指针。因为函数指针就是指向其函数的地址的,那么我们就应该利用函数指针来指向函数名就可以了。
(4)知识补充
我们都会觉得上面所说的函数指针的声明格式有点啰嗦,那么我们也可以
利用typedef来简化声明和定义的操作。比如说在上面函数指针的例子中的第39行,那么一长串。我们完全可以在程序一开始利用typedef来代替:
typedef int (*vp)(int &,int &);
这样一来,我们可以把程序的第61行简化成:
vp p;
而且,我们在声明和定义
do_print_sum()函数的时候,就可以将程序的第7行和第18行换成:
//函数声明
int do_print_sum(vp,int &a,int &b);
//函数定义
int do_print_sum(vp p,int &a,int &b);
总结:
函数指针,本质上一个指针,便于回调其他函数。
函数指针类型规范了它回调函数的参数类型和返回值类型,从而避免了回调函数设计的任意性。
很多时候,我们在设计一个软件的架构的时候,有些代码我们不能在设计软件架构的时候写死。只有在实际应用的时候,才知道该如何去实现它。此时,我们只要规范好函数的返回值和参数类型。当别人在使用我们的函数接口时,他只需要根据我们指定规范,将相应的函数实现好(在这个函数中,完成他需要的功能),接着将这个函数名传递给我们设计的函数接口即可。当然我们设计的函数接口,形参应该是函数指针类型!
如:Linux下signal函数的设计,设计者并不知道,当信号产生后,使用者具体要做哪些事情。所以他最终设计的函数原型如下:
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
signal函数的第二个参数是一个函数指针类型,我们调用这个函数的时候,需要将我们实现好的函数名传递过去。当然,我们在设计自己信号处理函数的时候,不能随意设计这个函数,必须根据signal函数第二个参数的函数指针类型来设计我们自己的信号处理函数,否则编译器就会给我们报参数类型不匹配的错误!
阅读(1620) | 评论(0) | 转发(0) |