函数指针、函数指针数组详解及典型应用

编程入门 行业动态 更新时间:2024-10-24 10:26:44

函数<a href=https://www.elefans.com/category/jswz/34/1768268.html style=指针、函数指针数组详解及典型应用"/>

函数指针、函数指针数组详解及典型应用

20200305 杨千嬅唱的《处处吻》真是太好听了,下个他他吻她他吻她吻他吻她… 已沉醉

一、何为函数指针

我们知道指针变量指向内存单元的地址,比如存放普通变量int a;的地址的就是一重指针,存放一重指针变量的地址的就是二重指针,指针变量存地址,以此来实现传址调用,
函数指针,顾名思义,就是指向函数的指针,那么何为指向函数呢?按照上面的逻辑,我们得有一个指针变量,这个指针变量里存放着该函数块在内存中的首地址。
要想理解这个,我们要从指针的层次上理解函数——函数的函数名实际上就是该函数的代码在内存中的首地址的别名,说白了还是个地址,这个有点类似于数组名和数组首元素地址的关系,随便找个反汇编代码就可印证上面所说的,下面的是汇编代码和反汇编调用main函数的例子
1.关于别名:586、587行
2.关于调用:我们可以看看汇编中是如何调用函数的,可以发现,PC指向了文字池中的内容30000848,这不正是mian函数的首地址吗,传址调用就好了

二、函数指针变量的类型及定义

类型表征着特征,而能够描述一个函数的特征的无非就是入口参数(形参)和返回值了

形式1:返回类型(*函数指针变量名)(参数表)

char(*pFun)(int); //定义了一个名为pFun的函数指针变量,要求指向的函数的要有一个int类型的形参并要求该函数返回值为char类型
char glFun(int a)
{return a;
}
void main()
{pFun =glFun;/*函数指针的普通使用,以下两种方式个都可*/pFun (2);//与其指向的函数用法无异  (*pFun)(2);//此处*pf两端括号必不可少  
}

第1行:pFun是 char (*)(int) 类型的指针
第2行:定义了一个函数glFun().该函数正好是一个以int为参数返回char的函数。即char (int) 类型
第8行:指针变量pFun指向了glFun函数的首地址

形式2:使用typedef更直接

typedef char(*PTRFUN)(int)
PTRFUN pFun;
char glFun(int a)
{return a;
}
void main()
{pFun = glFun;(*pFun)(2);
}

typedef的功能是定义新的类型。第一句就是定义了一种PTRFUN的类型,并定义这种类型为指向某种函数的指针,这种函数以一个int为参数并返回char类型。

三、举例说明用途

简单的计算函数回调

#include <stdio.h>
#define SIZE 4
/*typedef的功能是定义新的类型。
下面这句就是定义了一种PFUNC的类型,并定义这种类型为指向某种函数的指针,这种函数以两个int为参数并返回int类型。*/
typedef int (*PFUNC)(int, int);
PFUNC fucp_arr[SIZE] = {NULL};
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return b ? a / b : -1;
}/*返回函数类型,实现函数回调*/
PFUNC calc_func(char op)
{switch (op){case '+':return add;case '-':return sub;case '*':return mul;case '/':return div;default:return NULL;}return NULL;
}int main(void)
{/*无论是什么类型的指针,在32位系统下都占4字节*/printf("sizeof(PFUNC) = %d\n", sizeof(PFUNC));/*实现函数回调*/printf("%d + %d = %d\n", 13, 14, (calc_func('+'))(13, 14));printf("%d - %d = %d\n", 13, 14, (calc_func('-'))(13, 14));printf("%d * %d = %d\n", 13, 14, (calc_func('*'))(13, 14));printf("%d / %d = %d\n", 13, 14, (calc_func('/'))(13, 14));return 0;
}

编译运行后得到

root@youngfar-PC:/mnt/hgfs/Linux教程/mytest# gcc -o exe test.c -m32
root@youngfar-PC:/mnt/hgfs/Linux教程/mytest# ./exe
sizeof(PFUNC) = 4
13 + 14 = 27
13 - 14 = -1
13 * 14 = 182
13 / 14 = 0

四、利用函数指针数组进行函数的注册和调用

#include <stdio.h>
#define SIZE 4
/*typedef的功能是定义新的类型。
下面这句就是定义了一种PFUNC的类型,并定义这种类型为指向某种函数的指针,这种函数以两个int为参数并返回int类型。*/
typedef int (*PFUNC)(int, int);
PFUNC fucp_arr[SIZE] = {NULL};
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return b ? a / b : -1;
}/*返回函数类型,实现函数回调*/
PFUNC calc_func(char op)
{switch (op){case '+':return add;case '-':return sub;case '*':return mul;case '/':return div;default:return NULL;}return NULL;
}/* 不使用typedef,直接让函数返回一个函数指针,写起来比较麻烦,不直观,所以不推荐定义了一个函数,s_calc_func是函数名,s_calc_func这个函数需要char类型的形参,且最终返回一个函数指针,且要求这个函数指针指向的函数有两个int类型的形参和一个int类型的返回值*/
int (*s_calc_func(char op))(int, int) /* 注意:这个函数的用途与上一个名为calc_func的函数的作业和调用方式完全相同*/
{return calc_func(op);
}/*我们要从指针的层次上理解函数-函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。*/
void Fp_Register(int position, PFUNC fp)
{fucp_arr[position] = fp;
}int main(void)
{
/*无论是什么类型的指针,在32位系统下都占4字节*/printf("sizeof(PFUNC) = %d\n", sizeof(PFUNC));// /*实现函数回调*/// printf("%d + %d = %d\n", 13, 14, (calc_func('+'))(13, 14));// printf("%d - %d = %d\n", 13, 14, (calc_func('-'))(13, 14));// printf("%d * %d = %d\n", 13, 14, (calc_func('*'))(13, 14));// printf("%d / %d = %d\n", 13, 14, (calc_func('/'))(13, 14));/*函数注册*/Fp_Register(0, add);Fp_Register(1, sub);Fp_Register(2, mul);Fp_Register(3, div);/*函数调用*/printf("%d + %d = %d\n", 13, 14, fucp_arr[0](13, 14));printf("%d - %d = %d\n", 13, 14, fucp_arr[1](13, 14));printf("%d * %d = %d\n", 13, 14, fucp_arr[2](13, 14));printf("%d / %d = %d\n", 13, 14, fucp_arr[3](13, 14));return 0;
}

运行结果和上边的一样

函数指针数组应用实例

函数指针数组一般用于有相同操作流程但细节却不能使用一个函数统一描述的的函数的调用,比如在2440处理中断时的一些操作,下面贴出代码,举例说明,先来看2440中断的处理流程

The S3C2440A has two interrupt pending registers: source pending register (SRCPND) and interrupt pending register (INTPND). These pending registers indicate whether an interrupt request is pending or not. When the interrupt sources request interrupt the service, the corresponding bits of SRCPND register are set to 1, and at the same time, only one bit of the INTPND register is set to 1 automatically after arbitration procedure. If interrupts are masked, then the corresponding bits of the SRCPND register are set to 1. This does not cause the bit of INTPND register changed. When a pending bit of INTPND register is set, the interrupt service routine will start whenever the I-flag or F-flag is cleared to 0. The SRCPND and INTPND registers can be read and written, so the service routine must clear the pending condition by writing a 1 to the corresponding bit in the SRCPND register first and then clear the pending condition in the INTPND registers by using the same method.
S3C2440A有两个中断暂挂寄存器:源暂挂寄存器(SRCPND)和中断暂挂寄存器(INTPND)。这些挂起的寄存器指示中断请求是否挂起。当中断源请求中断服务时,SRCPND寄存器的相应位被设置为1,同时,经过仲裁程序,INTPND寄存器中只有1位被设置为1。如果中断被屏蔽,那么SRCPND寄存器的相应位被设置为1。这不会导致INTPND寄存器的位发生变化。当设置一个暂挂的INTPND寄存器位时,中断服务例程将在I-flag或F-flag被清除为0时启动。SRCPND和INTPND寄存器可以读写,因此服务例程必须先通过将1写入SRCPND寄存器中相应的位来清除挂起条件,然后使用相同的方法清除INTPND寄存器中的挂起条件。


下面是2440代码的按键中断处理函数

#include "s3c2440_soc.h"/* SRCPND 用来显示哪个中断产生了, 需要清除对应位* bit0-eint0* bit2-eint2* bit5-eint8_23*//* INTMSK 用来屏蔽中断, 1-masked* bit0-eint0* bit2-eint2* bit5-eint8_23*//* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位* bit0-eint0* bit2-eint2* bit5-eint8_23*//* INTOFFSET : 用来显示INTPND中哪一位被设置为1*//* 初始化中断控制器 Initial State 为1, 0 = Service available*/
void interrupt_init(void)
{INTMSK &= ~((1 << 0) | (1 << 2) | (1 << 5)); /*按键中断源配置*/INTMSK &= ~(1 << 10);						 /* timer0 中断源配置*/
}/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{/* 配置GPIO为中断引脚 */GPFCON &= ~((3 << 0) | (3 << 4));GPFCON |= ((2 << 0) | (2 << 4)); /* S2,S3被配置为中断引脚 */GPGCON &= ~((3 << 6) | (3 << 22));GPGCON |= ((2 << 6) | (2 << 22)); /* S4,S5被配置为中断引脚 *//* 设置中断触发方式: 双边沿触发 */EXTINT0 |= (7 << 0) | (7 << 8); /* S2,S3 */EXTINT1 |= (7 << 12);			/* S4 */EXTINT2 |= (7 << 12);			/* S5 *//* 设置EINTMASK使能eint11,19 */EINTMASK &= ~((1 << 11) | (1 << 19));
}/* 读EINTPEND分辨率哪个EINT产生(eint4~23)* 清除中断时, 写EINTPEND的相应位*/void key_eint_irq(int irq)
{unsigned int val = EINTPEND;unsigned int val1 = GPFDAT;unsigned int val2 = GPGDAT;if (irq == 0) /* eint0 : s2 控制 D12 */{if (val1 & (1 << 0)) /* s2 --> gpf6 */{/* 松开 */GPFDAT |= (1 << 6);}else{/* 按下 */GPFDAT &= ~(1 << 6);}}else if (irq == 2) /* eint2 : s3 控制 D11 */{if (val1 & (1 << 2)) /* s3 --> gpf5 */{/* 松开 */GPFDAT |= (1 << 5);}else{/* 按下 */GPFDAT &= ~(1 << 5);}}else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */{if (val & (1 << 11)) /* eint11 */{if (val2 & (1 << 3)) /* s4 --> gpf4 */{/* 松开 */GPFDAT |= (1 << 4);}else{/* 按下 */GPFDAT &= ~(1 << 4);}}else if (val & (1 << 19)) /* eint19 */{if (val2 & (1 << 11)){/* 松开 *//* 熄灭所有LED */GPFDAT |= ((1 << 4) | (1 << 5) | (1 << 6));}else{/* 按下: 点亮所有LED */GPFDAT &= ~((1 << 4) | (1 << 5) | (1 << 6));}}}EINTPEND = val; //写1清除中断标志
}void handle_irq_c(void)
{/* 分辨中断源 */int bit = INTOFFSET;/* 调用对应的处理函数 */if (bit == 0 || bit == 2 || bit == 5) /* eint0,2,eint8_23 */{key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */}else if (bit == 10){timer_irq();}/* 清中断 : 从源头开始清 */SRCPND = (1 << bit);INTPND = (1 << bit);
}

每次编写中断相关函数需要

汇编中:
1.中断发生前:

01.设置中断向量表
02.调用c中的mian函数

2.中断发生时:

跳转到中断向量表,去执行相应的处理(硬件自动)
01.设置 sp_irq
02.保存现场 :
在irq异常处理函数中有可能会修改r0-r12, 所以先保存
lr-4是异常处理完后的返回地址, 也要保存
05. 汇编中调用c中断处理函数处理中断
06. 恢复现场

C语言中 :
3.中断发生前

01.main函数由汇编调用,初始化各个中断控制器,配置中断源

4.中断发生时:

由汇编调用中断处理函数,该函数应包含:
01.中断产生时分辨中断源
02.调用对应的处理函数
03.清中断标志

函数指针数组主要用来解决上述 4 中断处理函数的流程调用,未使用函数指针数组时,每配置中断,都需要改变handle_irq_c来完成 4 中的01、02、03,改动interrupt_init来配置对应的MASK位,而使用之后我们就不需要改动了,由上述 2 中的05调用 后可自动配置,也是一种优化,下面是使用函数指针数组改进后的代码:

#include "s3c2440_soc.h"typedef void(*irq_func)(int) ;
irq_func irq_array[32];/* SRCPND 用来显示哪个中断产生了, 需要清除对应位* bit0-eint0* bit2-eint2* bit5-eint8_23*//* INTMSK 用来屏蔽中断, 1-masked* bit0-eint0* bit2-eint2* bit5-eint8_23*//* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位* bit0-eint0* bit2-eint2* bit5-eint8_23*//* INTOFFSET : 用来显示INTPND中哪一位被设置为1*//* 初始化中断控制器 */
// void interrupt_init(void)
// {
// 	INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
// 	INTMSK &= ~(1<<10);  /* enable timer0 int */
// }/* 读EINTPEND分辨率哪个EINT产生(eint4~23)* 清除中断时, 写EINTPEND的相应位*/void key_eint_irq(int irq)
{unsigned int val = EINTPEND;unsigned int val1 = GPFDAT;unsigned int val2 = GPGDAT;if (irq == 0) /* eint0 : s2 控制 D12 */{if (val1 & (1<<0)) /* s2 --> gpf6 */{/* 松开 */GPFDAT |= (1<<6);}else{/* 按下 */GPFDAT &= ~(1<<6);}}else if (irq == 2) /* eint2 : s3 控制 D11 */{if (val1 & (1<<2)) /* s3 --> gpf5 */{/* 松开 */GPFDAT |= (1<<5);}else{/* 按下 */GPFDAT &= ~(1<<5);}}else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */{if (val & (1<<11)) /* eint11 */{if (val2 & (1<<3)) /* s4 --> gpf4 */{/* 松开 */GPFDAT |= (1<<4);}else{/* 按下 */GPFDAT &= ~(1<<4);}}else if (val & (1<<19)) /* eint19 */{if (val2 & (1<<11)){/* 松开 *//* 熄灭所有LED */GPFDAT |= ((1<<4) | (1<<5) | (1<<6));}else{/* 按下: 点亮所有LED */GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));}}}EINTPEND = val;
}void handle_irq_c(void)
{/* 分辨中断源 */int bit = INTOFFSET;/* 调用对应的处理函数 */irq_array[bit](bit);/* 清中断 : 从源头开始清 */SRCPND = (1<<bit);INTPND = (1<<bit);	
}void register_irq(int irq, irq_func fp)
{irq_array[irq] = fp;INTMSK &= ~(1<<irq);
}/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{/* 配置GPIO为中断引脚 */GPFCON &= ~((3<<0) | (3<<4));GPFCON |= ((2<<0) | (2<<4));   /* S2,S3被配置为中断引脚 */GPGCON &= ~((3<<6) | (3<<22));GPGCON |= ((2<<6) | (2<<22));   /* S4,S5被配置为中断引脚 *//* 设置中断触发方式: 双边沿触发 */EXTINT0 |= (7<<0) | (7<<8);     /* S2,S3 */EXTINT1 |= (7<<12);             /* S4 */EXTINT2 |= (7<<12);             /* S5 *//* 设置EINTMASK使能eint11,19 */EINTMASK &= ~((1<<11) | (1<<19));register_irq(0, key_eint_irq);register_irq(2, key_eint_irq);register_irq(5, key_eint_irq);
}

只需在初始化时注册一下相应的处理函数
register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);
待到中断到来时即可自动完成 分辨中断源 、调用对应的处理函数 、清中断这一系列操作,时代码更加便于维护

注:本文参考了
typedef函数指针的用法(C++)

C/C++ 函数指针使用总结

和韦东山老师的讲解,转载请注明出处,本人初学Linux,如有错误,欢迎评论区批评指出,共同进步

更多推荐

函数指针、函数指针数组详解及典型应用

本文发布于:2024-03-13 16:24:26,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1734378.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:指针   函数   数组   详解   典型

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!