C++枚举新手入门教程

很多刚学 C++ 的朋友,写代码的时候总喜欢用一堆 012 来代表状态,比如用 0 代表周一,1 代表周二,过了半个月自己看代码都忘了这堆数字是什么意思,更别说别人来读你的代码了。

今天这篇文章,我们就来搞定这个问题 ------枚举,全程没有晦涩的术语,从 0 开始带你搞懂枚举,看完你就能直接上手,写出一眼就能看懂的干净代码。


一、什么是枚举?说白了就是给常量起个好懂的名字

枚举(英文名 Enumeration,简称 enum),说白了就是:把一组固定的、相关的常量,给它们起个语义化的名字,打包成一个新的类型

比如,一周只有 7 天,那我们就可以把这 7 天打包成一个 Weekday 枚举类型,以后表示星期的时候,就用 Weekday::Monday,而不是写 0,谁看了都懂。

它的核心作用就是:告别魔法数字,让代码更可读、更安全


二、传统枚举:入门的第一步,先学会用

我们先从最基础的传统枚举开始,这是 C++98 就有的,也是很多人刚学 C++ 最先接触到的枚举。

2.1 怎么定义枚举?一行代码搞定

定义的语法非常简单:

cpp 复制代码
enum 枚举名 {
    枚举值1, // 每个值都是常量
    枚举值2,
    枚举值3,
    // ... 最多可以写你需要的所有值
};

举个最常见的例子,定义星期的枚举:

cpp 复制代码
// 定义一个叫 Weekday 的枚举,代表一周的 7 天
enum Weekday {
    Monday,     // 第一个值,默认是 0
    Tuesday,    // 后面的自动 +1,所以是 1
    Wednesday,  // 2
    Thursday,   // 3
    Friday,     // 4
    Saturday,   // 5
    Sunday      // 6
};

你也可以手动给枚举值指定数字,比如:

cpp 复制代码
enum ScoreLevel {
    Low = 50,   // 我指定 Low 是 50
    Medium = 70,// Medium 是 70
    High = 90,  // High 是 90
    Perfect = 100 // Perfect 是 100
};

如果后面的没指定,就会自动在前一个的基础上 +1,比如你写 Low=50, Medium, High,那 Medium 就是 51,High 是 52。

2.2 枚举可以写在哪里?三种位置按需选

很多新手会问:我这个枚举的定义,到底写在代码的哪个位置?其实有三个地方可以写,看你需要它的作用范围:

(1)全局声明:整个程序都能用

如果你这个枚举很多地方都要用,就写在所有函数和类的外面,全局作用域:

cpp 复制代码
// 全局声明,整个文件的所有函数都能用
enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };

void print_today() {
    // 直接用,因为全局的
    Weekday today = Wednesday;
}

int main() {
    Weekday tomorrow = Thursday; // 这里也能用
}
(2)类内声明:只有这个类能用

如果这个枚举只是某个类自己用的,就写在类的里面:

cpp 复制代码
class NetworkConnection {
public:
    // 类内的枚举,只有 NetworkConnection 能用
    enum State {
        Disconnected,
        Connecting,
        Connected,
        Disconnecting
    };

    State get_state() {
        return m_state;
    }
private:
    State m_state;
};

// 外面用的时候,要加类名::
int main() {
    NetworkConnection conn;
    NetworkConnection::State s = conn.get_state();
    if (s == NetworkConnection::Connected) {
        // 已经连接了
    }
}
(3)函数内声明:只有这个函数能用

如果这个枚举只是某个函数自己临时用的,就写在函数里面:

cpp 复制代码
void test_func() {
    // 函数内的枚举,只有这个函数能用,外面看不到
    enum LocalTest { A, B, C };
    LocalTest e = A;
}

2.3 枚举怎么用?最常用的场景都在这

定义好之后,用起来也很简单:

cpp 复制代码
#include <iostream>
using namespace std;

enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };

int main() {
    // 1. 声明枚举变量,就和普通的 int、bool 一样
    Weekday today = Wednesday;

    // 2. 用来做判断
    if (today == Wednesday) {
        cout << "今天是周三,摸鱼的好日子~" << endl;
    }

    // 3. 用来做 switch,这是枚举最常用的场景!
    switch(today) {
        case Monday: cout << "周一,不想上班" << endl; break;
        case Tuesday: cout << "周二,慢慢适应" << endl; break;
        case Wednesday: cout << "周三,过半了!" << endl; break;
        // ... 剩下的 case
    }

    return 0;
}

2.4 枚举和整数怎么转?新手必看

很多新手会好奇:枚举变量能不能当整数用?答案是:传统枚举可以自动转成整数

因为枚举的本质,底层就是整数,所以:

cpp 复制代码
Weekday today = Wednesday;
// 传统枚举可以自动转成 int,直接赋值
int num = today; 
cout << "Wednesday 对应的数字是:" << num << endl; // 输出 2

反过来,你也可以把整数转成枚举,不过要手动强转:

cpp 复制代码
// 把整数 1 转成 Weekday,就是 Tuesday
Weekday t = static_cast<Weekday>(1);

⚠️ 注意:这里的整数如果不是你定义的枚举值,比如你转个 100,编译器也不会报错!因为它只看你是不是在底层类型的范围内,但是逻辑上这是个无效的星期,所以转的时候要小心。


三、传统枚举的坑:项目大了必踩的两个雷

传统枚举用起来很简单,但是用着用着,你就会发现它有两个致命的坑,尤其是项目大了之后,特别容易出 bug。

坑 1:名字会撞!两个无关的枚举不能重名

传统枚举的枚举值,会直接暴露在外面的作用域里,也就是说,如果你定义了两个枚举,里面有重名的枚举值,就会直接报错!

举个例子:

cpp 复制代码
// 我先写了颜色的枚举,有个 Red
enum Color { Red, Green, Blue };

// 后来我想加个水果的枚举,也有个 Red(红苹果的红)
enum Fruit { Red, Apple, Banana };

// 编译直接报错!Red 重定义了!

这就很离谱,两个完全没关系的枚举,就因为名字一样,就不能同时用?这就是传统枚举的问题:它的枚举值会到外面的作用域,污染了命名空间。

坑 2:类型不安全!颜色和水果居然能比较?

传统枚举因为能自动转成整数,所以导致两个完全没关系的枚举,居然能直接比较、赋值,编译器根本不会拦着你!

比如:

cpp 复制代码
enum Color { Red, Green, Blue };
enum Fruit { Apple, Banana };

Color c = Red; // 颜色的 Red,值是 0
Fruit f = Apple; // 水果的 Apple,值也是 0

// 编译器居然允许!这俩完全没关系啊,但是因为都转成 int,0==0,就相等了
if (c == f) {
    cout << "这显然不对啊,颜色怎么能和水果比?" << endl;
}

// 甚至还能直接赋值!
int num = c; // 把颜色转成 int,完全没问题,但是很容易写错

这就很容易出 bug,比如你本来想传个颜色给函数,结果不小心传了个水果,编译器根本发现不了,等到运行的时候才出问题,找 bug 找半天。


四、强类型枚举:解决所有问题的终极方案

为了搞定传统枚举的这两个坑,C++11 引入了强类型枚举 ,也叫 enum class,这就是现代 C++ 推荐用的枚举,它完美解决了传统枚举的所有问题,而且用起来也很简单。

4.1 怎么定义?就比传统枚举多两个字

和传统枚举几乎一样,就是把 enum 改成 enum class(或者 enum struct,这俩完全一样,随便选):

cpp 复制代码
// 基础语法
enum class/struct 枚举名 [: 底层类型(可选)] {
    枚举值1,
    枚举值2,
    // ...
};

比如我们把之前的颜色和水果的枚举改成强类型的:

cpp 复制代码
// 颜色的强类型枚举
enum class Color { Red, Green, Blue };
// 水果的强类型枚举,也有 Red,完全没问题!
enum class Fruit { Red, Apple, Banana };

这次编译完全不会报错!因为两个 Red 不会撞名字了。

4.2 怎么用?再也不怕名字撞了

用的时候,必须加 枚举名:: 来访问枚举值,因为枚举值被封装在枚举自己的作用域里了,不会跑出去:

cpp 复制代码
int main() {
    // 必须加 Color::,不能直接写 Red 了
    Color c = Color::Red;
    // 水果的 Red,要加 Fruit::,和 Color::Red 完全没关系
    Fruit f = Fruit::Red;

    // 现在,两个 Red 不会撞了,完美解决了名字冲突的问题!
}

4.3 它怎么解决了类型安全的问题?

强类型枚举不会自动转成整数了!也不能和其他类型混用,编译器会帮你拦住所有错误的用法:

cpp 复制代码
Color c = Color::Red;
Fruit f = Fruit::Apple;

// 编译报错!不同的枚举类型不能直接比较!
// if (c == f) {} 

// 编译报错!不能自动转成 int!
// int num = c;

// 你必须手动强转,才能转成 int,这就说明你是故意的,不是不小心写错的
int num = static_cast<int>(c);

这下好了,你再也不会不小心把水果当成颜色传给函数了,编译器直接就报错,把 bug 扼杀在编译期。

4.4 额外好处:还能帮你省内存?

强类型枚举还支持你手动指定底层的存储类型,比如你这个枚举只有 3 个值,根本不需要 4 字节的 int,你就可以用 1 字节的 uint8\_t,节省内存:

cpp 复制代码
// 用 uint8_t 作为底层类型,整个枚举只占 1 字节!
enum class SmallEnum : uint8_t { A, B, C };

// 编译期验证大小,确保它真的只占 1 字节
static_assert(sizeof(SmallEnum) == 1);

这个对嵌入式、网络协议这种对内存很敏感的场景特别有用。


五、学会这两个用法,日常开发够用了

入门之后,你还可以了解一下这两个常用的用法,日常开发基本就够用了。

5.1 用枚举做权限标志位,比你想的还简单

枚举非常适合用来做权限、标志位,比如文件的读写执行权限,我们可以给强类型枚举重载位运算符:

cpp 复制代码
#include <cstdint>
enum class Permissions : uint8_t {
    NONE = 0,
    READ = 1 << 0,  // 0b001
    WRITE = 1 << 1, // 0b010
    EXEC = 1 << 2   // 0b100
};

// 重载 | 运算符,用来组合权限
constexpr Permissions operator|(Permissions lhs, Permissions rhs) {
    return static_cast<Permissions>(
        static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs)
    );
}

// 重载 & 运算符,用来检查权限
constexpr Permissions operator&(Permissions lhs, Permissions rhs) {
    return static_cast<Permissions>(
        static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs)
    );
}

int main() {
    // 组合权限:可读可写
    Permissions perms = Permissions::READ | Permissions::WRITE;
    
    // 检查有没有读权限
    if ((perms & Permissions::READ) != Permissions::NONE) {
        cout << "你有读权限!" << endl;
    }
}

5.2 遍历所有枚举值,批量处理超方便

有时候你需要把所有的枚举值都遍历一遍,比如打印所有的状态,最简单的方法就是手动加个 COUNT 标记:

cpp 复制代码
enum class Color { Red, Green, Blue, COUNT }; // COUNT 就是成员的数量

void iterate_all_colors() {
    // 遍历 0 到 COUNT-1
    for (int i = 0; i < static_cast<int>(Color::COUNT); ++i) {
        Color c = static_cast<Color>(i);
        // 处理每个颜色
    }
}

六、新手怎么选?用 enum 还是 enum class?

很多新手会问:我到底是用传统的 enum,还是强类型的 enum class

答案很简单:

  1. 新项目,优先用 enum class 它的类型安全能帮你避免 90% 的枚举相关 bug,除非你有特殊的需求。

  2. 只有这几种情况,才用传统 enum

    • 你要和老的 C 语言代码、旧接口兼容

    • 你需要隐式转成整数,而且是非常简单的场景

    • 你在维护老的旧代码,改不动了


七、新手避坑指南:这三个坑别踩

最后,给新手提几个最容易踩的坑:

  1. 不要假设枚举的默认值 :如果你写 Color c;,这个 c 是未初始化的,值是随机的,不是第一个枚举成员!需要默认值一定要显式写 Color c = Color::Red;

  2. 不要乱转整数:把整数转成枚举的时候,一定要确保这个值是合法的,不然虽然编译能过,但是逻辑上是无效的。

  3. 尽量显式指定底层类型 :不管是传统还是强类型,最好手动指定底层类型,比如 : uint8\_t,这样不同平台下的大小都是一样的,不会有兼容性问题。


好了,以上就是 C++ 枚举的全部入门内容了,是不是比你想的简单?

学会枚举之后,你再也不用写一堆看不懂的魔法数字了,代码的可读性直接拉满,还能避免很多莫名其妙的 bug。

如果这篇文章帮到你了,欢迎点赞收藏,有任何问题都可以在评论区留言~

相关推荐
许长安11 小时前
RPC 同步调用基本使用方法:基于官方 RouteGuide 示例
c++·经验分享·笔记·rpc
kyriewen1111 小时前
WebAssembly:前端界的“外挂”,让C++代码在浏览器里跑起来
开发语言·前端·javascript·c++·单元测试·ecmascript
浅念-14 小时前
刷穿LeetCode:BFS 解决 Flood Fill 算法
数据结构·c++·算法·leetcode·职场和发展·bfs·宽度优先
楼田莉子15 小时前
Linux网络:NAT_代理
linux·运维·服务器·开发语言·c++·后端
南境十里·墨染春水16 小时前
C++日志 2——实现单线程日志系统
java·jvm·c++
zh_xuan16 小时前
api测试工具添加历史记录功能
c++·libcurl·duilib
休息一下接着来16 小时前
C++ 固定容量环形队列实现
c++·算法
wxin_VXbishe18 小时前
springboot新能源车充电站管理系统小程序-计算机毕业设计源码29213
java·c++·spring boot·python·spring·django·php
05候补工程师19 小时前
【408 从零到一】线性表逻辑特征、存储结构对比与 C/C++ 动态内存分配避坑指南
c语言·开发语言·数据结构·c++·考研