欢迎光临
我们一直在努力

C语言中结构体与联合体的区别与联系

本文从定义、底层原理、使用方法、典型案例四个层面系统梳理 C 语言的结构体(struct)与联合体(union),并通过内存布局图、代码示例与对比表格帮助建立清晰、可落地的认知。

目录

  • 一句话先记住本质区别
  • 结构体 struct:定义、原理与使用
  • 联合体 union:定义、原理与使用
  • 核心区别与内在联系(对比表)
  • 内存对齐:二者共用的底层规则
  • 典型案例实战
  • 易错点与最佳实践
  • 小结

  • 一、一句话先记住本质区别

    • 结构体(struct):把不同类型的数据打包在一起,每个成员各自拥有独立内存,总大小是各成员(含对齐填充)的累加。
    • 联合体(union):让多个成员共用同一段内存,同一时刻只能保存其中一个成员的值,总大小等于最大成员(含对齐)的大小。

    二、结构体 struct:定义、原理与使用

    2.1 定义

    结构体是一种用户自定义的聚合类型,将若干可能不同类型的变量组合成一个整体,每个变量称为一个成员(member)。

    struct Student {
    char name[20]; // 数组成员
    int age; // 整型成员
    float score; // 浮点成员
    };

    定义方式有三种:

    // 1. 直接定义变量
    struct Student s1;

    // 2. 定义类型的同时定义变量
    struct Student { char name[20]; int age; } s2;

    // 3. 用 typedef 起别名(最常用,省去 struct 关键字)
    typedef struct {
    char name[20];
    int age;
    float score;
    } Student;
    Student s3; // 直接使用别名

    2.2 原理

    编译器为结构体分配连续内存,每个成员在内存中按声明顺序依次排列,并通过 &s.成员 取得各自的地址。成员之间可能存在**填充字节(padding)**以满足对齐要求(详见第五节)。

    下图展示结构体成员的独立存储与对齐填充:

    2.3 使用方法

    typedef struct { int x; int y; } Point;

    Point p;
    p.x = 3; // 点运算符访问成员
    p.y = 4;

    Point *pp = &p;
    pp->x = 10; // 指针用箭头运算符访问

    printf("(%d, %d)\\n", p.x, p.y);

    结构体还支持整体赋值与作为函数参数/返回值:

    Point a = {1, 2};
    Point b = a; // 合法:结构体可整体拷贝

    void move(Point q) { q.x += 1; q.y += 1; } // 值传递会拷贝整个结构体


    三、联合体 union:定义、原理与使用

    3.1 定义

    联合体的语法与结构体几乎相同,唯一关键字差异是把 struct 换成 union:

    union Data {
    int i;
    float f;
    char c;
    };

    3.2 原理

    联合体的所有成员起始于同一地址,共享同一块内存。这块内存的大小取最大成员所需的大小。因此:

    • 同一时刻只有一个成员是"有效"的,写入新成员会覆盖旧成员的二进制内容;
    • sizeof(union) 由最大成员和对齐共同决定,不随成员个数累加。

     

    3.3 使用方法

    union Data d;
    d.i = 65; // 写入整型成员
    printf("%d\\n", d.i); // 65

    d.f = 3.14f; // 之后写浮点,i 的值已被破坏
    printf("%.2f\\n", d.f); // 3.14(此时读 d.i 是无意义的)

    关键纪律:用哪个成员写入,就用哪个成员读出,不要"交叉读取"未写入的成员(除少数刻意利用二进制表示的技巧外)。


    四、核心区别与内在联系(对比表)

    维度结构体 struct联合体 union
    关键字 struct union
    成员内存 各自独立、依次排列 共享同一块内存
    总大小 各成员(含填充)之和 最大成员大小(含对齐)
    同时有效成员 全部成员可同时存在 同一时刻仅 1 个
    成员地址 互不相同 完全相同
    写入影响 互不影响 后写覆盖先写
    典型用途 描述"一个事物的多个属性" 节省内存 / 同一数据的多种解释
    初始化 可同时初始化多个成员 一般只能初始化首个(C99 起可指定)

    内在联系

  • 二者都是用户自定义的聚合/派生类型,语法结构、成员访问运算符(. 与 ->)、typedef 用法完全一致;
  • 二者都遵循相同的内存对齐规则,sizeof 都需要按对齐粒度取整;
  • 二者可以互相嵌套:结构体里可以放联合体,联合体里也可以放结构体,常用来构造"带类型标签的变体"。

  • 五、内存对齐:二者共用的底层规则

    CPU 按"块"读取内存,成员若落在对齐边界上访问更快。常见规则:

    • 每个成员的偏移量必须是其自身对齐值(通常为 sizeof(类型))的整数倍,否则编译器插入 padding;
    • 结构体总大小必须是最大成员对齐值的整数倍,否则在末尾补 padding;
    • 联合体大小 = 最大成员大小,并向上取整到最大对齐值的整数倍。

    经典示例:成员顺序影响 sizeof

    struct A { char c; int i; char d; }; // 1+3(pad)+4+1+3(pad) = 12
    struct B { char c; char d; int i; }; // 1+1+2(pad)+4 = 8

    经验法则:声明结构体时把成员按占用字节从大到小排列,可减少填充、节省内存。


    六、典型案例实战

    案例 1:结构体描述"多属性实体"

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

    typedef struct {
    char name[32];
    int year;
    float rating;
    } Movie;

    int main(void) {
    Movie m = {"Inception", 2010, 8.8f};
    strcpy(m.name, "Interstellar"); // 数组成员用函数修改
    printf("%s (%d) score=%.1f\\n", m.name, m.year, m.rating);
    return 0;
    }
    // 输出:Interstellar (2010) score=8.8

    案例 2:联合体做"省内存的变体"

    需求:一个变量在不同场景下分别表示整数、浮点或字符,但任意时刻只用一种,用联合体避免为每种类型都分配空间。

    #include <stdio.h>

    typedef union {
    unsigned char bytes[4];
    int value;
    } IntBytes; // sizeof = 4

    int main(void) {
    IntBytes ib;
    ib.value = 0x41424344;
    // 通过 bytes 成员观察同一块内存的逐字节表示(与大小端相关)
    for (int k = 0; k < 4; k++)
    printf("%02X ", ib.bytes[k]);
    printf("\\n");
    return 0;
    }
    // 小端机器输出:44 43 42 41

    案例 3:联合体 + 结构体实现"类型标签联合"(tagged union)

    结构体与联合体配合的经典模式,联合体负责"多态地存",结构体里的标签负责"标明当前是哪种":

    #include <stdio.h>

    typedef struct {
    enum { INT_T, FLOAT_T } type; // 标签:当前有效的是哪个成员
    union {
    int i;
    float f;
    } data; // 联合体:共享存储
    } Variant;

    int main(void) {
    Variant v;
    v.type = INT_T; v.data.i = 42; // 写入并打标签
    if (v.type == INT_T) printf("int = %d\\n", v.data.i); // 按标签读取

    v.type = FLOAT_T; v.data.f = 3.5f; // 切换成员
    if (v.type == FLOAT_T) printf("float = %.1f\\n", v.data.f);
    return 0;
    }

    实际工程里更稳妥的做法是封装 set_int()/set_float() 函数,在写成员的同时设置标签,保证二者永远一致,杜绝"读错解释方式"。

    案例 4:结构体"柔性数组"实现变长对象

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

    typedef struct {
    int len;
    int data[]; // 柔性数组成员(必须是最后一个成员,C99)
    } Buffer;

    int main(void) {
    int n = 5;
    Buffer *b = malloc(sizeof(Buffer) + n * sizeof(int));
    b->len = n;
    for (int i = 0; i < n; i++) b->data[i] = i * i;
    printf("last = %d\\n", b->data[n-1]); // last = 16
    free(b);
    return 0;
    }


    七、易错点与最佳实践

  • 联合体的交叉读取:用 i 写入后用 f 读出,得到的是对同一串二进制按新类型重新解释的结果,多数情况下是"乱值",除非刻意做底层技巧(如案例 2)。
  • 忘了结构体也有填充:不要假设 sizeof(struct) 等于成员字节之和,做网络/文件格式序列化时要显式控制对齐(#pragma pack 或手写序列化)。
  • 成员顺序影响大小:把大成员放前面、小成员放后面,可减小 padding。
  • 联合体只能保证"首个成员"初始化的可移植性:需要初始化非首成员时,C99 起用 union Data d = { .f = 3.14f }; 指定成员。
  • 函数传参优先传指针:结构体较大时,值传递会整体拷贝,传 const 指针 更高效。
  • tagged union 必须维护标签一致性:写入成员的同时务必更新标签,否则读取会访问到错误的解释方式。

  • 八、小结

    • 结构体是"加法":为每个成员分配独立空间,用来描述一个对象的全部属性。
    • 联合体是"重叠":让多个成员共用同一块空间,用来在互斥的多种解释/表示之间切换、节省内存。
    • 二者语法与访问方式一致,共享同一套内存对齐规则,且可互相嵌套,组合出如 tagged union、柔性数组等实用结构。

    理解差异的钥匙只有一句话:结构体让成员"并存",联合体让成员"互斥"。把握了这一点,再复杂的嵌套与 sizeof 计算都能从容应对。

    赞(0)
    未经允许不得转载:171主机测评 » C语言中结构体与联合体的区别与联系
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址