嵌入式C语言基础知识 -- 函数指针&回调函数&结构体指针

2023-05-16

目录

一. 函数指针: 什么是函数指针?

 函数指针的三种定义方式:

(1)先定义出函数的类型,再通过类型定义函数指针变量

(2)先定义出函数指针的类型,再通过指针类型定义函数指针变量

(3)重点:直接定义函数指针变量

 函数指针和指针函数的区别:

二. 回调函数

实例一:

实例二:实例一的具体实现。

实例三:固件开发中使用到的一个回调函数实例。

三. 结构体指针

示例1:结构体指针的使用

示例2:指向结构体变量的指针 & 结构体嵌套

示例3:结构体数组指针的使用

示例4:结构体指针作为函数参数


一. 函数指针: 什么是函数指针?

指向函数入口地址的指针。

如果在程序中定义了一个函数,那么编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址函数名表示的就是这个地址

既然是地址,就可以定义一个指针变量来存放,这个指针变量就叫做函数指针变量。

函数指针的定义:

# 返回值类型 +(指针变量名)(形参列表)
eg:
int (*p)(int, int);

 函数指针的三种定义方式:

(1)先定义出函数的类型,再通过类型定义函数指针变量

//定义出一个函数类型,返回值是void,形参列表(int,char)
typedef void(FUNC_TYPE)(int, char);

FUNC_TYPE * pFunc = func;

(2)先定义出函数指针的类型,再通过指针类型定义函数指针变量

typedef void(*FUNC_TYPE)(int, char);

FUNC_TYPE pFunc = func;

(3)重点:直接定义函数指针变量

void(*p) (int, char) = func;

解释:

定义了一个指针变量 p ,该指针变量可以指向返回值类型为 void,且有两个整型参数的函数。p 的类型为 void(*)(int, char)。

注意:

(*p)两端的括号不能去掉。如果省略了括号,就变成一个函数声明了,即声明了一个返回值类型为指针型的函数,即变成了指针函数。

 函数指针和指针函数的区别:

# 指针函数本质是一个函数,其返回值是一个指针:
int* p(int, int);

# 函数指针本质是一个指针,其指向一个函数:
int (*p)(int, int);

简单点就是:函数名带括号的就是函数指针,否则就是指针函数。

同理,数组指针和指针数组的区别:

# 数组指针是一个指针,指向一个数组:
int (*p)[ ];

# 指针数组是一个数组,数组的元素都是指针:
int* p[ ];

以下实例声明了函数指针变量p,指向函数func_max:

#include<stdio.h>

int func_max(int x, int y)
{
	return x>y ? x:y;           // ?:是条件运算符,如果条件为真,则值为 x ,否则值为 y。
}

int main(void)
{
	int (*p)(int, int)= &func_max;   //p是函数指针,&可以省略
	int a, b, c, d;
	printf("请输入三个数字:");
	scanf("%d %d %d", &a, &b, &c);
	d=p(p(a,b),c);              //与直接调用函数等价,d=max(max(a,b),c)
	printf("最大的数字是: %d\n", d);
	return 0;
}

输出为:

请输入三个数字:1 2 3
最大的数字是: 3

二. 回调函数

函数指针变量可以作为某个函数的参数来使用,回调函数就是一个通过函数指针调用的函数。

也就是,你写一个函数A,并把这个函数A的地址赋值给一个函数指针,然后把这个函数指针当作参数赋给另一个函数B,函数B通过函数指针来调用函数A。这个函数A就是回调函数。

为什么要使用回调函数呢?

答:解耦。

实例一:

#include<stdio.h>
#include<softwareLib.h> // 包含Library Function所在的Software library库的头文件

int Callback() // Callback Function
{
    // TODO
    return 0;
}
int main() // Main program
{
    // TODO
    Library(Callback);
    // TODO
    return 0;
}

 仔细一看可以发现:在回调中,主程序把回调函数像参数一样传入库函数。这样我们只需改变传进库函数的参数,就可以实现不同的功能,并且丝毫不需要修改库函数的实现,这就是解耦

实例二:实例一的具体实现。

回调函数的作用 

 比如,我们写A B C D 四个函数,封装成一个库文件,然后我们的主函数里面要写一个功能函数,这个功能要用到函数A,假如不用函数指针,这个功能函数就要调用函数A,下次如果用到函数B,那么我们得删掉A,调用函数B,每次都要修改这个函数很麻烦,但如果使用回调函数就不一样了,我们可以定义4个函数指针,把4个函数的地址分别赋给4个函数指针,然后将函数指针当作参数传递给功能函数,功能函数就可以通过修改参数来调用对应的函数,而它本身不用做任何的修改。这样的话,功能函数就可以根据不同的情况,通过函数指针去调用不同的函数,代码如下:

#include <stdio.h>
#include <stdlib.h>

# 4个回调函数
float ADD(float a, float b)
{
    return a + b;
}

float SUB(float a, float b)
{
    return a - b;
}

float MUL(float a, float b)
{
    return a * b;
}

float DIV(float a, float b)
{
    return a / b;
}

# 4个函数指针
float (*A)(float x, float y) = ADD;
float (*B)(float x, float y) = SUB;
float (*C)(float x, float y) = MUL;
float (*D)(float x, float y) = DIV;

# 函数指针作为函数参数,以此来调用回调函数
float fun(float x, float y, float(*p)(float x, float y)) 
{
    return p(x, y);
}

int main()
{
    printf("%f", fun(2, 3, A));

}

实例三:固件开发中使用到的一个回调函数实例。

 /** \brief function pointer type to void/void function  **/
typedef void (*func_ptr_t)(void);   # 定义一个函数指针类型

 /** \brief IRQ registration structure definition  **/
typedef struct stc_irq_regi_conf
{
	en_int_src_t enIntSrc;
	IRQn_Type enIRQn;
	func_ptr_t pfnCallback;         # 定义一个函数指针变量
}stc_irq_regi_conf_t;

 /** \brief generic error codes  **/
typedef enum en_result
{
	Ok            = 0u;
	Error         = 1u;
	ErrorTimeout  = 2u;
	ErrorNotReady = 3u;
}en_result_t;

/**
 * @brief Timer0 interrupt callbackfunc
 */
static void Timer0_CallBack(void)
{
	//ToDo
}

void TIM0_Init(void)
{
	stc_irq_regi_conf_t stcIrqRegiConf;
	
	/* Register TMR_INI_GCMA Int to Vect.No.001 */
    stcIrqRegiConf.enIRQn = TIMER01_CHA_IRQn;    
    /* Select I2C Error or Event interrupt function */
    stcIrqRegiConf.enIntSrc = TMR_INI_GCMA; 
	/* Callback function */
    stcIrqRegiConf.pfnCallback =&Timer0_CallBack;  # 将回调函数的地址赋给函数指针 
	
	/* Registration IRQ */
    enIrqRegistration(&stcIrqRegiConf);
}

/**
 *******************************************************************************
 ** \brief IRQ Registration
 **
 ** param [in]                          pstcIrqRegiConf, IRQ registration
 **                                     configure structure
 **
 ** retval                              Ok, IRQ register successfully.
 **                                     ErrorInvalidParameter, IRQ No. and
 **                                     Vector No. are not match.
 **                                     ErrorUninitialized, Make sure the
 **                                     Interrupt select register (INTSEL) is
 **                                     default value (0x1FFu) before setting.
 **
 *****************************************************************************/
 # 函数指针作为该函数参数,通过函数指针调用回调函数
en_result_t enIrqRegistration(const stc_irq_regi_conf_t *pstcIrqRegiConf)
{
    //库函数IRQ Registration

}

三. 结构体指针

当一个指针变量指向结构体时,就称它为结构体指针。其定义方式为:

struct 结构体名 *变量名;

定义结构体指针的 eg 如下:

typedef struct stu{                  //定义结构体
    char *name;
    int age;
    char group;
    float score;
}stu;

stu stu1 = {"Tom", 18,'A', 136.5};

stu *pstu = &stu1;    //结构体指针

注意:

结构体变量名和数组名不同。数组名在表达式中会被转换为数组指针,而结构体变量名不会,要想取得结构体变量的地址,必须在前面加 & ,如下所示:

struct stu *pstu = &stu1;

还应该注意,结构体和结构体变量是两个不同的概念:结构体是一种数据类型,就像int、float这些关键字本身不占用内存一样;结构体变量才包含数据,才需要内存来存储。比如下面这种写法,是错误的,不可能去取一个结构体名的地址。

struct stu *pstu = &stu;

示例1:结构体指针的使用

#include<stdio.h>

typedef struct stu
{
	char *name;
	int num;
	char group;
	float score;
}stu, *pstu;

int main()
{
    stu stu1 = {"Tom", 12, 'A', 136.5};

	pstu pstu1 = &stu1;

    printf("%s %d %c %.1f \n", stu1.name, stu1.num, stu1.group, stu1.score);
    printf("%s的学号是%d,在%c组,今年的成绩是%f.\n", pstu1->name, pstu1->num, pstu1->group, pstu1->score);

	return 0;
}

输出为:

Tom的学号是12,在A组,今年的成绩是136.5。

示例2:指向结构体变量的指针 & 结构体嵌套

#include<stdio.h>
#include<string.h>

typedef struct AGE
{
    int year;
    int month;
    int day;
}age;

typedef struct STUDENT
{
    char name[20];
    int num;
    age birthday;
    float score;
}student;

int main(void)
{
	student student1;    //用struct STUDENT结构体类型定义结构体变量student1
#if 0
	student* p = NULL;   //定义一个指向struct STUDENT结构体类型的指针变量p
	p = &student1;              // *p指向结构体变量student1的首地址,即第一个成员的地址
#else
    student* p = &student1;
#endif
	strcpy(p->name,"小明");       //(*p).name等价于student1.name
	p->birthday.year = 1994;
	p->birthday.month = 9;
	p->birthday.day = 18;
	p->num = 1207041;
	p->score = 100;
	printf("name: %s\n", p->name);
	printf("birthday: %d-%d-%d\n", p->birthday.year, p->birthday.month, p->birthday.day);
	printf("num: %d\n", p->num);
	printf("score: %.1f\n", p->score);

	return 0;
}

输出为:

name: 小明
birthday: 1994-9-18
num: 1207041
score: 100.0

示例3:结构体数组指针的使用

如果定义一个结构体指针变量,并把结构体数组的数组名赋给这个指针变量的话,就意味着将结构体数组的第一个元素,即第一个结构体变量的地址,也即第一个结构变量中的第一个成员的地址赋给了这个指针变量。 

#include<stdio.h>

struct stu{
	char *name;
	int num;
	char group;
	float score;
};

int main(void)
{
    struct stu stus[] = {{"jack", 5, 'A', 12.5},{"rose", 4, 'B', 123.5},{"paul", 3, 'C', 1234.5}};
    struct stu *p = stus;   //定义结构体数组指针p,并指向结构体数组的第一个元素即stus[0].
	int len = sizeof(stus)/sizeof(struct stu);
	for(p = stus; p < stus+len; p++)    // p+1 就指向stus[1]。
	{
		printf("%s\t\t%d\t%c\t%f\n", p->name, p->num, p->group, p->score);
	}

	return 0;
}

 输出为:

jack		5	A	12.500000
rose		4	B	123.500000
paul		3	C	1234.500000

示例4:结构体指针作为函数参数

如果结构体成员较多,尤其是成员为数组时,传送的时间和空间开销会很大,影响程序效率。所以最好的办法就是使用结构体指针。

eg:计算全班学生的总成绩、平均成绩、140分以下的人数。

#include<stdio.h>

typedef struct stu
{
    char* name;
    int num;
    char group;
    float score;
}stu;
stu stus[] = {
    {"jack", 5, 'A', 12.5},
    {"rose", 4, 'B', 123.5},
    {"paul", 3, 'C', 1234.5}
};

void average(struct stu* ps, int len);

int main()
{
    int len = sizeof(stus) / sizeof(struct stu);
    average(stus, len);

    return 0;
}

void average(struct stu* ps, int len)
{
    int i, num_140 = 0;
    float average, sum = 0;
    for (i = 0; i < len; i++)
    {
        sum += (ps + i)->score;
        if ((ps + i)->score < 140)
        {
            num_140++;
        }
    }
    printf("sum=%.2f\naverage=%.2f\nnum_140=%d\n", sum, sum / 5, num_140);
}

输出为:

sum=1370.50
average=274.10
num_140=2

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

嵌入式C语言基础知识 -- 函数指针&回调函数&结构体指针 的相关文章

  • 我程序人生的启蒙书

    是这本书 xff0c 大一的我接触了c和c 43 43 xff0c 为数学专业的我打开了通往另一个世界的道路 xff0c 做一名优秀的程序员 是这本书 xff0c 大一的我开始废寝忘食的学习 xff0c 自习室里往往就放着这一本数 xff0
  • C++面试题(三)——STL相关各种问题

    C 43 43 面试题 STL相关各种问题 tanglu2004 http blog csdn net worldwindjp STL相关的各种问题 1 xff0c 用过那些容器 最常用的容器就是 xff1a vector list map
  • 基于 Docker 搭建开发环境

    基于 Node 官方镜像 https hub docker com node 获取镜像 在本地 Terminal 中执行 docker pull node 以获取 node 镜像 xff0c 可在 docker desktop 中查看 创建
  • tomcat 9 与mysql 5 的连接

    1 jdk的安装 配置JAVA HOME变量 xff0c 将该变量设置到path中 2 tomcat 下载 最新版本apache tomcat 9 0 0 M9配置根目录CATALINA HOME 61 D apache tomcat 9
  • 刷完 LeetCode 是什么水平?能拿到什么水平的 offer?

    链接 xff1a https www zhihu com question 32019460 编辑 xff1a 深度学习与计算机视觉 声明 xff1a 仅做学术分享 xff0c 侵删 刷题是我们一贯的学习方式 xff0c 但是学霸和学渣的区
  • 开心网争车位 发布

    本软件第一次使用C 编写 xff0c 是开心001争车位的辅助软件 可以帮助你管理多个账号一起停车挣钱 xff0c 使支持和热爱 开心网 的玩家更方便 xff0c 请勿与用商业 使用方法 xff1a 本软件需依靠 net3 5 xff0c
  • 博士“申请考核制”经验

    作者 xff1a 花花 链接 xff1a https zhuanlan zhihu com p 126168158 本文转载自知乎 xff0c 作者已授权 xff0c 未经许可请勿二次转载 本文是作者真实的经历 xff0c 给打算申请国内院
  • 研究生新生要怎么看论文?

    链接 xff1a https www zhihu com question 304334959 编辑 xff1a 深度学习与计算机视觉 声明 xff1a 仅做学术分享 xff0c 侵删 问题 xff1a 经常各种看不懂论文 而且感觉好多论文
  • 考博热会出现吗?

    链接 xff1a https www zhihu com question 408008199 编辑 xff1a 深度学习与计算机视觉 声明 xff1a 仅做学术分享 xff0c 侵删 考研热已经是不争的事实了 xff0c 每年考研人数都在
  • 基于python的MongoDB入门教程

    总览 MongoDB是数据科学家常用的一种非结构化数据库本文我们讨论如何使用Python xff08 和PyMongo库 xff09 来使用MongoDB数据库 本文我们使用Python实现对MongoDB数据库的所有基本操作 结构化数据库
  • 同组博士师兄的结果复现不出来,我应该怎么办?

    链接 xff1a https www zhihu com question 502804990 编辑 xff1a 深度学习与计算机视觉 声明 xff1a 仅做学术分享 xff0c 侵删 今年研二 xff0c 老师给了一个课题 xff0c 让
  • SOC电源标志 说明 VCC、VSS、VDD、VEE、VPP、Vddf

    VBAT VBAT是电源电压 xff0c VCC xff1a C 61 circuit 表示电路的意思 即接入电路的电压 VDD xff1a D 61 device 表示器件的意思 即器件内部的工作电压 VSS xff1a S 61 ser
  • “error LNK2019: 无法解析的外部符号”原因总结

    C 43 43 工程编译时出现如下链接错误提示 xff1a 原因一 xff1a 缺少实现 只是在 h里面声明了某个方法 xff0c 没有在cpp里面实现 xff1b 我出现过这个问题 xff1b 类方法的实现未加类标识 xff1a 如 xf
  • 不支持S/W HEVC(H265)解码的有效解决方案

    最近从WIN7更换为WIN10后 xff0c PotPlayer播放器加速出现不同步情况 xff0c 网上查找了很多办法 xff0c 最终奏效 失败方法一 xff1a FFmpeg64 dll 下载FFmpeg64 dll xff08 ht
  • Win10打开任务管理器卡死的解决方法

    我的情况是刚开始装了win10家庭版 xff0c 但是安装一些软件后 xff0c 过段时间打开任务管理器就会莫名其妙的卡死 xff0c 我去重新装了原装系统 xff0c 换了固态硬盘 xff0c 清理了电脑 xff0c 还是会出现这个问题
  • 基于双目视觉的非标机械臂的空间定位流程(未完待续)

    文章目录 系统坐标系变换原理双目标定原理准备步骤 图像极线校正 对应点匹配 空间定位图像校正计算视差计算深度目标点空间定位 三维重建手眼标定 xff08 eye in hand xff09 问题故障解决下一步计划参考 系统 接上一次的非标机
  • 如何用VC++60编写查看二进制文件程序

    雷霆工作室 韩燕 在计算机应用中 xff0c 经常需要查看二进制文件的内容 目前 xff0c 在各种VC 43 43 书籍中介绍查看文本文件的文章很多 xff0c 但鲜有介绍查看二进制文件的文章 本文从功能设计 方案设计 编程实现以及技术要
  • Matlab代码导入STM32F103流程

    文章目录 软件准备STM32CubeMX简介配置STM32CUBEMX配置SIMULINKSIMULINK对STM32F103进行点灯试验一般算法导入到STM32问题故障解决参考 软件准备 安装MATLAB2019a xff0c 64位 下
  • 树莓派利用OpenCV的图像跟踪、人脸识别等

    文章目录 准备配置测试程序颜色识别跟踪人脸识别手势识别形状识别条码识别二维码识别 故障问题解决module 39 cv2 39 has no attribute 39 dnn 39 ImportError numpy core multia
  • Linux(ubuntu)安装AppImage步骤

    方法一 设置允许执行文件 xff0c 双击无反应 运行以下代码 xff0c 出错 panda6 1 0 x86 64 appimage 运行sudo apt get install fuse 直接输入以下 xff0c 即可运行 panda6

随机推荐