我们一起聊聊指针与函数

开发 前端
日常开发中,我还真没找到指针函数的“优点”,让我觉得某个功能必须用指针函数实现,或用指针函数实现后代码更整洁,提高代码可读性。

指针函数

指针函数,从名字上看它本质上是一个函数。指针函数:返回值类型是指针的函数。函数声明如下:

int *plusfunction(int a,int b);

当然也可以写成如下格式:

int* plusfunction(int a,int b);

让指针标志 * 与int紧贴在一起,而与函数名f间隔开,这样看起来就明了些了,plusfunction是函数名,返回值类型是一个int类型的指针。

指针函数就是一个普通的函数,普通到仅仅是因为它的函数返回值是指针而已。

#include <stdio.h>
#include <stdlib.h>
int* plusfunction(int a,int b);
int main()
{
int *p = NULL;
p = plusfunction(1,2);
printf("*p is %d\n",*p);
free(p);
return(0);
}

int* plusfunction(int a,int b)
{
int *p = (int *) malloc( sizeof(int) );
*p = a + b;
return(p);
}

这是一个简单的指针函数的例子,运行结果如下,本文代码在VScode平台运行,使用方法《使用VScode调试C语言》。

不过我有个疑问,使用指针函数,和函数入参是指针有什么好处呢???

#include <stdio.h>
#include <stdlib.h>
void plusfunction(int a,int b,int *p);
int main()
{
int *p = NULL;
p = (int *) malloc(sizeof(int) );
plusfunction(1,2,p);
printf("*p is %d\n",*p);
free(p);
return(0);
}

void plusfunction(int a,int b,int *p)
{
*p = a + b;
}

这样执行也是没问题的啊,当然我也发现了指针函数的好处,就是可以把函数作为另一个函数的入参。

testfunction(plusfunction(1,2));

在这点上用第二种方法,将指针作为函数入参是不行的。

还有,将指针作为函数入参前需要向指针申请内存,而指针函数却不用。

除去这两点,日常开发中,我还真没找到指针函数的“优点”,让我觉得某个功能必须用指针函数实现,或用指针函数实现后代码更整洁,提高代码可读性。

函数指针

函数指针,本质上他是一个指针,并不是一个函数。在C语言中有些概念是一脉相承的,之前的推文《指针与数组》,数组指针和指针数组的概念更有效帮你理解函数指针和指针函数。函数指针说的就是一个指针,但这个指针指向的函数,不是普通的基本数据类型或者类对象。函数指针定义如下:

int (*f)(int a,int b);//声明函数指针

和指针函数的定义对比可以看到,函数指针与指针函数的最大区别是函数指针的函数名是一个指针,即函数名前面有一个指针类型的标志型号“*”。注意指针函数与函数指针表示方法的不同,千万不要混淆。最简单的辨别方式就是看函数名前面的指针*号有没有被括号()包含,如果被包含就是函数指针,反之则是指针函数。当然,函数指针的返回值也可以是指针。简单的函数调用示例:

#include <stdio.h>
void MyFun(int a);
int main()
{
MyFun(10);
return(0);
}
void MyFun(int a)
{
printf("a is %d\n",a);
}

这是一个再简单不过的函数调用了,其实他还可以写作下面格式:

#include <stdio.h>
void MyFun(int a);
int main()
{
(*MyFun)(10);
return(0);
}
void MyFun(int a)
{
printf("a is %d\n",a);
}

这个代码是正常运行的,也就是说(*MyFun)(10);和MyFun(10);是一样的,在这里强烈建议没有看过《指针与数组》的同学,先看一下。在教材和资料中,都会讲到数组名就是指向数组第一个数据的常量指针。从上面例子看到,函数名貌似也是“常量指针”。数组中,可以将数组名赋给一个指针,然后通过指针访问数组中的内容,那么我们就可以定义一个函数指针,将函数名赋给函数指针,通过这个函数指针调用函数。

#include <stdio.h>
void MyFun(int a);/* 这个声明也可写成:void MyFun( int )*/
void (*FunP)(int);/*也可声明成void(*FunP)(int x),但习惯上一般不这样。 */
int main()
{
FunP = MyFun;
*FunP(10);
return(0);
}
void MyFun(int a)
{
printf("a is %d\n",a);
}

在第7行在函数指针前加*相当取指针的值,在这里理解为将MyFun函数取出。那么再进一步:

#include <stdio.h>
void MyFun(int a); /* 这个声明也可写成:void MyFun( int )*/
void (*FunP)(int); /*也可声明成void(*FunP)(int x),但习惯上一般不这样。 */
int main()
{
FunP = MyFun;
FunP(10);
return (0);
}
void MyFun(int a)
{
printf("a is %d\n", a);
}

是的,将FunP前面的*号拿掉也是可以运行的,上面的示例代码就是函数指针在C语言中的最常见形态。之前的例子只是为了让你更能理解函数指针,实际开发中只需要用函数指针的最终,最常见的形态即可。

不然代码中出现之前的形式,其他程序员并不是很熟悉,就成了“骚操作”,虽然不影响运行,但是降低代码的可阅读性。

typedef的引入

C语言中typedef关键字作用:复杂的声明定义简单的别名,很明显我们上面讲述的函数指针就是一个比较复杂的类型,可以使用typedef关键字将函数指针的定义简单化。

#include <stdio.h>
void MyFun(int a); /* 这个声明也可写成:void MyFun( int )*/
typedef void (*FunType)(int); /*这样只是定义一个函数指针类型 */
FunType FunP; /*然后用FunType类型来声明全局FunP变量*/
int main()
{
FunP = MyFun;
FunP(10);
return (0);
}
void MyFun(int a)
{
printf("a is %d\n", a);
}

强烈建议使用typedef和函数指针组合的方式,这是最常见的方式,大家都能看懂的常规操作。在C语言的教程中typedef用于取别名,形式下:

typedef 旧名字 新名字;

确实也是这样,但遇到给函数指针类型、数组类型等定义别名的时候就要特别区分了。如:


typedef char ARRAY20[20];
ARRAY20 a1,a2; /* 等价于char a1[20],a2[20]; */

typedef void (*FunType)(int); /*这样只是定义一个函数指针类型 */
FunType FunP; /*然后用FunType类型来声明全局FunP变量*/

别问我为什么,因为我也不知道。

当然,并不是说用到了函数指针就要用typedef定义一下,一般在结构体中使用函数指针就不会使用typedef,如下:

typedef struct
{
uint8_t data;
void (*FunP)(int);
}Mode_Typedef;

以上均为个人建议,没有优劣,大家根据自己的习惯做即可。

函数指针作为入参

既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。所以,你还应知道函数指针是如何作为某个函数的参数来传递使用的。示例代码如下:

#include <stdio.h>
void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void (*FunType)(int); /* ②. 定义一个函数指针类型FunType,与①函数类型一致 */
void CallMyFun(FunType fp, int x);
int main(int argc, char *argv[])
{
CallMyFun(MyFun1, 10); /* ⑤. 通过CallMyFun函数分别调用三个不同的函数 */
CallMyFun(MyFun2, 20);
CallMyFun(MyFun3, 30);
}
void CallMyFun(FunType fp, int x) /* ③. 参数fp的类型是FunType。*/
{
fp(x); /* ④. 通过fp的指针执行传递进来的函数,注意fp所指的函数是有一个参数的。 */
}
void MyFun1(int x) /* ①. 这是个有一个参数的函数,以下两个函数也相同。 */
{
printf("MyFun1:%d\n", x);
}
void MyFun2(int x)
{
printf("MyFun2:%d\n", x);
}
void MyFun3(int x)
{
printf("MyFun3:%d\n", x);
}

运行结果如下:

可以看到,CallMyFun函数的参数是一个指针,当这个函数指针有参数时,需要通过另外增加一个参数来保存回调函数的参数值,同理也可以使用多个参数的函数指针。

单片机IAP

在单片机OTA时常用到函数指针,代码如下:

typedef void (*IapFun)(void);//定义一个函数指针
IapFun Jump_To_Application;//定义函数指针对象
if (((*(__IO uint32_t*)appxaddr) & 0x2FFE0000 ) == 0x20000000)//检查地址是否有效
{
Jump_To_Application = (iapfun) * (__IO uint32_t *)(appxaddr + 4);//用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP(*(__IO uint32_t *)appxaddr);//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
Jump_To_Application();//跳转app
}

这里直接将地址强制转换成函数指针,然后执行这个函数。appxaddr地址就是新固件存储的起始地址,appxaddr+4的位置就是新固建中的Reset_Handler函数,相当于执行了新固件中的Reset_Handler。

责任编辑:武晓燕 来源: 知晓编程
相关推荐

2024-02-26 00:00:00

架构老化重构

2024-02-20 21:34:16

循环GolangGo

2021-08-27 07:06:10

IOJava抽象

2022-09-07 07:27:36

函数元素

2023-08-04 08:20:56

DockerfileDocker工具

2023-08-10 08:28:46

网络编程通信

2023-06-30 08:18:51

敏捷开发模式

2022-05-24 08:21:16

数据安全API

2023-09-10 21:42:31

2024-01-29 09:01:20

React列表模式

2023-07-04 08:06:40

数据库容器公有云

2023-12-06 08:26:19

Service数据库

2022-11-12 12:33:38

CSS预处理器Sass

2022-02-14 07:03:31

网站安全MFA

2024-02-26 00:00:00

Go性能工具

2023-07-27 07:46:51

SAFe团队测试

2022-12-07 13:12:15

2022-10-28 07:27:17

Netty异步Future

2022-06-26 09:40:55

Django框架服务

2023-12-28 09:55:08

队列数据结构存储
点赞
收藏

51CTO技术栈公众号