函数指针
摘要:函数指针是指向函数的指针变量。 因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是一致的。函数指针有两个用途:调用函数和做函数的参数。
一 通常的函数调用
一个通常的函数调用的例子:
#include "msp430g2553.h"
/* 声明一个函数 */
unsigned int Addxy(unsigned char x,unsigned char y);
/* 声明一个全局变量 */
unsigned int ReceiveZ;
/**-----------------------------------------------------------------------------
* @brief Main program.
*/
void main()
{
/* 调用Addxy()函数 */
ReceiveZ = Addxy(1,2);
}
/**-----------------------------------------------------------------------------
* @brief 定义一个函数
* @param x
* @param y
* @retval z=x+y
*/
unsigned int Addxy(unsigned char x,unsigned char y)
{
unsigned int z;
z = x+y;
return z;
}
这个MyFun函数是一个unsigned int型返回值的函数,它仅仅将x+y的值返还给了main函数。这种调用函数的格式你应该是很熟悉的吧!看主函数中调用MyFun函数的书写格式:ReceiveZ = Addxy(1,2); 我们一开始只是从功能上或者说从数学意义上理解MyFun这个函数,知道MyFun函数名代表的是一个功能(或是说一段代码)。直到——学习到函数指针概念时。我才不得不在思考:函数名到底又是什么东西呢?
函数名就是函数的入口地址,调用一个函数就是call 一个地址。
二 函数指针变量的声明
就象某一数据变量的内存地址可以存储在相应的指针变量中一样,函数的首地址也以存储在某个函数指针变量里的。这样,我就可以通过这个函数指针变量来调用所指向的函数了。在C系列语言中,任何一个变量,总是要先申明,之后才能使用的。那么,函数指针变量也应该要先申明吧?那又是如何来申明呢?以上面的例子为例,我来申明一个可以指向Addxy函数的函数指针变量Point2Addxy。下面就是申明Point2Addxy变量的方法:
/* 声明一个函数指针Point2Addxy -- 指向Addxy */
unsigned int(*Point2Addxy)(unsigned char ,unsigned char );
函数括号中的形参列表可有可无,视情况而定,所以函数申明时也可以写成下面的形式,但习惯上一般不这样:
/* 声明一个函数指针Point2Addxy -- 指向Addxy */
unsigned int(*Point2Addxy)(unsigned char x,unsigned char y);
总结函数指针的声明方法为 :
int func(int x); /* 声明一个函数 */
int (*f) (int x); /* 声明一个函数指针 */
f=func; /* 将func函数的首地址赋给指针f */
三 通过函数指针变量调用函数
有了Point2Addxy函数指针后,我们就可以通过对它赋值,指向函数Addxy(),然后通过Point2Addxy来调用Addxy()函数了。
#include "msp430g2553.h"
/* 声明一个函数 */
unsigned int Addxy(unsigned char x,unsigned char y);
/* 声明一个函数指针Point2Addxy -- 指向Addxy */
unsigned int(*Point2Addxy)(unsigned char x,unsigned char y);
/* 声明一个全局变量 */
unsigned int ReceiveZ;
/**-----------------------------------------------------------------------------
* @brief Main program.
*/
void main()
{
/* 通过一般的方法调用Addxy()函数 */
ReceiveZ = Addxy(1,2);
/* 把函数的入口地址赋值给函数指针 - Point2Addxy就指向函数Addxy()了 */
Point2Addxy = Addxy;
/* 通过函数指针调用函数 */
ReceiveZ = Point2Addxy(3,4);
/* 设置断点用于观察变量值 */
_NOP();
}
/**-----------------------------------------------------------------------------
* @brief 定义一个函数
* @param x
* @param y
* @retval z=x+y
*/
unsigned int Addxy(unsigned char x,unsigned char y)
{
unsigned int z;
z = x+y;
return z;
}
五 利用typedef重定义某一函数指针的类型:
就像自定义数据类型一样,我们也可以先定义一个函数指针类型,然后再用这个类型来声明函数指针变量。下面我们来看一下函数指针类型的定义及使用:
#include "msp430g2553.h"
/* 声明一个函数 */
unsigned int Addxy(unsigned char x,unsigned char y);
/* 函数指针类型重定义 */
typedef unsigned int(*FunPtr_t)(unsigned char x,unsigned char y);
/* 利用重定义的函数指针类型声明一个函数指针 */
FunPtr_t Point2Addxy;
/* 声明一个全局变量 */
unsigned int ReceiveZ;
/**-----------------------------------------------------------------------------
* @brief Main program.
*/
void main()
{
/* 通过一般的方法调用Addxy()函数 */
ReceiveZ = Addxy(1,2);
/* 把函数的入口地址赋值给函数指针 - Point2Addxy就指向函数Addxy()了 */
Point2Addxy = Addxy;
/* 通过函数指针调用函数 */
ReceiveZ = Point2Addxy(3,4);
/* 设置断点用于观察变量值 */
_NOP();
}
/**-----------------------------------------------------------------------------
* @brief 定义一个函数
* @param x
* @param y
* @retval z=x+y
*/
unsigned int Addxy(unsigned char x,unsigned char y)
{
unsigned int z;
z = x+y;
return z;
}
- unsigned int (*FunPtr_t) (unsigned char x,unsigned char y); // 声明一个函数指针,指针变量名为FunPtr_t
- typedef unsigned int (*FunPtr_t) (unsigned char x,unsigned char y); // 函数指针类型重定义,类型名为FunPtr_t
利用typedef进行函数指针类型重定义(Function Pointer Type Define)。首先,在函数指针声明“unsigned int (*FunPtr_t) (unsigned char x,unsigned char y) ;”前加了一个typedef ,这样就完成了函数指针类型的重定义,此时的FunPtr_t就是一个函数指针类型名,而不是函数指针变量了。下面我们利用刚重定义的函数指针类型,定义一个函数指针变量:FunPtr_t Point2Addxy;下面1、2都是声明了一个函数指针Point2Addxy它们的作用是相同的。
1、/* 声明一个函数指针 */
unsigned int(*Point2Addxy)(unsigned char x,unsigned char y);
2、/* 函数指针类型重定义;并利用重定义的函数指针类型声明一个函数指针*/
typedef unsigned int(*FunPtr_t)(unsigned char x,unsigned char y);
FunPtr_t Point2Addxy;
六 函数指针作为某个函数的参数
既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。所以,你还应知道函数指针是如何作为某个函数的参数来传递使用的。
给你一个实例:
要求:我要设计一个CallMyFun函数,这个函数可以通过参数中的函数指针值不同来分别调用MyFun1、MyFun2、MyFun3这三个函数(注:这三个函数的定义格式应相同)。
实现:代码如下:
//自行包含头文件
- void MyFun1(int x);
- void MyFun2(int x);
- void MyFun3(int x);
- typedef void (*FunType)(int ); //②. 定义一个函数指针类型FunType,与①函数类型一至
- void CallMyFun(FunType fp,int x);
- int main(int argc, char* argv[])
- {
- CallMyFun(MyFun1,10); //⑤. 通过CallMyFun函数分别调用三个不同的函数
- CallMyFun(MyFun2,20);
- CallMyFun(MyFun3,30);
- }
- void CallMyFun(FunType fp,int x) //③. 参数fp的类型是FunType。
- {
- fp(x);//④. 通过fp的指针执行传递进来的函数,注意fp所指的函数是有一个参数的
- }
- void MyFun1(int x) // ①. 这是个有一个参数的函数,以下两个函数也相同
- {
- printf(“函数MyFun1中输出:%d\n”,x);
- }
- void MyFun2(int x)
- {
- printf(“函数MyFun2中输出:%d\n”,x);
- }
- void MyFun3(int x)
- {
- printf(“函数MyFun3中输出:%d\n”,x);
- }
分析:(看我写的注释。你可按我注释的①②③④⑤顺序自行分析。)
void(*reset)(void)就是函数指针定义,(void(*)(void))0是强制类型转换操作,将数值“0”强制转换为函数指针地址“0x00”。
通过调用reset()函数,程序就会跳转到程序执行的“0”地址处重新执行。在一些其他高级单片机Bootloader中,如NBoot、UBoot、EBoot,经常通过这些Bootloader进行下载程序,然后通过函数指针跳转到要执行程序的地址处。
1 void (*theUboot)(void);
。。。。
theUboot = (void (*)(void))(0x30700000);
theUboot();
。。。。。
2 (*(void (*)(void))(0x30700000))();
强制类型转换,将一个绝对地址转换为一个函数指针,并调用这个函数以跳转到前面提到的绝对地址.
翻译成汇编就是:
mov r0,0x30700000;
mov pc,r0
对于(*(void (*)(void))(0x30700000))();
可以这样理解
首先(void( * )(void) )是一个强制类型转换符,他将后面的0x30700000这个无符号整数强制转化为一个函数指针,该函数指针所指向的函数入口参数为 void,返回值也是void 。 如果到这步你看懂了,那么设(void (*)(void))(0x30700000)为 fp; 那么上面的表达式就可以简化为 (*fp)(); OK,这下就清楚了吧,我们将上面转化好的函数指针进行引用(也就是调用函数指针指向的函数)。
参考资源:http://blog.chinaunix.net/uid-25524263-id-2888273.html

浙公网安备 33010602011771号