在C语言中,普通变量只能存单个数据(一个数字或者一个字符),但是结构体可以把一组相关的、不同类型的数据捆成一个整体。
即:结构体可以把一个数据元素的各个不同的数据项聚合为一个整体 ,是一种自定义数据类型。核心作用是把多个不同类型的变量打包在一起,用来描述一个事物的多个属性。
本文主要介绍结构体的定义、引用,最后并通过一个实例来测试实际场景中的应用
一、声明格式
struct 结构体名{
成员表列;
};
例如定义学生这个结构体:
c
struct Student {
int id;
char name[20];
int age;
float score;
};
struct Student stu1;
定义完结构体类型后,还没有创建实际变量,不占内存
实际使用还需需要进行声明(创建具体的变量,可真正存数据的对象),如上面的struct Student stu1
二、引用
对结构体变量的运算必须通过成员运算符"."来访问结构体变量的成员,方式如下
结构体变量名.成员名
c
int main() {
// 2. 定义结构体变量
struct Student stu1;
// 3. 给成员赋值
stu1.id = 888;
strcpy(stu1.name,"王五");
stu1.age = 18;
stu1.score = 99.5;
// 4. 访问成员并打印
printf("学号:%d\n",stu1.id);
printf("姓名:%s\n",stu1.name);
printf("年龄:%d\n",stu1.age);
printf("成绩:%.1fd\n",stu1.score);
return 0;
}
编译运行后的结果如下:

三、结构体的特点
允许存在多种类型 :可以把int、char、float、数组甚至其他结构体打包在一起;
自定义性强 :可以根据需求创建任意类型
整体管理:一个结构体变量能代表一个完整事物,不用维护一堆零散变量。
四、实际场景练习
1、需求说明
使用结构体描述硬件通信数据的485通信
485通信的核心固定格式是:
zhen tou
| 帧头(固定) | 从机地址 | 功能码 | 数据长度 | 数据段 | 校验和 | 帧尾(固定) |
|---|---|---|---|---|---|---|
| 1字节 | 1字节 | 1字节 | 1字节 | n字节 | 1字节 | 1字节 |
2、实现逻辑
先定义帧结构体、实现求校验和函数,主机声明485结构体,模拟主机发送数据,从机接收数据。
c
#include <stdio.h>
#include <stdint.h>
// 1. 定义485通信数据帧---结构体类型
typedef struct {
uint8_t head; //帧头
uint8_t addr; //设备地址
uint8_t func; //功能码
uint8_t data_len; //数据长度
uint8_t data[10]; //数据区:最多存10个字节
uint8_t check_h; //校验和
uint8_t check_l; //校验和
uint8_t tail_h; //帧尾
uint8_t tail_l; //帧尾
}RS485_Frame;
int main() {
// ===================== 模拟主机:打包485指令()=====================
RS485_Frame tx_frame; // 定义发送帧变量
// 填充指令(给从机发:地址1,读数据,数据段2字节)
tx_frame.head = 0x3A; // 帧头固定
tx_frame.addr = 0x11; // 目标从机地址=0x11
tx_frame.func = 0x05; // 功能码=0x05(读数据)
tx_frame.data_len = 0x01; // 数据段长度=0字节
tx_frame.data[0] = 0x10; // 数据1
tx_frame.check_h = 0x27;
tx_frame.check_l = 0x00;
tx_frame.tail_h = 0x0D; // 帧尾固定
tx_frame.tail_l = 0x0A; // 帧尾固定
// 主机发送:直接把结构体转成字节流,通过485总线发出去
printf("主机发送485帧(十六进制):");
uint8_t *tx_buf = (uint8_t*)&tx_frame; // 结构体转字节指针
for(int i=0; i<sizeof(RS485_Frame); i++) {
printf("0x%02X ", tx_buf[i]);
}
printf("\n");
// ===================== 模拟从机:接收485指令(硬件从机用结构体解析)=====================
RS485_Frame rx_frame; // 定义接收帧变量
// 假设从机通过485接收到数据,直接存入结构体
rx_frame = tx_frame;
// 从机解析指令:直接访问结构体成员
printf("\n从机解析结果:\n");
printf("帧头:0x%02X\n", rx_frame.head);
printf("从机地址:%d\n", rx_frame.addr);
printf("功能码:0x%02X(读数据)\n", rx_frame.func);
printf("数据长度:%d字节\n", rx_frame.data_len);
printf("数据段:0x%02X 0x%02X\n", rx_frame.data[0], rx_frame.data[1]);
printf("校验和:0x%02X(数据有效)\n", rx_frame.check_h);
printf("帧尾:0x%02X\n", rx_frame.tail_h);
return 0;
}
运行结果如下:

通过实践可以看出:
485通信贴合硬件数据格式,发送时打包一个结构体,接收时直接解析成员,不用拼接字节也不用算偏移。
硬件通信最怕字节顺序错,结构体固定成员顺序,可杜绝bug;
扩展性好,后续增加数据或状态位,直接在结构体里添加成员就可以。
五、注意事项
1、结构体数据的空间中,可能产生填充信息
原因:大多数处理器,访问按照字或半字对齐的数据速度更快,在定义结构体时,编译器为了性能优化,可能会将他们按照半字或字对齐。
所以会出现:两个相同结构体,成员相同,但是排列顺序不同,其占空间也是不一样的
2、不同编译器以及编译选项的属性,系统为它分配的存储空间会有所不同
在存储该结构时会按照不同的内存对齐规则进行相关处理。