C 语言从 0 入门(十九)|共用体与枚举:自定义类型进阶

大家好,我是网域小星球。

前面我们学习了结构体 ------ 一种能打包多种不同类型数据的自定义类型,解决了复杂对象的描述问题。而在实际编程中,我们还会遇到两种特殊场景:

  1. 一块内存空间,同时只能存储一种数据(节省内存);
  2. 变量的取值,只能是固定的几个常量(规范取值,避免出错)。

C 语言提供的「共用体」和「枚举」,就专门解决这两个问题。它们和结构体同属自定义类型,用法简单、场景明确,是进阶编程和笔试面试的基础知识点。本篇全程 VS2022 可直接运行,从概念到实战,一步步吃透。

目录

一、本章学习目标

二、共用体(Union):共享内存的自定义类型

[2.1 什么是共用体?](#2.1 什么是共用体?)

[2.2 共用体的定义语法](#2.2 共用体的定义语法)

[2.3 共用体的初始化与使用](#2.3 共用体的初始化与使用)

(1)初始化

(2)成员访问

[2.4 共用体的核心特性与适用场景](#2.4 共用体的核心特性与适用场景)

核心特性

适用场景

[2.5 共用体与结构体的区别(重点)](#2.5 共用体与结构体的区别(重点))

三、枚举(Enum):固定常量的自定义类型

[3.1 什么是枚举?](#3.1 什么是枚举?)

[3.2 枚举的定义语法](#3.2 枚举的定义语法)

[3.3 枚举变量的定义与使用](#3.3 枚举变量的定义与使用)

(1)定义枚举变量

(2)使用枚举变量

[3.4 枚举的核心特性与适用场景](#3.4 枚举的核心特性与适用场景)

核心特性

适用场景

[四、综合实战案例(VS2022 可直接运行)](#四、综合实战案例(VS2022 可直接运行))

五、高频易错点

六、本章核心总结

下期预告

一、本章学习目标

学完本篇你将彻底掌握:

  1. 共用体(Union)的定义、初始化与使用,理解 "共享内存" 的核心特性;
  2. 共用体与结构体的区别,明确共用体的适用场景;
  3. 枚举(Enum)的定义、初始化与使用,掌握固定常量的定义方式;
  4. 枚举的实际应用,避免 "魔法数字",让代码更规范、易读;
  5. 共用体与枚举的实战案例,学会在项目中灵活运用。

二、共用体(Union):共享内存的自定义类型

2.1 什么是共用体?

共用体,也叫联合体,核心特点是:所有成员共享同一块内存空间,同一时间只能存储一个成员的值。

  • 结构体:所有成员 "各自拥有独立内存",总大小是所有成员大小之和;
  • 共用体:所有成员 "共享一块内存",总大小是最大成员的大小(保证能容纳任何一个成员)。

可以形象理解:

  • 结构体 → 多个独立的房间(每个成员一间房);
  • 共用体 → 一个房间,轮流住不同的人(每个成员轮流使用这块内存)。

2.2 共用体的定义语法

和结构体语法类似,只需把struct换成union

cpp 复制代码
// 定义共用体类型
union 共用体名
{
    数据类型 成员1;
    数据类型 成员2;
    ...
};

示例:定义一个能存储 int、char、float 的共用体

cpp 复制代码
// 共用体:同一时间只能存储一个类型的值
union Data
{
    int a;      // 4字节
    char b;     // 1字节
    float c;    // 4字节
};

这个共用体的大小是 4 字节(最大成员 int 和 float 都是 4 字节),而非 4+1+4=9 字节。

2.3 共用体的初始化与使用

(1)初始化

共用体只能初始化一个成员(因为同一时间只能存储一个值),语法和结构体类似:

cpp 复制代码
// 初始化共用体,只能给一个成员赋值
union Data d1 = {10};          // 初始化成员a(int类型)
union Data d2 = {.b = 'A'};    // 指定初始化成员b(char类型)
union Data d3 = {.c = 3.14f};  // 指定初始化成员c(float类型)
(2)成员访问

和结构体一样,用.访问成员;如果是共用体指针,用->访问:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

union Data
{
    int a;
    char b;
    float c;
};

int main()
{
    union Data d;
    // 给成员a赋值,此时共用体存储的是int类型
    d.a = 0x12345678;
    printf("d.a = %d\n", d.a);  // 输出:305419896(0x12345678对应的十进制)
    
    // 访问成员b(共享同一块内存,值会受a的影响)
    printf("d.b = %c\n", d.b);  // 输出:x(0x78对应的ASCII值)
    
    // 给成员c赋值,会覆盖a的值(共用体只能存一个值)
    d.c = 3.14f;
    printf("d.c = %.2f\n", d.c); // 输出:3.14
    printf("d.a = %d\n", d.a);  // 输出:随机值(被c覆盖)
    
    return 0;
}

2.4 共用体的核心特性与适用场景

核心特性
  1. 共享内存:所有成员占用同一块内存,修改一个成员,会覆盖其他成员的值;
  2. 大小 = 最大成员的大小:节省内存空间;
  3. 同一时间只能使用一个成员:不能同时给多个成员赋值并使用。
适用场景
  1. 节省内存:当数据之间 "互斥"(同一时间只需要存储一种)时,比如:
    • 存储学生的 "成绩类型":要么是 int(百分制),要么是 char(等级制 A/B/C);
    • 存储设备数据:要么是 int(整数指令),要么是 float(浮点数据)。
  2. 类型转换:利用共享内存的特性,实现不同类型数据的强制转换(进阶用法)。

2.5 共用体与结构体的区别(重点)

特性 结构体(struct) 共用体(union)
内存分配 所有成员独立分配内存 所有成员共享一块内存
总大小 所有成员大小之和(含内存对齐) 最大成员的大小
成员使用 可同时使用所有成员 同一时间只能使用一个成员
核心用途 描述复杂对象(多种数据共存) 节省内存(互斥数据存储)

三、枚举(Enum):固定常量的自定义类型

3.1 什么是枚举?

枚举,就是 "列举",核心作用是:定义一组固定的、有名字的常量,让代码更规范、易读,避免直接使用 "魔法数字"(无意义的数字常量)。

比如:

  • 一周的天数:周一~周日(固定 7 个值);
  • 性别:男、女、未知(固定 3 个值);
  • 状态:成功、失败、等待(固定 3 个值)。

这些场景用枚举定义,比直接写 1、2、3 更易理解,也能避免写错值。

3.2 枚举的定义语法

cpp 复制代码
// 定义枚举类型
enum 枚举名
{
    常量1,
    常量2,
    ...
    常量n
};

示例 1:定义 "星期" 枚举

cpp 复制代码
// 枚举:一周的七天,固定7个常量
enum Week
{
    Monday,    // 默认值0
    Tuesday,   // 1(自动递增1)
    Wednesday, // 2
    Thursday,  // 3
    Friday,    // 4
    Saturday,  // 5
    Sunday     // 6
};

示例 2:自定义枚举值

cpp 复制代码
// 自定义枚举值,未指定的自动递增
enum Gender
{
    Male = 1,   // 自定义值1
    Female = 2, // 自定义值2
    Unknown     // 自动为3
};

3.3 枚举变量的定义与使用

(1)定义枚举变量

有 3 种常用方式,推荐前两种(更规范):

cpp 复制代码
// 方式1:先定义枚举类型,再定义变量
enum Week w1;

// 方式2:定义枚举类型的同时,定义变量
enum Week w2, w3;

// 方式3:省略枚举名(不推荐,无法重复使用该枚举类型)
enum
{
    Success,
    Failed,
    Waiting
} status;
(2)使用枚举变量

枚举变量的取值,只能是枚举中定义的常量,不能赋值其他值(编译器会警告):

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

// 定义枚举:状态
enum Status
{
    Success = 0,  // 成功
    Failed = 1,   // 失败
    Waiting = 2   // 等待
};

int main()
{
    enum Status s = Success;
    
    // 枚举变量赋值(只能用枚举常量)
    s = Waiting;
    
    // 用switch判断枚举值(最常用场景)
    switch (s)
    {
        case Success:
            printf("操作成功!\n");
            break;
        case Failed:
            printf("操作失败!\n");
            break;
        case Waiting:
            printf("操作等待中...\n"); // 输出此句
            break;
        default:
            printf("无效状态!\n");
    }
    
    return 0;
}

3.4 枚举的核心特性与适用场景

核心特性
  1. 枚举常量是常量 ,不能修改(比如不能写Monday = 10);
  2. 枚举变量的取值,只能是枚举中定义的常量,超出范围会有警告;
  3. 枚举常量默认从 0 开始,依次递增 1;也可以自定义每个常量的值;
  4. 枚举本质是 int 类型,枚举常量的值可以参与整数运算。
适用场景
  1. 固定取值的场景:星期、性别、状态、颜色等;
  2. 替代 "魔法数字":比如用Success替代 0,Failed替代 1,让代码更易读、易维护;
  3. 函数参数限制:让函数参数只能取固定值,避免传入无效参数(进阶用法)。

四、综合实战案例(VS2022 可直接运行)

结合共用体和枚举,实现一个 "学生成绩录入" 程序,支持两种成绩类型(百分制 / 等级制),用枚举限制成绩类型,用共用体存储成绩:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1<stdio.h>

// 枚举:成绩类型(百分制/等级制)
enum ScoreType
{
    Percent = 1,  // 百分制(int)
    Grade         // 等级制(char),自动为2
};

// 共用体:存储成绩(同一时间只能存一种类型)
union Score
{
    int percent;  // 百分制成绩(0-100)
    char grade;   // 等级制成绩(A/B/C/D/E)
};

// 结构体:学生信息(包含成绩类型和成绩)
struct Student
{
    int id;
    char name[20];
    enum ScoreType type;  // 成绩类型
    union Score score;    // 成绩(共用体)
};

int main()
{
    struct Student stu;
    
    printf("请输入学生信息:\n");
    printf("学号:");
    scanf("%d", &stu.id);
    printf("姓名:");
    scanf("%s", stu.name);
    printf("成绩类型(1=百分制,2=等级制):");
    scanf("%d", &stu.type);
    
    // 根据成绩类型,录入对应成绩
    if (stu.type == Percent)
    {
        printf("请输入百分制成绩(0-100):");
        scanf("%d", &stu.score.percent);
    }
    else if (stu.type == Grade)
    {
        printf("请输入等级制成绩(A-E):");
        scanf(" %c", &stu.score.grade); // 空格避免读取换行符
    }
    else
    {
        printf("无效的成绩类型!\n");
        return 0;
    }
    
    // 输出学生信息
    printf("\n学生信息如下:\n");
    printf("学号:%d\n", stu.id);
    printf("姓名:%s\n", stu.name);
    printf("成绩:");
    if (stu.type == Percent)
    {
        printf("%d分(百分制)\n", stu.score.percent);
    }
    else
    {
        printf("%c级(等级制)\n", stu.score.grade);
    }
    
    return 0;
}

五、高频易错点

  1. 共用体使用误区:误以为能同时存储多个成员的值,修改一个成员后,其他成员的值会被覆盖;
  2. 共用体初始化错误:同时给多个成员赋值,编译器会报错或警告;
  3. 枚举使用误区:给枚举变量赋值枚举外的常量,虽然可能运行,但不符合规范,易出错;
  4. 混淆枚举与结构体:误用共用体存储共存的数据(比如同时存储学生的年龄和成绩),导致数据错乱;
  5. 枚举常量修改:试图修改枚举常量的值(比如Sunday = 10),编译器会报错。

六、本章核心总结

  1. 共用体(Union):所有成员共享一块内存,同一时间只能存储一个成员,核心作用是节省内存
  2. 共用体大小 = 最大成员的大小,与结构体的 "成员内存独立" 有本质区别;
  3. 枚举(Enum):定义一组固定常量,核心作用是规范取值、避免魔法数字,让代码更易读;
  4. 枚举常量默认从 0 递增,可自定义值,枚举变量只能取枚举中定义的常量;
  5. 共用体 + 枚举 + 结构体,可实现更灵活、规范的自定义数据管理,是进阶编程的基础。

下期预告

下一篇我们进入指针进阶内容,拆解易混的三大指针:指针数组、数组指针、函数指针,掌握它们的定义、区别与实战用法,攻克 C 语言指针的核心难点。

相关推荐
老花眼猫2 小时前
数学艺术图案画-曼陀罗(二)
c语言·经验分享·青少年编程·课程设计
Evand J2 小时前
【滤波代码介绍|MATLAB】粒子滤波(PF)与自适应粒子滤波(APF)在三维动态系统状态估计中的对比,使用Sage Husa自适应的思想
开发语言·matlab·pf·粒子滤波·apf·自适应滤波
favour_you___2 小时前
算法练习2026/4/13
算法·深度优先
zybsjn2 小时前
异步并发的“流量警察”:在C#中使用SemaphoreSlim进行并发控制的最佳实践
开发语言·c#
吃着火锅x唱着歌2 小时前
LeetCode 1963 使字符串平衡的最小交换次数
算法·leetcode·职场和发展
Cx330❀2 小时前
线程进阶实战:资源划分与线程控制核心指南
java·大数据·linux·运维·服务器·开发语言·搜索引擎
人道领域2 小时前
【黑马点评日记02】:Session+ThreadLocal实现短信登录
java·开发语言·spring·tomcat·intellij-idea
广州灵眸科技有限公司2 小时前
瑞芯微(EASY EAI)RV1126B 系统操作-线进程操作
开发语言·科技·嵌入式硬件·物联网
无敌昊哥战神2 小时前
【算法与数据结构】深入浅出回溯算法:理论基础与核心模板(C/C++与Python三语解析)
c语言·数据结构·c++·笔记·python·算法