13_C语言三大宗门圣主——结构体联合体位字段
🤘速记指南
🤖结构体(struct)—— 不同类型的数据集合
结构体:成员独立,内存叠加。
👾联合体(union)—— 共用内存的“多面手”
联合体:成员共享,内存覆盖。
☠️位字段(Bit-field)—— 比特级别的操作
位字段:精准控制比特位,通常嵌入结构体/联合体中使用。
🖐结构体(struct)详解
结构体是C语言中用于将不同类型的数据组合成一个整体的工具。它是用户自定义的数据类型,也是面向对象语言中“类”的雏形。
但要搞清楚
struct他只是定义了一种数据格式,并不是某个变量的值,所在他里面不能有初始化的行为
🧚♀️扩展
做个类似的比喻,你家有万亩良田。你想把这块地承包给int(整型)、char(字符)、float(浮点型变量)三个农户。你只能在承包前做规划(定义结构体),却无法决定他们拿这块地做什么用。所以他就是“形”的概念,他只做规划!只做规划!但是你是个强迫症患者,你喜欢整整齐齐的。所以分地的时候,喜欢用这三个农户要地最大的那个人为标准划分。而且大小为N倍。
所以,他在程序中常常用于做内存对齐
💁♀️内存对齐规则
- 成员偏移量为其大小的整数倍(如
int需4字节对齐)。- 结构体总大小为最大成员大小的整数倍。
💁♂️优化技巧
将较小成员集中声明可减少填充字节。例如:
struct Optimized
{
int a; // 4字节
short b; // 2字节
char c; // 1字节
}; // 总大小可能为8字节(而非7字节)
- 🙋对齐前

- 🧏对齐后

🫸联合体(union)核心特性
联合体的所有成员共享同一内存空间,大小由最大成员决定。
典型应用
- 类型双关(Type Punning):同一内存的不同解释方式。
- 协议解析:灵活读取不同格式的数据。
union Data
{
int i;
float f;
char str[4];
};
// 修改任一成员会影响其他成员的值
🧑💻举个例子
- 👩🎓情况一
#include <stdio.h>
#include <string.h>
union Data
{
int i; // 4字节
float f; // 4字节
char str[4]; // 4字节
};
int main()
{
union Data data;
// 情况1:先设置整数,再读取其他成员
data.i = 0x41424344; // 十六进制,对应 ASCII: 'A'(41) 'B'(42) 'C'(43) 'D'(44)
printf("设置 i = 0x%x (十进制: %d)\n", data.i, data.i);
printf("作为 float 读取: %f\n", data.f); // 把同样的内存解释为 float
printf("作为字符串读取: %s\n", data.str); // 把同样的内存解释为字符数组
// 打印内存的十六进制值
printf("内存内容: ");
for(int j = 0; j < 4; j++)
{
printf("%02x ", (unsigned char)data.str[j]);
}
printf("\n\n");
return 0;
}
- 💂输出结果
设置 i = 0x41424344 (十进制: 1094861636) 作为 float 读取: 789.456283 // 乱码,因为把整数解释为浮点数 作为字符串读取: DCBA // 注意是小端序,所以字符是反的 内存内容: 44 43 42 41
- 👨🔧情况二
#include <stdio.h>
#include <string.h>
union Data
{
int i; // 4字节
float f; // 4字节
char str[4]; // 4字节
};
int main()
{
union Data data;
// 情况2:修改字符串成员
strcpy(data.str, "XYZ");
printf("设置 str = \"%s\"\n", data.str);
printf("此时 i 的值: 0x%x (十进制: %d)\n", data.i, data.i);
printf("此时 f 的值: %f\n", data.f);
return 0;
}
设置 str = "XYZ" 此时 i 的值: 0x5a594858 (十进制: 1517975640) // 'X','Y','Z'的ASCII值组合 此时 f 的值: 345829376.000000 // 再次乱码
🫰为什么会这样
内存地址: [0x1000] [0x1001] [0x1002] [0x1003]
初始状态: [ 44 ] [ 43 ] [ 42 ] [ 41 ] (小端序存储 0x41424344)
对应关系:
👉作为 int: 0x41424344 (实际存储为 44 43 42 41)
👉作为 float: 把这4字节解释为IEEE 754浮点数 → 乱码
👉作为 char[4]: str[0]='D'(44), str[1]='C'(43), str[2]='B'(42), str[3]='A'(41)修改 str 为 "XYZ\0":
内存变成: [ 'X' ] [ 'Y' ] [ 'Z' ] [ '\0' ]
(0x58) (0x59) (0x5A) (0x00)此时作为 int 读取:0x005A5958 (注意小端序)
作为 float 读取:把这4字节按浮点数格式解析
👊位字段(Bit-field)
位字段允许以比特为单位定义成员宽度,适用于紧凑存储标志位或状态码。
🤳使用场景
- 🖐嵌入式系统与硬件寄存器
- 🙀网络协议头解析
- 👩💻文件系统与数据存储
- 🙄图形图像处理
- 🤵状态机与标志位管理
- 👯♀️数据库与序列化
👨🎓加深理解 —— 以嵌入式系统与硬件寄存器为例
🧑🚀案例带入
🥾实战案例:I2C设备驱动中的寄存器配置
🧥场景描述
假设你在开发一个温度传感器驱动,传感器内部有一个配置寄存器
- 需要设置:
测量精度(它只需要2位)
采样速率(它只需要2位)
加热器开关(它只需要1位)
模式选择(它只需要2位)
测量精度(它只需要2位)
保留位(1位)
这个配置寄存器只有 8位(1字节),要放下这么多控制项。这就是位字段的典型应用。
#include <stdio.h>
#include <stdint.h>
// ============ 温度传感器配置寄存器定义 ============
// 使用联合体 + 结构体位字段
typedef union
{
uint8_t reg_value; // 直接读写寄存器的值
struct
{
uint8_t meas_accuracy : 2; // bit0-1: 测量精度 (0-3)
uint8_t sample_rate : 2; // bit2-3: 采样速率 (0-3)
uint8_t heater_en : 1; // bit4: 加热器开关 (0/1)
uint8_t mode : 2; // bit5-6: 工作模式 (0-3)
uint8_t reserved : 1; // bit7: 保留位
} bits;
} TempSensor_Config;
// ============ 定义可读的宏(替代魔数)============
#define ACCURACY_HIGH 3 // 高精度
#define ACCURACY_MEDIUM 2 // 中精度
#define ACCURACY_LOW 1 // 低精度
#define RATE_10HZ 0 // 10次/秒
#define RATE_50HZ 1 // 50次/秒
#define RATE_100HZ 2 // 100次/秒
#define RATE_200HZ 3 // 200次/秒
#define MODE_SLEEP 0 // 休眠
#define MODE_SINGLE 1 // 单次测量
#define MODE_CONTINUOUS 2 // 连续测量
#define MODE_TEST 3 // 测试模式
// ============ 模拟的I2C设备 ============
uint8_t sensor_register = 0; // 模拟传感器的配置寄存器
// I2C写函数(模拟)
void i2c_write_register(uint8_t reg_val)
{
sensor_register = reg_val;
printf("[I2C] 写入寄存器: 0x%02X\n", reg_val);
}
// I2C读函数(模拟)
uint8_t i2c_read_register(void)
{
printf("[I2C] 读取寄存器: 0x%02X\n", sensor_register);
return sensor_register;
}
// ============ 主程序 ============
int main()
{
TempSensor_Config config;
printf("===== 温度传感器驱动演示 =====\n\n");
// 1. 初始配置:连续测量模式,100Hz采样率,高精度,关加热器
printf("1. 配置传感器参数...\n");
config.bits.meas_accuracy = ACCURACY_HIGH;
config.bits.sample_rate = RATE_100HZ;
config.bits.heater_en = 0; // 关加热器
config.bits.mode = MODE_CONTINUOUS;
config.bits.reserved = 0; // 保留位必须写0
printf(" - 精度: 高 (3)\n");
printf(" - 采样率: 100Hz (2)\n");
printf(" - 加热器: 关闭\n");
printf(" - 模式: 连续测量 (2)\n");
// 2. 写入传感器
printf("\n2. 写入传感器配置寄存器...\n");
i2c_write_register(config.reg_value);
// 3. 查看实际写入的字节值
printf("\n3. 寄存器实际值分析: 0x%02X\n", config.reg_value);
printf(" 二进制: ");
for(int i = 7; i >= 0; i--) {
printf("%d", (config.reg_value >> i) & 1);
}
printf(" (bit7-0)\n\n");
// 4. 演示按位解析:从寄存器读取回来
printf("4. 从传感器读取当前配置...\n");
TempSensor_Config current;
current.reg_value = i2c_read_register();
// 5. 解析读回的值
printf("\n5. 解析读回的数据:\n");
printf(" - 测量精度: %d ", current.bits.meas_accuracy);
switch(current.bits.meas_accuracy)
{
case 3: printf("(高精度)\n"); break;
case 2: printf("(中精度)\n"); break;
case 1: printf("(低精度)\n"); break;
default: printf("(未知)\n");
}
printf(" - 采样速率: %d ", current.bits.sample_rate);
switch(current.bits.sample_rate) {
case 0: printf("(10Hz)\n"); break;
case 1: printf("(50Hz)\n"); break;
case 2: printf("(100Hz)\n"); break;
case 3: printf("(200Hz)\n"); break;
}
printf(" - 加热器: %s\n", current.bits.heater_en ? "开启" : "关闭");
printf(" - 工作模式: %d ", current.bits.mode);
switch(current.bits.mode)
{
case 0: printf("(休眠)\n"); break;
case 1: printf("(单次测量)\n"); break;
case 2: printf("(连续测量)\n"); break;
case 3: printf("(测试模式)\n"); break;
}
// 6. 演示动态修改:临时开启加热器(比如要除湿)
printf("\n6. 动态修改配置(开启加热器)...\n");
current.bits.heater_en = 1;
printf(" 修改后寄存器值: 0x%02X\n", current.reg_value);
return 0;
}
🫵注意事项
- 不可取地址(无指针操作)。
- 位顺序依赖编译器实现(小端/大端)。
💪示例
struct Status
{
unsigned int flag1 : 1; // 1位
unsigned int code : 4; // 4位(0~15)
unsigned int pad : 3; // 填充3位
}; // 总大小通常为4字节(对齐)
说实话关于位字段的运用本人都用的很少,这里等后面有新的感悟 再补充吧.....
留个悬念
2026年2月27日11点16分
👨👧三者的协同应用
🧑🧒🧒网络协议头解析示例
union Packet {
uint32_t raw;
struct {
uint16_t src;
uint16_t dest;
uint8_t type : 4;
uint8_t flags : 2;
} fields;
};
// 可直接读写raw,或通过fields访问具体位域

更多推荐

所有评论(0)