c++ 函数指针实例详解

1.函数指针

1.1 什么是函数指针

每一个函数,都会占据一部分内存单元,这部分内存单元有一个起始地址。指向函数入口起始地址的指针称为函数指针。

1.2 函数指针的语法

1.2.1 如何定义函数指针
数据类型 (*指针变量名字)(参数列表)

首先我们需要注意上面的数据类型是指函数的返回值类型。

1.2.2 注意括号

其次,我们重点看一下下面两个写法

void (*p)(char a, char b)
void *p(char a, char b)

上面那一句,表示的是一个函数指针。该函数指针所指向的函数返回值为void类型,函数参数为char a与char b。
下面那一句,则是表示一个普通的函数,函数名为p,返回类型为void *。

1.2.3 赋值只需给出函数名,无需参数。
int sum(int a, int b)
int (*func)(int a, int b)
p = sum;

上面将sum赋值给p的时候,作用是将sum函数的入口地址赋值给p。此时注意不要带上sum的参数。

1.2.4 函数指针可以指向先后不同的函数

因为函数指针不是固定指向某一个函数的,而只是定义了一个该类型的变量,它是专门用来存放函数的入口地址的;所以在代码中把哪一个函数的地址赋给它,它就指向哪一个函数,因此也可以指向不同的函数。但是需要注意的是,函数指针不能指向类型不一致的函数,即函数的参数与返回值的类型必须一致。

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')
1.2.5 函数指针的调用

定义一个函数指针以后,调用的方式如下

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;
}

注意上面两种调用方式都可以,得到的结果也一致,看个人喜好。有的人更倾向于用第一种方式调用,因为它明确指出是通过指针而非函数名来调用函数。

1.2.6 不能使用*(p+1)的格式来表示函数下一条指令

注意函数指针,指向的只是函数入口地址,而不是函数中间的某条指令,因此*(p+1)这种方式我们无法使用。

3.函数指针充当参数

函数指针最常见的用法,就是将其作为参数传到其他函数。

我们以测试最常见的冒泡排序算法为例,看看函数指针充当参数的用法。

#include<iostream>
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,就是一个函数指针,该函数指针即用来确定排序规则。通过该函数指针,我们可以实现任何复杂的排序逻辑。而且代码逻辑简单清晰明了,使用起来非常方便。

4.函数指针用于回调

说到函数指针,还经常会与回调联系在一起。我们先来简单说说何为回调?
把一段代码,像传递参数一样传递给其他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

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值