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


前言

在C语言编程中,结构体是我们常用的自定义类型,但面对内存优化、常量管理等场景,联合体和枚举能发挥更灵活的作用。本文将结合实例,详细拆解联合体和枚举的核心特性、使用场景及实战技巧,帮助大家高效运用这两种自定义类型。


一、联合体

1.1 联合体的核心定义与特性

联合体(又称共用体)是由一个或多个不同类型成员构成的自定义类型,所有成员共用同一块内存空间,编译器仅为最大成员分配足够内存。这一特性带来两个关键表现:

  • 给任意成员赋值会覆盖其他成员的内容。
  • 所有成员的内存起始地址完全相同。
c 复制代码
#include <stdio.h>
// 联合体声明
union Un {
    char c;
    int i;
};
int main() {
    union Un un = {0};
    // 输出三个相同的地址
    printf("%p\n", &(un.i));
    printf("%p\n", &(un.c));
    printf("%p\n", &un);
    un.i = 0x11223344;
    un.c = 0x55;
    // 输出结果为11223355,c覆盖了i的第4个字节
    printf("%x\n", un.i);
    return 0;
}

1.2 联合体与结构体的内存布局对比

相同成员的结构体和联合体,内存占用差异显著:

  • 结构体:成员按对齐规则依次排列,可能存在内存碎片(填充字节)。
  • 联合体:成员重叠存储,内存大小仅与最大成员及对齐规则相关。
类型 成员(char c + int i) 内存大小(32位系统) 内存特性
结构体struct S char c、int i 8字节(含4字节填充) 成员独立存储
联合体union Un char c、int i 4字节 成员重叠存储

1.3 联合体大小的计算规则

联合体的大小遵循"最小足够+对齐补充"原则:

  1. 基础规则:大小至少为最大成员的大小
  2. 对齐规则:若最大成员大小不是最大对齐数的整数倍,需补充至最大对齐数的整数倍(对齐数通常为成员类型大小,如int为4,double为8)。
c 复制代码
#include <stdio.h>
// 示例1:char[5](大小5)与int(对齐数4)
union Un1 {
    char c[5];
    int i;
};
// 示例2:short[7](大小14)与int(对齐数4)
union Un2 {
    short c[7];
    int i;
};
int main() {
    // 输出8(5不足4的2倍,补充至8)
    printf("%d\n", sizeof(union Un1));
    // 输出16(14不足4的4倍,补充至16)
    printf("%d\n", sizeof(union Un2));
    return 0;
}

1.4 联合体的实战场景:优化内存占用

当数据存在"互斥属性"时,用联合体替代结构体可大幅节省内存。例如礼品兑换单设计:图书、杯子、衬衫的特殊属性互斥,仅需在联合体中存储当前商品的专属属性。

c 复制代码
// 优化后的礼品兑换单结构
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;
};

1.5 联合体进阶用法:判断机器字节序

利用联合体成员内存重叠的特性,可快速判断当前机器是大端还是小端字节序:

c 复制代码
// 返回1为小端,返回0为大端
int check_sys() {
    union {
        int i;
        char c;
    } un;
    un.i = 1;
    // 小端:低地址存低字节(c=1);大端:低地址存高字节(c=0)
    return un.c;
}

二、枚举

2.1 枚举的核心定义与赋值规则

枚举是将可能取值一一列举的自定义类型,{}中的内容为枚举常量,默认从0开始依次递增1,也可在声明时显式赋值。

c 复制代码
// 默认赋值:Mon=0, Tues=1, ..., Sun=6
enum Day {
    Mon,
    Tues,
    Wed,
    Thur,
    Fri,
    Sat,
    Sun
};
// 显式赋值:RED=2, GREEN=4, BLUE=8
enum Color {
    RED=2,
    GREEN=4,
    BLUE=8
};

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

相比#define定义的无类型常量,枚举具有明显优势:

  1. 可读性更强 :枚举常量自带语义关联,便于代码理解(如enum Sex {MALE, FEMALE, SECRET})。
  2. 类型安全:枚举变量有明确类型,编译器会进行类型检查,而#define无类型约束。
  3. 便于调试:枚举常量在编译阶段保留,#define在预处理阶段被替换,无法在调试中查看。
  4. 批量定义:一次可定义多个相关常量,语法简洁。
  5. 作用域约束:枚举声明在函数内时,仅在函数内有效,避免全局污染。

2.3 枚举的使用注意事项

  • C语言中,枚举变量可直接赋值整数(不推荐,破坏类型一致性)。
  • C++中,枚举变量仅能赋值枚举常量(类型检查更严格)。
  • 枚举常量本质是整数,可参与算术运算(如RED + GREEN = 6)。
c 复制代码
enum Color {
    RED=1,
    GREEN=2,
    BLUE=4
};
int main() {
    // 正确用法:枚举常量赋值
    enum Color clr = GREEN;
    // C语言允许,C++报错:类型不匹配
    enum Color clr2 = 3;
    return 0;
}

三、联合体与枚举的适用场景

类型 核心特性 典型场景
联合体 成员共用内存、节省空间 1. 互斥属性数据存储;2. 字节序判断;3. 内存优化场景
枚举 类型化常量、规范取值 1. 状态标识(如登录状态、订单状态);2. 有限选项列举(如性别、颜色);3. 替代魔法数字

四、联合体额外实战案例

4.1 数据类型转换:无拷贝内存复用

用联合体实现char数组与int/float的快速转换(无内存拷贝):

c 复制代码
#include <stdio.h>
union DataConvert {
    int i;
    float f;
    char bytes[4];
};

int main() {
    union DataConvert dc;
    // float转字节数组
    dc.f = 3.14f;
    printf("3.14的字节表示:");
    for (int i=0; i<4; i++) {
        printf("%02x ", (unsigned char)dc.bytes[i]);
    }
    printf("\n");
    
    // 字节数组转int
    dc.bytes[0] = 0x44;
    dc.bytes[1] = 0x33;
    dc.bytes[2] = 0x22;
    dc.bytes[3] = 0x11;
    printf("字节数组转int:0x%x\n", dc.i);
    return 0;
}

效果 :利用内存重叠直接转换数据类型,比memcpy更高效。


五、枚举额外实战案例

5.1 订单状态机管理

电商系统中用枚举规范订单状态流转,避免魔法数字:

c 复制代码
#include <stdio.h>
// 定义订单状态枚举
enum OrderStatus {
    ORDER_CREATED = 101,    // 已创建
    ORDER_PAID,             // 已支付(102)
    ORDER_SHIPPED,          // 已发货(103)
    ORDER_DELIVERED,        // 已送达(104)
    ORDER_CANCELLED         // 已取消(105)
};

// 订单状态流转函数
void updateOrderStatus(enum OrderStatus* status, enum OrderStatus newStatus) {
    // 模拟状态合法性检查(如已取消订单不能再支付)
    if (*status == ORDER_CANCELLED && newStatus == ORDER_PAID) {
        printf("错误:已取消订单无法支付!\n");
        return;
    }
    *status = newStatus;
    printf("订单状态更新为:%d\n", *status);
}

int main() {
    enum OrderStatus order1 = ORDER_CREATED;
    updateOrderStatus(&order1, ORDER_PAID);    // 合法流转
    updateOrderStatus(&order1, ORDER_SHIPPED); // 合法流转
    updateOrderStatus(&order1, ORDER_CANCELLED); // 合法流转
    updateOrderStatus(&order1, ORDER_PAID);    // 非法流转(触发错误)
    return 0;
}

效果:通过枚举约束状态取值,结合流转检查,避免非法状态变更。

5.2 菜单选项枚举

控制台程序中用枚举定义菜单选项,提升代码可读性:

c 复制代码
#include <stdio.h>
enum MenuOption {
    MENU_QUIT = 0,     // 退出
    MENU_ADD_USER,     // 添加用户(1)
    MENU_DEL_USER,     // 删除用户(2)
    MENU_QUERY_USER    // 查询用户(3)
};

void showMenu() {
    printf("===== 用户管理系统 =====\n");
    printf("%d. 退出\n", MENU_QUIT);
    printf("%d. 添加用户\n", MENU_ADD_USER);
    printf("%d. 删除用户\n", MENU_DEL_USER);
    printf("%d. 查询用户\n", MENU_QUERY_USER);
    printf("请选择操作:");
}

int main() {
    enum MenuOption choice;
    while (1) {
        showMenu();
        scanf("%d", &choice);
        switch (choice) {
            case MENU_QUIT:
                printf("退出系统...\n");
                return 0;
            case MENU_ADD_USER:
                printf("执行添加用户逻辑...\n");
                break;
            case MENU_DEL_USER:
                printf("执行删除用户逻辑...\n");
                break;
            case MENU_QUERY_USER:
                printf("执行查询用户逻辑...\n");
                break;
            default:
                printf("无效选项,请重新选择!\n");
        }
    }
}

效果:菜单选项与枚举常量一一对应,switch-case逻辑更清晰,后续扩展菜单只需修改枚举。


合理运用联合体和枚举,既能优化内存占用,又能提升代码的可读性、规范性和可维护性。在实际开发中,结合数据的存储特性和使用场景选择合适的自定义类型,是写出高效代码的关键。

至此,我们已梳理完"联合体与枚举"的全部内容了。最后我们在文末来进行一个投个票,告诉我你对哪部分内容最感兴趣、收获最大,也欢迎在评论区聊聊你的学习感受。

以上就是本期博客的全部内容了,感谢各位的阅读以及关注。如有内容存在疏漏或不足之处,恳请各位技术大佬不吝赐教、多多指正。

相关推荐
csbysj20201 小时前
DOM 节点
开发语言
Bona Sun1 小时前
单片机手搓掌上游戏机(十五)—pico运行fc模拟器之编译环境
c语言·c++·单片机·游戏机
小尧嵌入式2 小时前
C++基础语法总结
开发语言·c++·stm32·单片机·嵌入式硬件·算法
white-persist2 小时前
【攻防世界】reverse | IgniteMe 详细题解 WP
c语言·汇编·数据结构·c++·python·算法·网络安全
@游子2 小时前
Python学习笔记-Day2
开发语言·python
qq_336313932 小时前
java基础-集合进阶
java·开发语言·windows
222you2 小时前
MybatisPlus常用注解
java·开发语言·spring
你的冰西瓜2 小时前
C++20 新特性详解:相较于 C++17 的主要改进
开发语言·c++·stl·c++20
济宁雪人2 小时前
Java安全基础——JNI安全基础
java·开发语言