C/C++函数指针
3 C/C++函数指针的语法
从语法上讲,有两种不兼容的函数指针形式:
(1) 指向C语言函数和C++静态成员函数的函数指针
(2) 指向C++非静态成员函数的函数指针
不兼容的原因是因为在使用C++非静态成员函数的函数指针时,需要一个指向类的实例的this指针,而前一类不需要。
3.1 定义一个函数指针
指针是变量,所以函数指针也是变量,因此可以使用变量定义的方式来定义函数指针,对于普通的指针,可以这么定义:
int a=10;
int *pa=&a;
这里,pa是一个指向整型的指针,定义这个指针的形式为:
int * pa;
区别于定义非指针的普通变量的“形式”就是在类型中间和指针名称中间加了一个“*”,所以能够表达不同的“内容”。这种形式对于表达的内容是完备的,因为它说明了两点:(1)这是一个指针(2)这是一个指向整型变量的指针
以下给出三个函数指针定义的形式,第一个是C语言的函数指针,第二个和第三个是C++的函数指针的定义形式(都是指向非静态函数成员的函数指针):
int (*pFunction)(float,char,char)=NULL;
int (MyClass::*pMemberFunction)(float,char,char)=NULL;
int (MyClass::*pConstMemberFunction)(float,char,char) const=NULL;
我们先不管函数指针的定义形式,如果让我们自己来设计指向函数的函数指针的定义形式的话,我们会怎么设计?
首先,要记住一点的就是形式一定要具备完备性,能表达出我们所要表达的内容,即指向函数这个事实。我们知道普通变量指针可以指向对应类型的任何变量,同样函数指针也应该能够指向对应类型的任何变量。对应的函数类型靠什么来确定?这个我们可以想一下C++的函数重载靠什么来区分不同的函数?这里,函数类型是靠这几个方面来确定的:(1)函数的参数个数(2)函数的参数类型(3)函数的返回值类型。所以我们要设计一种形式,这种形式定义的函数指针能够准确的指向这种函数类型的任何函数。
在C语言中这种形式为:
返回类型 (*函数指针名称)(参数类型,参数类型,参数类型,…);
嗯,定义变量的形式显然不是我们通常见到的这种形式:
类型名称 变量名称;
但是,这也是为了表达函数这种相对复杂的语义而不得已采用的非一致表示形式的方法。因为定义的这个函数指针变量,能够明确的表达出它指向什么类型的函数,这个函数都有哪些类型的参数这些信息,确切的说,它是完备的。你可能会问为什么要加括号?形式上讲能不能更简洁点?不能,因为不加括号就会产生二义性:
返回类型 *函数指针名称(参数类型,参数类型,参数类型,…);
这样的定义形式定义了一个“返回类型为‘返回类型*’参数为(参数类型,参数类型,参数类型,…)的函数而不是函数指针了。
接下来,对于C++来说,下面这样的定义形式也就不难理解了(加上类名称是为了区分不同类中定义的相同名称的成员函数):
返回类型 (类名称::*函数成员名称)(参数类型,参数类型,参数类型,….)
3.2 函数的调用规则
一般来说,不用太关注这个问题。调用规则主要是指函数被调用的方式,常见的有_stdcall,_fastcall,_pascal,_cdecl等规则。不同的规则在参数压入堆栈的顺序是不同的,同时在有调用者清理压入堆栈的参数还是由被调用者清理压入堆栈的参数上也是不同的。一般来说,如果你没有显式的说明调用规则的话,编译器会统一按照_cdecl来处理。
3.3 给函数指针赋值和调用
给函数指针赋值,就是为函数指针指定一个函数名称。这个过程很简单,下面是两个例子:
int func1(float f,int a,int b){return f*a/b;}
int func2(float f,int a,int b){return f*a*b}
然后我们给函数指针pFunction赋值:
pFunction=func1;
pFunction=&func2;
上面这段代码说明了两个问题:(1)一个函数指针可以多次赋值(想想C++中的引用)(2)取地址符号是可选的,却是推荐使用的。
我们可以思考一下为什么取地址符号是可选的,在普通的指针变量赋值时,如上面所示,需要加取地址符号,而这里却是可选的?这是由于要同时考虑到两个因素(1)避免二义性(2)形式一致性。在普通指针赋值,需要加取地址符号是为了区别于将地址还是将内容赋给指针。而在函数赋值时没有这种考虑,因为这里的语义是清晰的,加上&符号是为了和普通指针变量一致---“因为一致的时候就不容易出错”。
最后我们来使用这个函数
pFunction(10.0,’a’,’b’);
(*pFunction)(10.0,’a’,’b’);
上面这两种使用函数指针调用函数的方式都是可以的,原因和上面一样。
下面来说明C++中的函数指针赋值和调用,这里说明非静态函数成员的情况,C++中规则要求的严格的多了。让我感觉C++就像函数指针的后爸一样,对函数指针要求特别死,或许是因为他有一个函数对象这个亲儿子。
在C++中,对于赋值,你必须要加“&”,而且你还必须再次之前已经定义好了一个类实例,取地址符号要操作于这个类实例的对应的函数成员上。在使用成员函数的指针调用成员函数时,你必须要加类实例的名称,然后再使用.*或者->*来使用成员函数指针。举例如下:
MyClass
{
public:
int func1(float f,char a,char b)
{
return f*a*b;
}
int func2(float f,char a,char b) const
{
return f*a/b;
}
}
首先来赋值:
MyClass mc;
pMemberFunction= &mc.func1; //必须要加取地址符号
pConstMemberFunction = &mc.func2;
接下来,调用函数:
(mc.*pMemberFunction)(10.0,’a’,’b’);
(mc.*pConstMemberFunction)(10.0,’a’,’b’);
我感觉,C++简直在虐待函数指针啊。
下面是一个完整的例子:
- /*
- *Author:Choas Lee
- *Date:2012-02-28
- */
- #include<stdio.h>
- float func1(float f,char a,char b)
- {
- printf("func1\n");
- return f*a/b;
- }
- float func2(float f,char a,char b)
- {
- printf("func2\n");
- return f*a*b;
- }
- class MyClass
- {
- public:
- MyClass(float f)
- {
- factor=f;
- }
- float func1(float f,char a,char b)
- {
- printf("MyClass::func1\n");
- return f*a/b*factor;
- }
- float func2(float f,char a,char b) const
- {
- printf("MyClass::func2\n");
- return f*a*b*factor;
- }
- private:
- float factor;
- };
- int main(int argc,char *argv[])
- {
- float (*pFunction)(float,char,char)=NULL;
- float (MyClass::*pMemberFunction)(float,char,char)=NULL;
- float (MyClass::*pConstMemberFunction)(float,char,char)const=NULL;
- float f=10.0;
- char a='a',b='b';
- float result;
- pFunction=func1;
- printf("pointer pFunction's address is:%x\n",pFunction);
- result=(*pFunction)(f,a,b);
- printf("result=%f\n",result);
- pFunction=&func2;
- printf("pointer pFunction's address is:%x\n",pFunction);
- result=pFunction(f,a,b);
- printf("result=%f\n",result);
- if(func1!=pFunction)
- printf("not equal.\n");
- pMemberFunction=&MyClass::func1;
- MyClass mc1(0.2);
- printf("pointer pMemberFunction's address is:%x\n",pMemberFunction);
- result=(mc1.*pMemberFunction)(f,a,b);
- printf("result=%f\n",result);
- pConstMemberFunction=&MyClass::func2;
- MyClass mc2(2);
- printf("pointer pConstMemberFunction's address is:%x\n",pConstMemberFunction);
- result=(mc2.*pConstMemberFunction)(f,a,b);
- printf("result=%f\n",result);
- return 0;
- }
运行结果为:
- pointer pFunction's address is:400882
- func1
- result=9.897959
- pointer pFunction's address is:400830
- func2
- result=95060.000000
- not equal.
- pointer pMemberFunction's address is:400952
- MyClass::func1
- result=1.979592
- pointer pConstMemberFunction's address is:4008f2
- MyClass::func2
- result=190120.000000
注意:上面的代码还说明了一点就是函数指针的一些基本操作,函数指针没有普通变量指针的算术操作,但是可以进行比较操作。如上面代码所示。
使用类的静态函数成员的函数指针和使用C语言的函数很类似,这里仅仅给出一个例子和其执行结果:
程序代码为:
- /*
- *Author:Chaos Lee
- *Date:2012-02-28
- */
- #include<iostream>
- class MyClass
- {
- public:
- static float plus(float a,float b)
- {
- return a+b;
- }
- };
- int main()
- {
- float result,a=10.0,b=10.0;
- float (*p)(float,float);
- p=&MyClass::plus;
- result=p(a,b);
- printf("result=%f\n",result);
- return 0;
- }
执行结果为:
result=20.000000
3.4 函数指针作为参数
如果你已经明白了函数的参数机制,而且完全理解并实践了3.3节的内容,这一节其实是很简单的。只需要在函数的参数列表中,声明一个函数指针类型的参数即可,然后再调用的时候传给它一个实参就可以了。你可以这么想象,就是把函数指针的赋值语句的等号换成了形参和实参结合的模式就行。
下面给一个简单的例子:
- /*
- *Author:Choas Lee
- *Date:2012-02-28
- */
- #include<stdio.h>
- float add(float a,float b){return a+b;}
- float minus(float a,float b){return a-b;}
- float multiply(float a,float b){return a*b;}
- float divide(float a,float b){return a/b;}
- int pass_func_pointer(float (*pFunction)(float a,float b))
- {
- float result=pFunction(10.0,12.0);
- printf("result=%f\n",result);
- }
- int main()
- {
- pass_func_pointer(add);
- pass_func_pointer(minus);
- pass_func_pointer(multiply);
- pass_func_pointer(divide);
- return 0;
- }
输出结果为:
- result=22.000000
- result=-2.000000
- result=120.000000
- result=0.833333
3.5 使用函数指针作为返回值
函数指针可以作为返回值。我们先类比的思考一下,如果说整型可以作为返回值,你会怎么声明函数?嗯,应该是下面这个样子的:
int func(){}
整数对应的类型为int。同样再类比以下,如果说整型指针可以作为返回值,你会怎么声明?嗯,这个貌似难度也不大:
int * func(){}
好吧,现在说函数指针如果可以作为返回值,你该怎么声明?首先要保证的一点就是返回的函数指针的类型必须是能够明显的表达在这个函数的声明或者定义形式中的,也就是说在这个形式中,要能够包含函数指针所对应的能够确定函数类型的信息:这个函数类型的返回值类型,这个函数类型的参数个数,这个函数类型的参数类型。,
现在我们在类比一次,如果要返回浮点型指针,那么返回类型应该表达为:
float *
如果要函数指针对应的函数是返回值为浮点型,带有两个参数,两个参数都是浮点型,那么返回类型应该表达为下面的表达形式:
float (*)(float ,float )
嗯,没办法,函数的语义比较复杂,对应的表现就是形式的复杂性了。对于返回为浮点型指针的情况,定义的函数的名称放在“float *”的后面,而对于返回为上面类型的函数指针的话,定义的函数就要放在“(*)”这个括号中的*的后面了。
所以对于以下形式:
float (* func(char op) ) (float ,float)
其具体含义就是,声明了这样一个函数:
l 其名称为func,其参数的个数为1个;
l 其各个参数的类型为:op—char;
l 其返回变量(函数指针)类型为:float(*)(float,float)
再次强调:函数指针时变量哦。
到了这里之后,我们再来分析一下unix的系统调用函数signal的定义形式:
void (*signal)(int signo,void (*func)(int)))(int);
其具体含义为就是,声明了这样一个函数:
l 其函数名称为:signal
l 其参数个数为:2
l 其各个参数的类型为:signo--int, func— void (*)(int)
l 其返回的变量(函数指针)的类型为:void(*)(int)
上面这个函数比较经典,有一个参数类型为函数指针,返回值还是函数指针。
哦,我的天,如果你一步一步看到这里了,就快大功告成啦。嘿嘿,接下来看一个例子:
- /*
- *Author:Choas Lee
- *Date:2012-02-28
- */
- #include<stdio.h>
- #include<stdlib.h>
- #include<string.h>
- float add(float a,float b){return a+b;}
- float minus(float a,float b){return a-b;}
- float multiply(float a,float b){return a*b;}
- float divide(float a,float b){return a/b;}
- float(* FunctionMap(char op) )(float,float)
- {
- switch(op)
- {
- case '+':
- return add;
- break;
- case '-':
- return minus;
- break;
- case '*':
- return multiply;
- break;
- case '\\':
- return divide;
- break;
- default:
- exit(1);
- }
- }
- int main()
- {
- float a=10,b=5;
- char ops[]={'+','-','*','\\'};
- int len=strlen(ops);
- int i=0;
- float (*returned_function_pointer)(float,float);
- for(i=0;i<len;i++)
- {
- returned_function_pointer=FunctionMap(ops[i]);
- printf("the result caculated by the operator %c is %f\n",ops[i],returned_function_pointer(a,b));
- }
- return 0;
- }
计算的结果为:
- the result caculated by the operator + is 15.000000
- the result caculated by the operator - is 5.000000
- the result caculated by the operator * is 50.000000
- the result caculated by the operator \ is 2.000000
3.6 使用函数指针数组
函数指针有意思的地方在于,它使用从0到n-1这个n个连续的整数下标直接映射到函数上。
和前面一样,我们也是类比着定义普通指针数组来定义函数指针数组。首先,考虑一个浮点数指针数组,数组的长度为10.我们都知道用下面的形式来定义:
float * pFloatArray[10];
从形式上分析,用中括号明确了是定义指针变量还是定义指针数组这个语义。用数字10明确了这个数组能容纳多少个函数指针这个语义。形式上看,中括号是紧接在指针名称的后面再中括号里面是一个需要在编译时期间就能够确定的常数。
现在我们来类比函数指针数组的定义,定义一个指向函数指针类型为:float (*)(float,float)的函数指针数组,数组长度为10.正确的形式为:
float(* pFunctionArray[10])(float,float)
从形式上看,这种定义方式和定义普通指针的定义方式是一致的:都是在指针名称后面紧接着一个中括号,然后里面是一个编译期间能够确定的常数。这种形式上的一致性,可以方便我们对形式的记忆,进而达到对内容的理解。
下面是一个例子程序:
- /*
- *Author:Choas Lee
- *Date:2012-02-28
- */
- #include<stdio.h>
- float add(float a,float b){return a+b;}
- float minus(float a,float b){return a-b;}
- float multiply(float a,float b){return a*b;}
- float divide(float a,float b){return a/b;}
- int main()
- {
- float(*func_pointers[4])(float,float)={add,minus,multiply,divide};
- int i=0;
- float a=10.0,b=5.0;
- for(i=0;i<4;i++)
- {
- printf("result is %f\n",func_pointers[i](a,b));
- }
- return 0;
- }
以下为对应的运行结果:
- result is 15.000000
- result is 5.000000
- result is 50.000000
- result is 2.000000
3.7 使用typedef
从哲学角度讲,形式过于复杂的话,还是抽象的层次太低。如果我们使用多层次的抽象,这样最上层的表示就会简化很多。这就是引入typedef的原因,使用typedef可以简化函数指针的定义,因为typedef可以定义新的类型:
同样,在使用typedef定义函数指针类型的时候,也和普通的使用typedef引入新类型的方式不一样。我们和前面一样对照着普通的定义方式来学习:
typedef int bool;
这在C语言中很常用,由于C语言中没有bool类型,这样定义之后可以从形式上引入一个bool类型,提高代码可读性。所以形式为:
typedef 已知类型 新类型;
现在我们要将float (*)(float,float)类型声明为一种新类型,按照上面的方式,貌似为:typedef float(*)(float,float) fpType;然而,前面的经验告诉我们应该这样定义啊:
typedef float(*fpType)(float,float);
这样我们就可以用fpType来表示float (*)(float,float)这种类型了。所以定义一个新的指向float (*)(float,float)类型的指针变量的时候,我们就可以采用下面这种形式了:
fpType pFunction;
在定义函数指针数组的时候可以这样定义:
fpType pFunctions[10];
在定义函数指针类型参数时可以这样定义:
void func(fpType pFunction);
在定义函数指针类型的返回值时可以这样定义:
fpType func(int a);
现在我们再来看一下,unix中的那个signal函数,其形式为:
void (*signal)(int signo,void (*func)(int)))(int);
现在我们定义一个类型为:
typedef void (*pSgnType)(int);
这样上面的函数就能表达为:
pSgnType signal(int signo,pSgnType func);
这样是不是看起来清爽多了。
其实上面的signal函数也能这样定义:
首先引入新类型:
typedef void SgnType(int)
然后signal函数的声明改为:
SgnType *signal(int signo,SgnType *func);
按照前面对这些形式的解释,理解这个应该没难度~~
现在在引入最后一个例子,关于使用typedef来简化函数指针定义的:
- /*
- *Author:Choas Lee
- *Date:2012-02-29 00:25
- */
- #include<stdio.h>
- #include<stdlib.h>
- #include<string.h>
- float add(float a,float b){return a+b;}
- float minus(float a,float b){return a-b;}
- float multiply(float a,float b){return a*b;}
- float divide(float a,float b){return a/b;}
- typedef float (*pArithmeticOperations)(float,float);
- typedef float ArithmeticOperations(float,float);
- int main()
- {
- pArithmeticOperations pao=add;
- pArithmeticOperations paos[4]={add,minus,multiply,divide};
- ArithmeticOperations *ao=add;
- ArithmeticOperations *aos[4]={add,minus,multiply,divide};
- float a=10.0,b=5.0;
- float result=0.0;
- int i=0;
- result=pao(a,b);
- printf("the result of pao is %f\n",result);
- printf("the results of paos are:\n");
- for(i=0;i<4;i++)
- {
- result=paos[i](a,b);
- printf("result=%f\n",result);
- }
- result=ao(a,b);
- printf("\n\nthe result of ao is :%f\n",result);
- printf("the results of aos are:\n");
- for(i=0;i<4;i++)
- {
- result=aos[i](a,b);
- printf("result=%f\n",result);
- }
- return 0;
- }
输出结果为:
- result=15.000000
- result=5.000000
- result=50.000000
- result=2.000000
- the result of ao is :15.000000
- the results of aos are:
- result=15.000000
- result=5.000000
- result=50.000000
- result=2.000000
浙公网安备 33010602011771号