一、函数指针的定义和修饰
函数指针广泛应用于嵌入式软件开发中,其常用的两个用途:调用函数和做函数的参数。
定义了一个指向返回值为int,无参数的函数的指针。
-
void (*fptr)();
-
//把函数的地址赋值给函数指针,一般采用如下形式:
-
fptr=Function;
-
//如果是函数调用,还必须包含一个圆括号括起来的参数表。可以采用如下方式来通过指针调用函数:
-
x=(*fptr)();使用typedef来“修饰”一个函数指针
使用typedef来“修饰”一个函数指针 -
typedef void (*fptr)();
-
fptr func;
-
fptr func1;
-
fptr func2;
-
(二)示例2:一个极简的函数指针
-
//返回值类型 (*指针名称)();
-
int (*)()
-
//定义了一个指向返回值为int,无参数的函数的指针;
-
(三)示例3:函数指针的跳转
我们在真实的项目开发过程中,可能需要直接跳转到函数的某个地址去指针。void (*function_p)(void); //定义函数指针function_p,无返回值,无参数
function_p = my_func; //函数指针指向function函数
(*function_p)(); //采用函数指针运行函数
这个等同于直接调用my_func函数,那么这个有什么意义呢?其实这样提出了一个思路,就是可以根据函数的地址,跳转到函数的地址。(具体可参照示例4) -
四)示例4:使用函数指针调用特定内存地址的函数
首先看一个例子:(*(void(*) ())0)()
void (*)() 是一个函数指针,只是把p去掉了而已。把上面的void (*)()用PN代替,上面的表达式变成(*(PN)0)();
PN后面有一个0,这个是让人咋舌的地方,然后想一下 (char)a这样的表达式;因此所以*(PN)0就是把0当成一个地址,强制转换为PN类型,用*这个钥匙取出这个地址区域的值。
把(*(PN)0)()替换成PM,原来的表达式变成PM(),这样看起来就明了了,就是正常的函数调用。
-
(五)示例5:bootloader中经典的内存跳转
比如我们在bootloader中,当把二进制文件加载到内存中后,如何去执行这个kernel程序呢?也就是实现一个bootloader到kernel的跳转。((void(*)())0x80000)();
这里就是说0x80000处的地址是函数类型,并且没有返回值。当我们的kernel地址为0x80000时程序跳转过去,不再返回。这就是一个比较经典的例子。实际上此示例同示例4。const修饰指针
此处插入一下,如果指针所指向内存中的数据或指针指向不想被修改,可以参考const用法。const int *p;//指针变量p所指向的空间内容不可修改即*p不可修改,但p可修改
int const *q;//指针变量q所指向的空间内容不可修改即*q不可修改,但q可修改
int *const r;//指针变量r不可修改,但*r所指向的空间内容可修改
const int * const x;//指针变量x自身不可修改,x所指向的空间内容亦不可修改
(六)示例6:函数指针数组形式的调用
void (*f[])(char *)
f 是个什么what?[] 的优先级 比 *的优先级高,所以 f首先是修饰了数组,然后跟后面的 *组合,就说明这个数组里面住的都是指针,这些指针是什么呢,再出来看看就看到了,这个指针是 一个函数,这个函数的 参数是 char *返回值是void。
因此这是一个函数指针数组!!!
-
三、函数指针的调用
(一)示例1:用typedef申请一个简单的函数指针
//声明一个函数指针,一般的方法是
int (*pfunc)(int a,int b)
//当命名很多个函数指针的时候,用上面的方法显得非常不方便,可以这样做
typedef int (*PF) (int a,int b)
PF pfunc;
/********************************************************************/
//例程
#include "stdio.h"
typedef int(*PF)(int, int);
int add(int a, int b)
{
return a + b;
}
int reduce(int a, int b)
{
return a - b;
}
int main()
{
PF pfunc = NULL;
pfunc = add;
printf("add:%d\n",pfunc(3, 4));
pfunc = reduce;
printf("reduce:%d\n", pfunc(3, 4));
/*getchar是用VS编写方便查看输出*/
getchar();
return 0;} -
(五)函数指针的三大要素
当看到复杂的函数指针形势时,准确区分三大要素就能把握函数指针。1、函数指针的返回值;
2、函数指针的参数;
3、函数指针所指向函数的参数;
(六)函数指针作为返回值,实现回调函数的注册
函数指针的形式如此复杂的主要原因是 : 函数指针既可以作为函数的参数,也可以作为函数的返回值;而作为函数参数的函数指针又可以带有函数指针参数和函数指针返回值,层层嵌套,一旦展开那还是非常恐怖的。void ( *signal(int signum,void(*handler)(int)) )(int)
pFuc1 *signal(int signum,void(*handler)(int))
pFuc1 *signal(int signum,void( *handler )(int))
pFuc2 void(*)(int)
pFuc1 *signal(int signum,pFuc2 *handler )
void (signal(int , void()(int)))(int) 是一个复杂的函数原型,它的作用是:signal() 函数接收两个参数:第一个参数是中断信号,第二个参数是中断信号对应的处理函数。
signal() 函数返回值是中断信号之前的处理函数。也就是说,这个函数原型定义了 signal() 函数,它可以用来注册中断信号对应的处理函数,并返回中断信号之前的处理函数。
这个函数原型的具体语法为:
void (*signal(int signum, void (*handler)(int)))(int);
它返回一个指向参数为int,返回值为void的函数指针。使用这个函数的步骤为:
1. 定义中断信号处理函数,其原型为 void fun(int)。
2. 调用 signal() 函数,传入要注册的中断信号编号和处理函数名。如:
void (*old_handler)(int);
old_handler = signal(SIGINT, fun);
3. signal() 函数会注册 fun() 函数为 SIGINT 信号的处理函数。4. signal() 返回之前的 SIGINT 信号处理函数,存储在 old_handler 中。
5. 当产生中断时,fun() 函数会被调用,用于处理该中断。
6. 如果需要恢复老的处理函数,可以调用:signal(SIGINT, old_handler)。
具体的回调函数和回调注册机制将在写一篇文章中阐述。
以上signal函数的简化写法
void (*signal(int , void(*)(int)))(int);
/*
1、signal 是一个函数声明;
2、这个函数的参数有 2 个:
第一个是 int 类型;
第二个是函数指针,该指针类型为“返回void,函数参数 int”;
3、signal 函数的返回类型也是函数指针,该指针指向的函数参数 int,返回类型是 void 。
*/
typedef void(*pfun_t)(int);
//原函数
void (*signal(int , void(*)(int)))(int);
//简化后
pfun_t signal(int , pfun_t);