每一个函数,都会占据一部分内存单元,这部分内存单元有一个起始地址。指向函数入口起始地址的指针称为函数指针。
数据类型 (*指针变量名字)(参数列表)
首先我们需要注意上面的数据类型是指函数的返回值类型。
其次,我们重点看一下下面两个写法
void (*p)(char a, char b)
void *p(char a, char b)
上面那一句,表示的是一个函数指针。该函数指针所指向的函数返回值为void类型,函数参数为char a与char b。
下面那一句,则是表示一个普通的函数,函数名为p,返回类型为void *。
int sum(int a, int b)
int (*func)(int a, int b)
p = sum;
上面将sum赋值给p的时候,作用是将sum函数的入口地址赋值给p。此时注意不要带上sum的参数。
因为函数指针不是固定指向某一个函数的,而只是定义了一个该类型的变量,它是专门用来存放函数的入口地址的;所以在代码中把哪一个函数的地址赋给它,它就指向哪一个函数,因此也可以指向不同的函数。但是需要注意的是,函数指针不能指向类型不一致的函数,即函数的参数与返回值的类型必须一致。
int mysum(int a, int b) {return a+b;}
int mymax(int a, int b) {return a>b?a:b;}
int mydecrease(int a, int b) {return a-b;}
void printfunc(int a, int b) {cout<<a<<b<<endl;}
void f3() {
int (*p)(int a, int b);
p = mysum; // right
p = mymax; // right
p = mydecrease; // right
p = printfunc; // error
}
以上代码,最后一行编译器会提示错误
assigning to 'int (*)(int, int)' from incompatible type 'void (int, int)': different return type ('int' vs 'void')
定义一个函数指针以后,调用的方式如下
int mydecrease(int a, int b) {return a-b;}
void f3() {
int (*p)(int a, int b);
int result = (*p)(5, 2);
int result2 = p(5, 2); // 与上面的调用方式结果一样
cout<<"result is: "<<result<<endl;
}
注意上面两种调用方式都可以,得到的结果也一致,看个人喜好。有的人更倾向于用第一种方式调用,因为它明确指出是通过指针而非函数名来调用函数。
注意函数指针,指向的只是函数入口地址,而不是函数中间的某条指令,因此*(p+1)这种方式我们无法使用。
函数指针最常见的用法,就是将其作为参数传到其他函数。
我们以测试最常见的冒泡排序算法为例,看看函数指针充当参数的用法。
#include
using namespace std;
void sort(int arr[], int n, bool (*cmp)(int, int));
bool descend(int a, int b);
bool ascend(int a, int b);
void maopao_sort(int arr[], int size, bool (*cmp)(int, int)) {
int tmp;
for(int i=0; i<size-1; i++) {
for(int j=i+1; j<size; j++) {
if (cmp(arr[i], arr[j])) {
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}
bool descend(int a, int b) {
return a > b ? true : false;
}
bool ascend(int a, int b) {
return a > b ? false : true;
}
void maopao_run() {
int arr[] = {1, 3, 5, 2, 4, 6, 0, 9, 8, 7};
maopao_sort(arr, sizeof(arr)/sizeof(arr[0]), ascend);
for(int i=0; i<10; i++) {
std::cout<<arr[i]<<" ";
}
std::cout<<std::endl;
}
我们在maopao_sort方法中,参数有一个bool (*cmp)(int, int)类型的函数指针,分别用来表示其到底是按升序排列还是降序排列。可能有同学会问,这里为什么要搞这么复杂用一个函数指针,搞一个标识符不就OK了?
在我们这个简单的场景中,这么说没毛病。但是,如果更复杂一些的场景,或者说真实场景中,一般不会有这么简单的情况,不存在说有一个标识符就可以解决的问题。我们使用函数指针,把指针函数当作形参传递给某些具有一定通用功能的模块。并封装成接口来提高代码的灵活性和后期维护的便捷性。
我们可以看一个经典的快速排序的例子
void qsort(void *__base, size_t __nel, size_t __width,
int (* _Nonnull __compar)(const void *, const void *));
最后的__compar,就是一个函数指针,该函数指针即用来确定排序规则。通过该函数指针,我们可以实现任何复杂的排序逻辑。而且代码逻辑简单清晰明了,使用起来非常方便。
说到函数指针,还经常会与回调联系在一起。我们先来简单说说何为回调?
把一段代码,像传递参数一样传递给其他code,而这段代码在合适的时机将会被调用,这种情况就被称为回调。像我们第三部分讲的冒泡排序以及快排中的comp,本质都属于回调。
在c++中,回调函数是通过函数指针来实现的。函数指针作为一个参数传递给其他函数,当这个指针被用来调用其所指向的函数时,这就是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
在网络上找到一个函数指针与回调函数的实例比较有代表性,下面就复制过来。
一个GPRS模块联网的小项目,使用过的同学大概知道2G、4G、NB等模块要想实现无线联网功能都需要经历模块上电初始化、注册网络、查询网络信息质量、连接服务器等步骤,这里的的例子就是,利用一个状态机函数(根据不同状态依次调用不同实现方法的函数),通过回调函数的方式依次调用不同的函数,实现模块联网功能,如下:
/********* 工作状态处理 *********/
typedef struct
{
uint8_t mStatus;
uint8_t (* Funtion)(void); //函数指针的形式
} M26_WorkStatus_TypeDef; //M26的工作状态集合调用函数
/**********************************************
** >M26工作状态集合函数
***********************************************/
M26_WorkStatus_TypeDef M26_WorkStatus_Tab[] =
{
{GPRS_NETWORK_CLOSE, M26_PWRKEY_Off }, //模块关机
{GPRS_NETWORK_OPEN, M26_PWRKEY_On }, //模块开机
{GPRS_NETWORK_Start, M26_Work_Init }, //管脚初始化
{GPRS_NETWORK_CONF, M26_NET_Config }, /AT指令配置
{GPRS_NETWORK_LINK_CTC, M26_LINK_CTC }, //连接调度中心
{GPRS_NETWORK_WAIT_CTC, M26_WAIT_CTC }, //等待调度中心回复
{GPRS_NETWORK_LINK_FEM, M26_LINK_FEM }, //连接前置机
{GPRS_NETWORK_WAIT_FEM, M26_WAIT_FEM }, //等待前置机回复
{GPRS_NETWORK_COMM, M26_COMM }, //正常工作
{GPRS_NETWORK_WAIT_Sig, M26_WAIT_Sig }, //等待信号回复
{GPRS_NETWORK_GetSignal, M26_GetSignal }, //获取信号值
{GPRS_NETWORK_RESTART, M26_RESET }, //模块重启
}/**********************************************
** >M26模块工作状态机,依次调用里面的12个函数
***********************************************/
uint8_t M26_WorkStatus_Call(uint8_t Start)
{
uint8_t i = 0;
for(i = 0; i < 12; i++)
{
if(Start == M26_WorkStatus_Tab[i].mStatus)
{
return M26_WorkStatus_Tab[i].Funtion();
}
}
return 0;
}
所以,如果有人想做个NB模块联网项目,可以copy上面的框架,只需要修改回调函数内部的具体实现,或者增加、减少回调函数,就可以很简洁快速的实现模块联网。
https://blog.51cto.com/u_13933750/3229829