该文章参考了网络上的部分代码(关于三星的那一部分),但是网络代码部分有错,在此我已经改正
(一)基本用法
不通过函数名调用函数,注意!!对于函数指针p, (*p)(5)和p(5)等价,都可以调用函数(实测过),(*p)(5)不能写成*p(5)
#include <stdio.h>
void printInt(int n);
typedef void (*pFunc_t)(int);//给返回值为void 参数为一个int的函数指针重命名,命名为pFunc_t
int main(void){
pFunc_t p = printInt;//如果没有重命名,不可以使用此句,应该为:void (*p)(int) = printInt;
p(5);//或者(*p)(5)
return 0;
}
void printInt(int n){
printf("%d\n", n);
}
(二) 应用场景
-
将函数指针作为参数(本质:调用函数在不同的场景需要不同的函数指针作为参数)
先来个简单的,比如,编程的时候,经常用到for循环,想偷懒,可将其封装成函数。(当然实际开发应该没有人这么干,只适合用来举例学习,for循环并没有繁杂到非得去写一个函数,我们假设for循环很累人,需要把它封装为函数)
#include <stdio.h> void print(int *n); void opposite(int *n); void myabs(int *n); typedef void (*pFunc_t)(int*); void for_each(int *arr, int size, pFunc_t p); int main(void){ pFunc_t pri = print; pFunc_t opp = opposite; pFunc_t abs = myabs; int arr[]={1,2,3,-4,-5,-6}; for_each(arr, 6, pri); printf("\n"); for_each(arr, 6, opp); for_each(arr, 6, pri); printf("\n"); for_each(arr, 6, abs); for_each(arr, 6, pri); printf("\n"); return 0; } /* * 该循环函数,下标为0~size-1,自增1 * 该函数用来遍历数组,并对数组中单个数进行操作 * arr :数组指针 * size:数组大小 * p: 对每个数进行的操作 * */ void for_each(int *arr, int size, pFunc_t p){ int i = 0; for(i = 0; i<size; i++){ p(arr+i); } } //打印指针指向的值 void print(int *n){ printf("%d ", *n); } //将指针指向的值取反 void opposite(int *n){ *n = -*n; } //将指针指向的值取绝对值 void myabs(int *n){ *n = *n < 0 ? -(*n) : *n; }
如果还没体会到函数指针在这里发挥的作用,可以这样理解:以前写程序,当遇到“打印数组的值”“取反数组的值”“将数组的值取绝对值”这种需求:
-
我们通常写成三个函数,每个函数都写一遍for循环,3个函数之间的唯一区别就在于for循环遍历时,对数组中的值所进行的操作不一样,这种方法是代码量最大的一种写法。
-
写一个函数进行遍历,该函数参数除了数组指针,数组大小外,还应有一个变量值,作为标记,代码如下。在for循环遍历时,通过if语句对标记变量进行判断,以执行不同的操作。这样的坏处在于增加了耦合度(产生了控制耦合),独立的代码聚集在if语句的各个分支:耦合度低的一个特点就是你不知道该怎么给函数起名了,这个函数,标记为1时干这个活儿,标记为2时干那个活儿,这个函数到底叫打印数组,还是叫取反数组?如果我新加上flag == 3, flag ==4时,功能更多了怎么办?所以有了函数指针。
for_each(int *arr, int size, int flag){ for(int i=0; i<size; i++){ if(flag==1){ //打印数值 }else if(flag == 2){ //取反数值 }else if(flag ==3){ //取绝对值 } else{ //... ... } } }
-
函数指针的写法。函数指针相比于上一种依赖flag的方法,最直观上的体验就是,我可以给函数起名了:该函数完成了循环遍历数组并执行pFunc_t p功能。那为什么2.中的函数不可以这样描述为:该函数完成了循环遍历数组并执行flag功能?因为flag只是一个变量,而pFunc_t p函数指针真真实实的就是一个功能,另外,flag取值数量很庞大时,我们还需要额外的开销来记录每一个flag值对应的功能是什么。最重要的是,依赖flag来控制,严重降低了模块化。比如:在函数指针的实现方法中,当我们在.c的其他文件处想对一个数进行取反的时候,我可以调用opposite方法,也可以用函数指针进行调用,但flag的那种不行,因为flag实现的for_each,其中的各种操作(比如取反、打印、取绝对值)都是直接写在for_each里面的,没有现成的函数供使用,无法调用,并且每当我有新的需求需要增加功能的时候,我都需要重新修改for_each,得不偿失
再来一个例子体会一下这种思想,例子来源 分析函数指针及其两个主要用途
#include <stdio.h> //Calculate用于计算积分。一共三个参数。第一个为函数指针func,指向待积分函数。二三参数为积分上下限 double Calculate(double(*func)(double x), double a, double b) { double dx = 0.0001;//细分的区间长度 double sum = 0; for (double xi = a+dx; xi <= b; xi+=dx) { double area = func(xi)*dx; sum +=area; } return sum; } double func_1(double x) { return x*x; } double func_2(double x) { return x*x*x; } void main() { printf("%lf\n", Calculate(func_1, 0, 1)); printf("%lf\n", Calculate(func_2, 0, 1)); }
-
-
引用不在代码段中的函数
来自 分析函数指针及其两个主要用途 他的调用方法稍微不对,在此已经改正。此功能在嵌入式系统中经常使用。我们知道,我们写的用户程序的code是存放在代码段中的,在嵌入式系统中,一般情况下是存放在flash中的。什么叫不在代码段中的函数?很多微控制器在出厂前会将一些功能函数(系统函数)固化在rom中(类似于PC机中的BIOS),如Flash擦写功能,Flash Copy功能。而我们写的代码是不认识这些函数的,不能直接使用函数名调用。所以,当我们想在用户程序中调用这些系统函数时,就只能使用函数指针的方式,通过将系统函数的入口地址传给函数指针,来达到调用rom中程序的目的。这些系统函数一般都会在官方手册中给出功能,返回值类型和参数列表。
下面是从三星的S5PV210_applicationnote中截取的一个系统函数。
使用define来定义函数指针
#define CopySDMMCtoMem(z,a,b,c,e)(((bool(*)(int, unsigned int, unsigned short, unsigned int*, bool))(*((unsigned int *)0xD0037F98)))(z,a,b,c,e))
从上我们可以分析出,此系统函数的入口地址为0xD0037F98。返回bool型,带有int, unsigned int, unsigned short, unsigned int*, bool型五个参数。实际使用时,我们可以如下调用:
//方法一 #define CopySDMMCtoMem(z,a,b,c,e)(((bool(*)(int, unsigned int, unsigned short, unsigned int*, bool))(*((unsigned int *)0xD0037F98)))(z,a,b,c,e)) CopySDMMCtoMem(1,1,1,1,1); //方法二 typedef bool (*CopySDMMCToMem_t)(int, unsigned int, unsigned short, unsigned int*, bool); CopySDMMCToMem_t p = (CopySDMMCToMem_t)0xD0037F98; p(1,1,1,1,1);//或者 (*p)(1,1,1,1,1);