【C语言】自定义类型:联合体与枚举

文章目录

在C语言中,自定义类型为数据组织提供了灵活的方式。除了常见的结构体,联合体(共用体)和枚举也是非常实用的类型。它们在内存管理和代码可读性方面有着独特的优势,本文将详细解析这两种类型的特性、用法及应用场景。

一、联合体(Union):内存共用的灵活类型

1.1 什么是联合体?

联合体是一种特殊的自定义类型,它由多个不同类型的成员组成,但所有成员共用同一块内存空间。这意味着联合体的大小仅需满足最大成员的存储需求,相比结构体能显著节省内存。

声明方式 与结构体类似,关键字为union

c 复制代码
// 联合体类型声明
union Un {
    char c;  // 字符型成员
    int i;   // 整型成员
};

// 联合体变量定义
union Un un;

1.2 联合体的核心特点

  1. 内存共用:所有成员共享同一块内存,起始地址相同。

    c 复制代码
    #include <stdio.h>
    union Un {
        char c;
        int i;
    };
    int main() {
        union Un un;
        printf("&un = %p\n", &un);    // 输出:001AF85C
        printf("&un.i = %p\n", &un.i);// 输出:001AF85C
        printf("&un.c = %p\n", &un.c);// 输出:001AF85C
        return 0;
    }

    上述代码中,联合体变量un及其成员ic的地址完全相同,证明它们共用内存。

  2. 成员值相互影响:修改一个成员的值会影响其他成员。

    c 复制代码
    #include <stdio.h>
    union Un {
        char c;
        int i;
    };
    int main() {
        union Un un;
        un.i = 0x11223344;  // 赋值整型成员
        un.c = 0x55;        // 修改字符型成员
        printf("%x\n", un.i);  // 输出:11223355
        return 0;
    }

    原理:int类型占4字节,char占1字节。un.c修改的是un.i的第一个字节(低地址位),导致un.i的原值0x11223344变为0x11223355

1.3 联合体与结构体的对比

类型 内存布局特点 大小计算方式
结构体(struct) 成员占用独立内存,按对齐规则排列 所有成员大小之和(含填充字节)
联合体(union) 成员共用一块内存 至少为最大成员大小(需满足对齐)

示例对比

c 复制代码
// 结构体:成员独立占用内存
struct S {
    char c;  // 1字节
    int i;   // 4字节(因对齐,c后填充3字节)
};
// 大小:1 + 3(填充) + 4 = 8字节

// 联合体:成员共用内存
union Un {
    char c;  // 1字节
    int i;   // 4字节
};
// 大小:4字节(取最大成员大小)

1.4 联合体大小的计算规则

  1. 基础规则:联合体大小至少为最大成员的大小。
  2. 对齐规则 :若最大成员大小不是"最大对齐数"的整数倍,需向上对齐到整数倍。
    (对齐数:成员自身大小与默认对齐数的较小值,默认对齐数通常为4或8)

示例计算

c 复制代码
#include <stdio.h>

// 案例1:char[5](大小5)与int(大小4)
union Un1 {
    char c[5];  // 对齐数1,大小5
    int i;      // 对齐数4,大小4
};
// 最大成员大小5,最大对齐数4。5不是4的倍数,向上对齐到8(4×2)。
// sizeof(Un1) = 8

// 案例2:short[7](大小14)与int(大小4)
union Un2 {
    short c[7];  // 对齐数2,大小14
    int i;       // 对齐数4,大小4
};
// 最大成员大小14,最大对齐数4。14不是4的倍数,向上对齐到16(4×4)。
// sizeof(Un2) = 16

int main() {
    printf("%d\n", sizeof(union Un1));  // 输出:8
    printf("%d\n", sizeof(union Un2));  // 输出:16
    return 0;
}

1.5 联合体的典型应用场景

场景1:节省内存(互斥属性存储)

当数据包含"公共属性"和"互斥的特殊属性"时,用联合体存储特殊属性可减少内存浪费。

例如"礼品兑换单"设计:

c 复制代码
// 优化前:结构体包含所有属性,浪费内存
struct gift_list {
    // 公共属性
    int stock_number;  // 库存量
    double price;      // 价格
    int item_type;     // 商品类型(1-图书,2-杯子,3-衬衫)
    
    // 特殊属性(互斥,仅一种生效)
    char title[20];    // 书名(仅图书)
    char author[20];   // 作者(仅图书)
    int num_pages;     // 页数(仅图书)
    char design[30];   // 设计(仅杯子/衬衫)
    int colors;        // 颜色(仅衬衫)
    int sizes;         // 尺寸(仅衬衫)
};

// 优化后:用联合体存储特殊属性
struct gift_list {
    int stock_number;
    double price;
    int item_type;
    
    union {  // 特殊属性共用内存
        struct { char title[20]; char author[20]; int num_pages; } book;
        struct { char design[30]; } mug;
        struct { char design[30]; int colors; int sizes; } shirt;
    } item;
};

优化后,特殊属性仅占用最大成员的内存(如shirt结构体大小),避免了无效属性的内存浪费。

场景2:判断机器字节序

字节序是数据在内存中的存储顺序(大端/小端),联合体可简单判断:

c 复制代码
// 返回1:小端(低地址存低位数据);返回0:大端(低地址存高位数据)
int check_sys() {
    union {
        int i;    // 4字节:0x00000001
        char c;   // 1字节:取i的低地址位
    } un;
    un.i = 1;
    return un.c;  // 小端:c=1;大端:c=0
}

二、枚举(Enum):有限取值的清晰表示

2.1 什么是枚举?

枚举用于定义有限个可能取值的集合,例如星期、性别、颜色等。它将离散值命名,增强代码可读性。

声明方式

c 复制代码
// 枚举类型声明(默认值从0开始递增)
enum Day {
    Mon,   // 0
    Tues,  // 1
    Wed,   // 2
    Thur,  // 3
    Fri,   // 4
    Sat,   // 5
    Sun    // 6
};

// 手动指定枚举值
enum Color {
    RED = 2,   // 2
    GREEN = 4, // 4
    BLUE = 8   // 8
};

enum Dayenum Color为枚举类型,MonRED等为枚举常量

2.2 枚举的优势(对比#define)

优势 说明
增强可读性 RED代替1,代码意图更清晰。
类型检查更严格 枚举变量只能接收枚举常量,#define定义的常量无类型限制。
便于调试 预处理阶段会删除#define符号,而枚举常量在调试时可见。
批量定义常量 一次声明多个相关常量,无需重复写#define
遵循作用域规则 枚举声明在函数内时,仅在函数内有效,避免全局命名污染。

2.3 枚举的使用方法

  1. 定义枚举变量

    c 复制代码
    enum Color { RED, GREEN, BLUE };
    enum Color clr = GREEN;  // 用枚举常量赋值
  2. 赋值规则

    • C语言允许直接赋值整数(不推荐,破坏类型检查):

      c 复制代码
      enum Color clr = 2;  // C中允许,C++中禁止
    • 建议始终使用枚举常量赋值,保证代码规范性。

三、总结

  • 联合体:通过成员共用内存实现内存优化,适合存储互斥属性,大小计算需考虑最大成员和对齐规则。
  • 枚举 :清晰表示有限取值集合,相比#define更安全、易维护,适合定义状态码、选项等。

合理使用这两种类型,能让代码更高效、更易读,尤其在嵌入式开发(内存受限)和大型项目(代码规范性要求高)中尤为重要。