自定义类型:联合体与枚举

目录

一、联合体

(一)联合体类型的定义

1、语法格式

2、关键字与内存分配原则

3、示例:基础联合体定义与内存验证

(二)联合体的核心特点

1、内存共享

2、瞬时独占性

3、与结构体的本质区别

[(三) 联合体大小的计算](#(三) 联合体大小的计算)

1、两个规则

2、具体示例

(四)联合体的典型应用场景

[1、场景 1:礼品兑换单设计(节省内存)](#1、场景 1:礼品兑换单设计(节省内存))

[2、场景 2:判断机器大小端存储](#2、场景 2:判断机器大小端存储)

二、枚举

(一)枚举的定义与语法

1、枚举常量与枚举变量

(二)枚举常量的自定义赋值

(三)枚举的应用场景

[1、场景 1:switch-case 分支(计算器菜单优化)](#1、场景 1:switch-case 分支(计算器菜单优化))

[2、场景 2:函数参数限制(限定输入范围)](#2、场景 2:函数参数限制(限定输入范围))

[(四)枚举与 #define 的对比(优点)](#define 的对比(优点))

三、联合体与枚举总结


一、联合体

联合体(union,又称 "共用体")是一种特殊的自定义类型,所有成员共享同一块内存空间,核心价值是在特定场景下节省内存(避免存储互斥的冗余数据)。

像结构体⼀样,联合体也是由一个或者多个成员构成,这些成员可以不同的类型。

(一)联合体类型的定义
1、语法格式
cpp 复制代码
// 定义方式
union 联合体类型名 {
    成员类型1 成员名1;
    成员类型2 成员名2;
    ...
    成员类型n 成员名n;
};

// 声明方式
union 联合体类型名; // 仅声明联合体的存在,不定义其成员(称为"不完全类型")
2、关键字与内存分配原则

**(1)关键字:**union(对比结构体 struct、枚举 enum)。

(2)内存分配原则: 编译器仅为最大成员分配足够的内存空间(所有成员共享此空间)。

3、示例:基础联合体定义与内存验证
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

// 声明联合体:包含 char(1字节)和 int(4字节)
union Un {
    char c;   // 成员1:1字节
    int i;    // 成员2:4字节
};

int main() {
    union Un un;
    // 1. 计算联合体大小:最大成员(int)的大小,结果为 4
    printf("联合体大小:%zd\n", sizeof(un));
    
    // 2. 验证成员共享内存:所有成员的起始地址相同
    printf("&un = %p\n", &un);    // 联合体变量地址
    printf("&un.c = %p\n", &un.c); // 成员c的地址
    printf("&un.i = %p\n", &un.i); // 成员i的地址
    return 0;
}

输出结果:三个地址完全相同,证明成员共享内存。

(二)联合体的核心特点
1、内存共享

所有成员共用同一块空间,修改一个成员会覆盖其他成员的值。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

union Un {
    char c;
    int i;
};

int main() {
    union Un un;
    un.i = 0x11223344;  // 给int成员赋值(4字节)
    printf("修改前 un.c = 0x%x\n", un.c);  // 输出 0x44(小端存储下的首字节)
    
    un.c = 0x55;        // 修改char成员(覆盖首字节)
    printf("修改后 un.i = 0x%x\n", un.i);  // 输出 0x11223355(首字节被改为55)
    return 0;
}
2、瞬时独占性

同一时间只能有效使用一个成员(因为修改一个会影响其他),需自行维护 "当前有效成员" 的逻辑。

3、与结构体的本质区别
对比维度 结构体(struct) 联合体(union)
内存分配方式 为每个成员单独分配独立的内存空间 所有成员共享同一块内存空间
成员独立性 成员相互独立,修改一个成员不会影响其他成员 成员互斥,修改一个成员会覆盖其他成员的值
内存占用大小 所有成员内存大小之和 (需考虑内存对齐) 等于联合体中最大成员的内存大小(需考虑内存对齐)
适用场景 存储关联且需同时存在、同时访问的数据(例如:学生信息包含的姓名、年龄、学号、成绩) 存储互斥且不同时使用的数据 (例如:商品属性中,"重量" 与 "体积" 仅需保留其一,或 "整数价格" 与 "小数价格" 二选一)
(三) 联合体大小的计算
1、两个规则

联合体大小需遵循两个规则,本质是平衡内存利用率与 CPU 访问效率(内存对齐):

(1)基础规则:联合体大小至少是最大成员的大小。(需容纳最大成员)

(2)对齐规则:若最大成员的大小 不是最大对齐数的整数倍 ,需填充内存至最大对齐数的整数倍。(为了让所有成员都能满足自身对齐要求)

(3)关键概念:对齐数

① 每个成员的对齐数 = 编译器默认对齐数(通常为 4 或 8)与成员自身大小的较小值。

② 联合体的最大对齐数 = 所有成员对齐数中的最大值。

2、具体示例

(1)示例 1:含数组与 int 的联合体

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

// 联合体:char arr[5](5字节,对齐数1)、int i(4字节,对齐数4)
union Un1 {
    char arr[5];  // 成员1:5字节,对齐数1
    int i;        // 成员2:4字节,对齐数4
};

int main() {
    // 计算大小:最大成员大小5,最大对齐数4;5不是4的倍数,填充至8(4×2)
    printf("union Un1 大小:%zd\n", sizeof(union Un1));  // 输出 8
    return 0;
}

(2)示例 2:含短整型数组与 int 的联合体

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

// 联合体:short arr[7](14字节,对齐数2)、int i(4字节,对齐数4)
union Un2 {
    short arr[7];  // 成员1:7×2=14字节,对齐数2
    int i;         // 成员2:4字节,对齐数4
};

int main() {
    // 计算大小:最大成员大小14,最大对齐数4;14不是4的倍数,填充至16(4×4)
    printf("union Un2 大小:%zd\n", sizeof(union Un2));  // 输出 16
    return 0;
}
(四)联合体的典型应用场景
1、场景 1:礼品兑换单设计(节省内存)

(1)问题

礼品兑换单需描述三种商品(图书、杯子、衬衫),它们有公共属性(库存量、价格、类型),但特殊属性互斥(图书需书名 / 作者,杯子需设计,衬衫需设计 / 颜色 / 尺寸)。若用结构体存储所有属性,会浪费大量内存(如描述图书时,杯子 / 衬衫的特殊属性空间闲置)。

(2)解决方案

用联合体存储特殊属性(互斥数据共享空间),结构体存储公共属性

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

// 礼品兑换单结构体:使用 "公共属性+联合体存储特殊属性" 的设计
struct GiftList {
    int stock_number;  // 公共属性:库存量
    float price;       // 公共属性:价格
    char type;         // 公共属性:商品类型('B'=图书,'M'=杯子,'S'=衬衫)

    // 联合体:存储互斥的特殊属性,成员共享内存
    union {
        struct {
            char title[50];  // 特殊属性:书名
            char author[30]; // 特殊属性:作者
            int pages;       // 特殊属性:页数
        } book;

        struct {
            char design[30]; // 特殊属性:设计图案
        } mug;

        struct {
            char design[30]; // 特殊属性:设计图案
            char color[20];  // 特殊属性:可选颜色
            char size[10];   // 特殊属性:可选尺寸
        } shirt;
    } info; 
};

int main() {
    // 1. 初始化图书数据
    struct GiftList book_gift = {
        .stock_number = 100,  // 图书库存
        .price = 59.9f,       // 图书价格(补充打印)
        .type = 'B',          // 商品类型(图书)
        .info.book = {        // 初始化图书特殊属性,符合文件联合体初始化规则
            "C语言程序设计",  // 书名
            "张三",           // 作者
            300               // 页数(补充打印)
        }
    };
    // 完整打印图书所有属性:公共属性(库存、价格、类型)+ 特殊属性(书名、作者、页数)
    printf("==================== 图书信息 ====================\n");
    printf("商品类型:%c('B'=图书)\n", book_gift.type);
    printf("书名:《%s》\n", book_gift.info.book.title);
    printf("作者:%s\n", book_gift.info.book.author);
    printf("页数:%d 页\n", book_gift.info.book.pages);
    printf("库存量:%d 本\n", book_gift.stock_number);
    printf("单价:%.1f 元\n", book_gift.price);  // 浮点型打印保留1位小数,更直观
    printf("==================================================\n");
    printf("\n");

    // 2. 初始化衬衫数据
    struct GiftList shirt_gift = {
        .stock_number = 50,   // 衬衫库存
        .price = 99.0f,       // 衬衫价格(补充打印)
        .type = 'S',          // 商品类型(衬衫)
        .info.shirt = {       // 初始化衬衫特殊属性,符合文件联合体初始化规则
            "条纹",           // 设计图案
            "白色/蓝色",      // 可选颜色
            "M/L/XL"          // 可选尺寸(补充打印)
        }
    };
    // 完整打印衬衫所有属性:公共属性(库存、价格、类型)+ 特殊属性(设计、颜色、尺寸)
    printf("==================== 衬衫信息 ====================\n");
    printf("商品类型:%c('S'=衬衫)\n", shirt_gift.type);
    printf("设计图案:%s\n", shirt_gift.info.shirt.design);
    printf("可选颜色:%s\n", shirt_gift.info.shirt.color);
    printf("可选尺寸:%s\n", shirt_gift.info.shirt.size);  // 补充尺寸打印
    printf("库存量:%d 件\n", shirt_gift.stock_number);
    printf("单价:%.1f 元\n", shirt_gift.price);  // 浮点型打印保留1位小数
    printf("==================================================\n");

    return 0;
}
2、场景 2:判断机器大小端存储

(1)大小端定义

**① 大端存储:**数据的高位字节存放在低地址(如 0x11223344 存储为 11 22 33 44)。

**② 小端存储:**数据的低位字节存放在低地址(如 0x11223344 存储为 44 33 22 11)。

(2)核心思路

利用联合体成员共享内存的特性,给 int 成员赋值 1(二进制 0x00000001),通过 char 成员读取首字节值:

若首字节为 1(低地址存低位字节),则为小端。

若首字节为 0(低地址存高位字节),则为大端。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

// 函数:判断大小端,返回1=小端,0=大端
int check_endian() {
    // 定义匿名联合体(仅用一次,无需命名)
    union {
        int i;    // 4字节成员
        char c;   // 1字节成员(共享首字节)
    } un;
    
    un.i = 1;  // 给int赋值1,二进制 0x00000001
    return un.c;  // 读取首字节值:1=小端,0=大端
}

int main() {
    if (check_endian() == 1) {
        printf("当前机器为小端存储\n");
    } else {
        printf("当前机器为大端存储\n");
    }
    return 0;
}

优势代码简洁 (无需指针强制类型转换),逻辑直观(利用联合体内存共享特性)。

二、枚举

枚举类型(enum)是 C 语言中一种自定义类型,通过为整型常量赋予语义化名称,解决 "魔法数字" 导致的代码可读性差、维护成本高的问题,核心价值在于提升代码的直观性与可维护性。

(一)枚举的定义与语法

enum 枚举类型名 { 枚举常量1, 枚举常量2, ..., 枚举常量n };

1、枚举常量与枚举变量

(1)枚举常量

① 默认从 0 开始自动递增;

② 也可显式指定值,支持跳跃赋值,后续常量会基于前一个值递增。

cpp 复制代码
// 计算器功能选项(显式赋值,与菜单数字对应)
enum Option {
    EXIT = 0,  // 退出(对应菜单0)
    ADD = 1,   // 加法(对应菜单1)
    SUB = 2,   // 减法(对应菜单2)
    MUL = 3,   // 乘法(对应菜单3)
    DIV = 4    // 除法(对应菜单4)
};

// 状态机(默认从0递增)
enum State {
    RUNNING,  // 0:运行中
    PAUSED,   // 1:暂停
    STOPPED   // 2:停止
};

关键特性:

枚举常量本质是整型,但通过命名体现语义(如 ADD 比 1 更直观)。C 语言中枚举常量可隐式转换为 int,但 C++ 中类型检查严格,需显式转换,如 (int)ADD 。

(2)枚举变量

通过枚举类型创建变量,如enum Week today = MONDAY;,变量仅能赋值为枚举常量。下面通过一个完整的例子来理解枚举变量的赋值规则:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

// 定义枚举类型enum Week,包含7个枚举常量
enum Week {
    ZERO,
    MONDAY,    // 枚举常量,默认值0
    TUESDAY,   // 枚举常量,默认值1
    WEDNESDAY, // 枚举常量,默认值2
    THURSDAY,  // 枚举常量,默认值3
    FRIDAY,    // 枚举常量,默认值4
    SATURDAY,  // 枚举常量,默认值5
    SUNDAY     // 枚举常量,默认值6
};

int main() {
    // 创建枚举变量today,类型是enum Week
    enum Week today;
    
    // 正确赋值:使用enum Week中定义的枚举常量
    today = MONDAY;    // 合法,将"周一"赋值给today
    printf("今天是周几:%d\n", today);  // 输出:今天是周几:0
    
    today = FRIDAY;    // 合法,将"周五"赋值给today
    printf("今天是周几:%d\n", today);  // 输出:今天是周几:4
    
    // 错误赋值:使用非枚举常量(C语言可能警告,C++直接报错)
    today = 3;         // 不推荐:虽然3对应THURSDAY,但失去了枚举的可读性
    today = 10;        // 错误:10不是enum Week中定义的常量(超出范围)
    today = "周一";    // 错误:类型不匹配(字符串不能赋值给枚举变量)
    
    return 0;
}
(二)枚举常量的自定义赋值

可显式指定枚举常量的值,未指定的常量会在前一个常量的基础上递增 1;

注意这不是在改动它,而是给它增加一个初始值,枚举里面的值是不能被修改的。

cpp 复制代码
enum Week {
    MON = 1,  // 1
    TUE,      // 2(1+1)
    WED = 5,  // 5
    THU       // 6(5+1)
};
(三)枚举的应用场景
1、场景 1:switch-case 分支(计算器菜单优化)

**(1)问题:**原始代码用数字 1/2/3/4 对应加减乘除,需记忆数字与功能的映射,可读性差。

(2)优化方案:用枚举常量替代数字,逻辑直观且无需记忆映射关系。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

// 定义枚举类型
enum Option {
    EXIT = 0,
    ADD = 1,
    SUB = 2,
    MUL = 3,
    DIV = 4
};

int main() {
    int input = 0;
    do {
        // 打印菜单
        printf("1. 加法\n2. 减法\n3. 乘法\n4. 除法\n0. 退出\n请选择:");
        scanf("%d", &input);
        
        // 用枚举常量判断分支
        switch (input) {
            case ADD:  // 直观:直接对应"加法"
                printf("执行加法\n");
                break;
            case SUB:  // 直观:直接对应"减法"
                printf("执行减法\n");
                break;
            case MUL:
                printf("执行乘法\n");
                break;
            case DIV:
                printf("执行除法\n");
                break;
            case EXIT:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误,请重新输入\n");
        }
    } while (input != EXIT);
    return 0;
}
2、场景 2:函数参数限制(限定输入范围)

通过枚举类型作为函数参数,强制调用者只能传入预定义的合法值,减少非法输入。在C语言中,为枚举类型的函数参数赋值,最好使用枚举常量;虽然在C语言中可以传入非枚举常量,但是在C++中是不允许的。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>  // 包含printf函数所需头文件

// 1. 定义枚举类型Color,显式指定常量值(参考文件枚举声明语法)
enum Color {
    RED = 1,    // 红色,值为1
    GREEN = 2,  // 绿色,值为2
    BLUE = 4    // 蓝色,值为4
};

// 2. 定义设置文本颜色的函数,参数为枚举类型Color(参考文件枚举函数参数用法)
void setColor(enum Color clr) {
    // 通过switch-case匹配枚举常量,实现语义化分支(参考文件计算器案例逻辑)
    switch (clr) {
        case RED:
            printf("设置为红色\n");
            break;  // 跳出分支,避免穿透
        case GREEN:
            printf("设置为绿色\n");
            break;
        case BLUE:
            printf("设置为蓝色\n");
            break;
        default:  // 处理非法枚举值(C中可能传入非枚举常量,增加健壮性)
            printf("无效的颜色选项,请传入RED、GREEN或BLUE\n");
    }
}

// 3. 主函数:调用setColor函数,验证枚举类型使用
int main() {
    printf("=== 测试枚举类型设置文本颜色 ===\n");
    
    // 正确调用:传入枚举常量(参考文件枚举变量赋值规则)
    setColor(RED);    // 输出"设置为红色"
    setColor(GREEN);  // 输出"设置为绿色"
    setColor(BLUE);   // 输出"设置为蓝色"
    
    // 错误示例(C中允许,C++中报错):传入非枚举常量(参考文件枚举类型安全说明)
    // setColor(5);  // 编译运行后输出"无效的颜色选项,请传入RED、GREEN或BLUE"
    
    return 0;
}
(四)枚举与 #define 的对比(优点)

1、增加代码的可读性和可维护性。

2、和 #define 定义的标识符比较枚举有类型检查,更加严谨。

3、便于调试,预处理阶段会删除 #define 定义的符号。

4、使用方便,一次可以定义多个常量。

5、枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使使用。

对比维度 枚举 #define
类型检查 有类型检查(C++ 中严格禁止整型与枚举类型隐式转换) 无类型检查,仅文本替换
调试支持 保留符号信息,调试时可看到常量名(如 MALE) 预处理阶段删除符号,调试时仅显示数值(如 0)
批量定义 单次声明可定义多个关联常量,语法简洁 需逐一定义,繁琐且易出错
作用域 遵循块作用域(函数内声明的枚举仅限函数内使用) 全局作用域,可能引发命名冲突

枚举的优势总结:

**① 消除魔法数字:**用 ADD 替代 1,无需记忆数值与功能的映射。

**② 增强可维护性:**若菜单功能调整(如新增 "取余"),仅需修改枚举定义,无需全局替换数字。

**③ 提升可读性:**代码逻辑直观,新手可快速理解 case ADD 对应 "加法" 操作。

三、联合体与枚举总结

类型 核心特性 关键优势 典型应用场景
联合 所有成员共享内存空间 节省内存(存储互斥数据) 协议解析、变体记录、大小端判断
枚举 为整型常量赋予语义化名称 提升可读性、降低维护成本 菜单选项、状态机、配置参数

枚举与联合体均为 "轻量级" 自定义类型,合理使用可显著提升代码质量。

枚举 解决 "语义模糊" 问题联合体****解决 "内存浪费" 问题,二者常结合结构体在实际项目中(如嵌入式协议、数据解析、资源管理)发挥重要作用。

以上即为 自定义类型:联合体与枚举 的全部内容,麻烦三连支持一下呗~

相关推荐
进击的小头2 小时前
行为型模式:策略模式的C语言实战指南
c语言·开发语言·策略模式
爱编码的小八嘎3 小时前
C语言对话-5.通过任何其他名字
c语言
定偶5 小时前
C语言入门指南
c语言·开发语言
的卢马飞快6 小时前
【C语言进阶】给数据一个“家”:从零开始掌握文件操作
c语言·网络·数据库
我能坚持多久6 小时前
D17—C语言结构体详解:从声明、对齐到位段应用
c语言·开发语言
willingli6 小时前
c语言经典题目 91-100
c语言
傻乐u兔7 小时前
C语音进阶————数据在内存中的存储2
c语言·开发语言·算法
麒qiqi8 小时前
ADC 的原理与实战
c语言·开发语言·单片机·嵌入式硬件
比昨天多敲两行8 小时前
C/C++内存管理
c语言·开发语言·jvm