你是否历经这般情形:C语言已然学完,计算机二级也已通过了,然而一旦打开STM32的库函数代码瞧瞧,瞅见GPIO_InitTypeDef此类结构体,刹那间就懵圈了?这绝非你愚昧,而是于C语言与单片机实际应用之间,欠缺了至关重要的一个过渡环节。现今,我们会采用最为通俗易懂的形式,联合真切的单片机开发场景,将结构体这一难关给你彻头彻尾地讲清楚。
有不少刚开始学习的人员,在编写单片机程序之际,惯常用到碰到一个变量就去定义一个的方式。比如说,在某个项目当中,设置有光照传感器、烟雾传感器、酒精传感器以及湿度传感器,如此一来,你极有可能会去定义四个彼此独立的变量,用以存放数据。在经过两天之后,发觉针对每个传感器而言,还需要增添一个状态标志位,于是在代码内,就又额外多出了四个变量。一旦项目稍微变得复杂一些,变量便会到处都是,不要说对其进行维护了,就连自己都难以分辨清楚,究竟哪个变量是对应哪一个设备的。
这种混乱所存在的根源,在于并未将相关的数据整合于一处。结构体乃是针对解决此问题而存在的。它准许你把与一个设备或者一个模块有关联的全部变量汇聚成一个整体。比如说一个传感器,你能够把它的数值、状态以及校准值统统放置进一个结构体当中,如此一来代码逻辑便大幅清晰起来了。
# include "sys.h" # include "delay.h" # include "usart.h" /*记录传感器的数值*/ float temperature; //温度 char humidity; //湿度 char alcohol; //酒精浓度 int illumination; //光照强度 /*记录传感器高低阈值*/ float temperature_threshold[ 2 ]; float humidity_threshold[ 2 ]; float alcohol_threshold[ 2 ]; float illumination_threshold[ 2 ]; int main ( void ) {
uart_init( 115200 ); //串口初始化 delay_init();
while ( 1 )
{
}
}
若将结构体理解为自定义数据类型的盒子的话,就如同int可容纳整数,char能装字符那般,结构体能使你自行创造一种新型,其中能够同时装入整数、字符、数组,甚至是其他结构体。当定义结构体之际,所运用的乃是struct关键字,随后紧跟你为该类型所取的名字,接着于大括号内罗列其涵盖的所有成员。
在完成这个全新数据类型的定义之后,你便能如同使用int那般,借助它去定义变量。然而,当你打算对该变量之中的具体成员实施操作之际,得运用点号“.”来衔接变量名与成员名。众多初学者会对这个点号存有别扭之感,实际上你无需为此纠结,记住这属于C语言的语法规则便可,使用次数增多后自然就会得心应手了。
直接采用GPIO_TypeDef这般崭新名字去给变量加以定义,代码看上去会简约许多。这般写法于单片机开发里近乎算得上标准做法,缘由在于库函数设计者期望叫你于使用之际少敲些字,代码也更为美观好看。你仅仅得记住,瞅见typedef struct之时,后面那个名称就是我们能够径直用以定义变量的“新型名称。”。
# include "sys.h" # include "delay.h" # include "usart.h" typedef int zhjiguoxin; //zhjiguoxin就是int zhjiguoxin value = 0 ; int main ( void ) {
uart_init( 115200 ); //串口初始化 delay_init();
printf ( "value=%d\r\n" ,value);
while ( 1 )
{
}
}
当你将一个结构体变量完成定义以后,最是经常会使用的操作便是给其内部的成员赋予相应的值。比如说你定义了一个用来配置GPIO引脚的结构体变量,你能够借助“变量名.成员名”这样的形式,去对引脚号、工作模式、输出速度等参数予以设置。在现代化的单片机库当中,这些成员通常来说在代码编辑器里会自动弹出提示,你仅仅需要进行选择以及赋值就行。
还有一种平常会出现的情形,就是在你获取到一个结构体指针之际,访问其成员的途径就转变为使用“->”符号了。举例来说的话,倘若一个函数传入了结构体的地址的时候,函数自身内部要是想要去修改结构体的内容的话,那就一定要使用指针和箭头。好多刚开始接触的新手看到点号跟箭头混合使用就晕头转向了,实际上,你只要记住这么一点就行喽:属于变量本身则用点,变量的地址就用箭头,这可是区分两者最为简便的办法。
结构体不但能够放置数据,而且还能够放置函数指针,这种情况在单片机的高级编程里极为常见,特别是当在模拟面向对象的思想这个状态的时候,譬如的话你能够去定义一个结构体,这个结构体里面是包含初始化函数指针、读取数据函数指针之类的,如此一来,从同一个结构体类型定义产生出来的不同设备,能够指向不同的具有针对性的功能函数,达成类似多态的那种效果。
# include "sys.h" # include "delay.h" # include "usart.h" typedef struct
{ float temperature; //温度 char humidity; //湿度 char alcohol; //酒精浓度 int illumination; //光照强度 char CO; //一氧化碳浓度 int *p; //int型的指针变量 } sensor;
sensor sen; int value = 0 ; int main ( void ) {
uart_init( 115200 ); //串口初始化 delay_init();
sen.p=&value; //把value的地址赋值 //打印p代表的地址里面的值(其实就是打印value的值) printf ( "value=%d\r\n" ,*(sen.p));
while ( 1 )
{
}
}
给函数指针予以赋值之际,只需径直将函数名赋予它便可,理由为函数名自身已然代表函数的入口地址。当你打算调用此函数之时,便采用“结构体变量.函数指针名()”这样的格式。这般写法于编写驱动层或者设备管理框架之际极为有用,能够使得你的代码复用性大幅提升,是进阶单片机编程务必掌握的一项技能。
# include "sys.h" # include "delay.h" # include "usart.h" typedef struct
{ float temperature; //温度 char humidity; //湿度 char alcohol; //酒精浓度 int illumination; //光照强度 char CO; //一氧化碳浓度 int *p; //int型的指针变量 void (*fun)();
} sensor;
sensor sen; void function () {
printf ( "zhiguoxin\r\n" );
} int value = 0 ; int main ( void ) {
uart_init( 115200 ); //串口初始化 delay_init();
sen.fun=function;
sen.fun();
while ( 1 )
{
}
}
# include "sys.h" # include "delay.h" # include "usart.h" typedef struct
{ int i;
}zhiguoxin; typedef struct
{ float temperature; //温度 char humidity; //湿度 char alcohol; //酒精浓度 int illumination; //光照强度 char CO; //一氧化碳浓度 int *p; //int型的指针变量 void (*fun)();
zhiguoxin guougo;
}sensor;
sensor sen; int main ( void ) {
uart_init( 115200 ); //串口初始化 delay_init();
sen.guougo.i= 100 ;
printf ( "i=%d\r\n" ,sen.guougo.i);
while ( 1 )
{
}
}
于实际项目当中,你时常会碰到结构体里还含有另一结构体这般的情形,此即结构体嵌套。像一个被称作“系统状态”的结构体,当中或许涵盖“传感器状态”结构体、“通信状态”结构体等。当去访问嵌套结构体里的成员之际,就得借助多个点号一级一级地朝着下探寻,例如sysStatus.sensorStatus.value。
结构体指针,属于极为关键的概念范畴。能够对指针予以定义,使其指向结构体变量,随后借助指针去操控结构体。于传递参数之际,此尤为有用,原因在于,若直接传递整个结构体,将会占用大量栈内空间以及CPU运行时间,然而传递一个指针,则会高效许多。唯有掌握了结构体指针以及嵌套相关内容,代码方可称得上真正踏入了嵌入式开发的门槛。
学到此处,想必你早已对于结构体在单片机里的运用拥有了明晰的认知。那么问题出现了:在你近期所做的一个单片机项目之中,是否存在某一段代码是由于未妥善运用结构体从而变得难以进行维护的呢?欢迎在评论区域分享你的经历,我们一同研讨改进方式。要是这篇文章对你有所助益,同样别忘记点赞并且转发给更多正在学习单片机的友人。