🤘速记指南


🤖结构体(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访问具体位域  

Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务

更多推荐