
博主介绍程序喵大人35 - 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章首发gzh见文末记得订阅专栏以防走丢C基础系列专栏C语言基础系列专栏C大佬养成攻略专栏C训练营个人网站好文推荐【AIAgent项目】从零构建一个代码PRAgent【C地基】C必备知识点 - 01 一个最小 C 程序是怎么跑起来的【C地基】C必备知识点 - 02 类型和变量给一块数据定规则、起名字【C地基】C必备知识点 - 03 表达式和运算符一行代码怎样算出结果【C地基】C必备知识点 - 04 控制流程序为什么走这条路【C地基】C必备知识点 - 05 函数把一段逻辑装进一个可调用的盒子【C地基】C必备知识点 - 06 作用域和生命周期名字在哪能用对象活到什么时候【C地基】C必备知识点 - 07 引用给同一个对象再起一个名字https://blog.csdn.net/qq_29426201/article/details/164094785?spm1001.2014.3001.5501)【C地基】C必备知识点 - 08 const把“这里是否允许修改“写进类型里【C地基】C必备知识点 - 09 指针入门变量的地址也是一种数据【C地基】C必备知识点 - 10 auto让编译器替你写类型名随着程序规模的增长代码中需要管理的名字和状态会越来越多。分数等级、订单状态、菜单命令这些值本来就属于一组固定选项工具函数、业务函数、测试函数这些名字也需要有清楚的归属。枚举解决固定值集合的问题命名空间解决名字归属的问题。本章的核心判断只有两条枚举给一组合法值划边界命名空间给一组名字划边界这两个工具本身不复杂但它们决定了代码能不能长大。在一个小程序里用int status 2;表示状态、直接写一个Print()函数也能跑。但项目变大后状态越来越多函数名越来越多读者需要更明确的边界。枚举把哪些值合法写进类型命名空间把这个名字属于哪里写进代码结构。用整数表示状态容易写错先看一个用int表示订单状态的例子intstatus2;这行代码本身很难读。2是已支付、已发货还是已取消需要读者去找文档或注释。可以加常量constintkPending1;constintkPaid2;constintkShipped3;constintkCancelled4;intstatuskPaid;可读性好了一点但status仍然只是int。这意味着理论上你仍然可以写status999;编译器很难知道这是非法状态。对于需要约束取值范围的场景更适合的工具是枚举。用整数表示状态最大的问题是它把业务含义藏起来了。2对编译器只是一个普通数值对读者也只是一个数值读者必须额外记住2 代表 paid。这类隐含约定越多代码越容易出现状态错用。枚举的作用就是把这些约定从注释里搬进类型系统中让编译器帮忙检查。这类直接出现在代码里的数字常被称为魔法数字。它们的危险不在于数字本身而在于数字背后的含义没有在代码结构里体现——status 2看上去像普通的整数比较读者需要额外知道它的业务含义。常量能改善名字问题但状态变量仍然是整数。枚举继续往前走一步让状态本身变成一个独立类型。枚举把固定选项收成一个类型在现代 C 中优先使用enum classenumclassOrderStatus{kPending,kPaid,kShipped,kCancelled,};OrderStatus是一个独立类型它的合法值集中在大括号里。定义变量时OrderStatus statusOrderStatus::kPaid;读者能直接看到两点信息status是订单状态并且只能从这组状态里选择。enum class的枚举值必须通过类型名访问OrderStatus::kPending OrderStatus::kPaid OrderStatus::kShipped OrderStatus::kCancelled这让名字边界更清楚。kPaid属于OrderStatus使用时写完整范围读者不会把它和别处的同名kPaid混淆。可以写成通过枚举状态从普通整数变成了有边界的独立类型。这层边界会直接改善阅读体验。读到OrderStatus status你能知道它是订单状态读到OrderStatus::kPaid你能知道它是订单状态集合里的合法值。代码不再依赖读者去记魔法数字——状态越多枚举带来的清晰度提升越明显。枚举还能让函数接口更清楚。比较这两个声明std::stringToString(intstatus);std::stringToString(OrderStatus status);第一个版本需要一个整数调用者可能传入任何int比如分数或数组下标。第二个版本直接说明需要订单状态调用者不能随手传入其他数据。类型越贴近业务角色编译器越能挡住误用。switch很适合处理枚举枚举在实际代码中经常和switch搭配。比如把订单状态转成字符串#includestringenumclassOrderStatus{kPending,kPaid,kShipped,kCancelled,};std::stringToString(OrderStatus status){switch(status){caseOrderStatus::kPending:returnpending;caseOrderStatus::kPaid:returnpaid;caseOrderStatus::kShipped:returnshipped;caseOrderStatus::kCancelled:returncancelled;}returnunknown;}switch的每个case都对应一个枚举值读者能看出这个函数覆盖了哪些状态。以后如果新增一个状态比如kRefunded开发者可以回来检查switch是否也需要新增分支。这种结构比散落的整数判断更稳基础阶段可以在switch后补一个return unknown;确保函数总有返回路径。在更严格的项目里还会配合编译器警告来检查枚举分支是否覆盖完整。switch和枚举搭配还有一个好处它把针对同一组状态的处理逻辑集中起来。读者想知道订单状态怎么显示只需要看ToString想知道新增状态会影响哪里也能顺着switch找。相比在代码里到处写if (status 2)这种结构更容易维护。这也给后续修改留下了明确入口——状态集合变了先看enum class状态展示变了先看ToString状态逻辑变了先看对应的switch。好代码不是永远不用改而是改动入口清楚。枚举和switch的组合把固定状态的变化点集中管理起来。enum class的边界更清楚C 也有传统enumenumOrderStatusOld{kPending,kPaid,};传统enum的枚举值会进入所在作用域名字边界较弱。enum class的枚举值则必须通过类型名访问OrderStatus statusOrderStatus::kPaid;这更适合基础阶段建立清晰模型——类型名在左边合法值挂在类型名下面读者能看出每个值来自哪一组。enum class和整数之间的混用也更少。你不能随手把普通整数当成OrderStatus使用这让状态建模更稳。底层存储类型可以指定比如enum class OrderStatus : int这属于补充知识本系列先不展开。基础阶段优先用enum class的原因就是边界清楚。传统enum更容易把枚举值散布到外层作用域读者需要额外判断名字来源。enum class每次使用都带着类型名虽然多敲几个字符但换来的是更稳定的阅读模型。enum class也减少了不同枚举之间的混用。订单状态里的kPaid和支付状态里的kPaid在语义上不同。写成OrderStatus::kPaid或PaymentStatus::kPaid来源一眼可见。工程环境里多写类型名的成本很小避免误读的收益很大。枚举值的名字也要保持语义一致。OrderStatus下放kPending、kPaid、kShipped很自然因为它们都是订单状态。如果把kHighPriority也放进去语义就混淆了。枚举应该表达一组互斥的合法选项而不是作为常量的杂物箱。判断一个枚举是否合适的方法看这个变量在任意时刻是否只能处于这些状态之一。订单只能处于待支付、已支付、已发货、已取消等状态之一适合枚举分数是 0 到 100 的连续数值不适合用枚举列出每个分数。枚举适合固定集合范围数值仍用数值类型配合校验逻辑表达。命名空间让名字有归属枚举给值划边界命名空间给名字划边界。看一个成绩处理相关的工具函数namespacegrade{intClampScore(intscore){if(score0){return0;}if(score100){return100;}returnscore;}}// namespace gradeClampScore定义在grade命名空间里。调用时写intscoregrade::ClampScore(120);grade::ClampScore表示这是grade范围里的ClampScore。这和std::cout是相同的模式cout是std命名空间里的名字。命名空间类似目录前缀项目变大后不同模块经常会出现Print、Parse、Validate这样的通用名字。命名空间让这些同名函数归属于不同范围减少冲突让代码组织更清楚。命名空间就像代码里的名字目录。grade::ClampScore和network::ClampScore可以共存因为隶属于不同范围。读者看到前缀就能知道函数来自哪个模块。大型代码库中函数名本身可能比较短补充上下文的任务就由命名空间承担。命名空间不仅仅是防止重名它还在表达模块边界。grade::ClampScore说明函数属于成绩模块std::cout说明名字来自标准库。项目越大函数名越倾向于缩短因为上下文交给了命名空间。合理的命名空间前缀能让调用点自带来源信息。基础阶段不要急于使用过深的命名空间层级。grade::ClampScore已经能表达清楚归属写成project::training::score::grade::ClampScore对小程序反而复杂。命名空间的目标是帮读者定位名字来源。命名空间也无法替代好的函数命名。grade::Run()依然含糊因为Run没有传达具体动作grade::ClampScore()则清楚很多。前缀说明归属函数名说明动作两者配合调用点的信息才完整。基础代码里用一层清楚的命名空间加上具体的函数名就足够了。命名空间里的名字在逻辑上应该是一组相关能力。成绩模块里放等级枚举、分数校验、转换函数和输出函数都围绕同一个业务。如果把不相关的工具函数放进同一个命名空间归属关系就模糊了。命名空间应该帮助读者缩小搜索范围。::是作用域解析回顾前面用过的::符号std::cout OrderStatus::kPaid grade::ClampScore它们遵循的是同一套理解模型std::cout指std命名空间里的coutOrderStatus::kPaid指OrderStatus枚举里的枚举值grade::ClampScore指grade命名空间里的函数。建立这个统一理解后遇到project::module::Parser、std::vectorint这样的写法可以用同一套模型解读看到::先看左边的范围再读右边的名字。它们都在表达名字的归属关系。::的作用就是声明右边这个名字在左边这个范围里。范围可以是命名空间、枚举类型也可以是类。基础阶段先掌握命名空间和enum class这两种常见用法后面学类和静态成员时会自然接上。正因如此基础教程中不建议一开始就大量使用using namespace std;。它能省去写std::但会隐藏名字的来源。保留std::cout、std::string等完整写法是为了让读者意识到名字来自标准库。等熟悉命名空间概念后再根据项目规范判断如何局部使用using声明。局部的using声明有时是合理的。比如在一个短函数里写using std::cout;可以减少反复输出时的前缀。但基础教程保持完整前缀更有利于建立直觉——std::会强化这个名字来自标准库的认知。一个完整示例下面这段代码把枚举、switch、命名空间和::放在一起#includealgorithm#includeiostream#includestringnamespacegrade{enumclassGradeLevel{kExcellent,kGood,kPass,kFail,};intClampScore(intscore){returnstd::max(0,std::min(100,score));}GradeLevelGetLevel(intscore){scoreClampScore(score);if(score90){returnGradeLevel::kExcellent;}if(score80){returnGradeLevel::kGood;}if(score60){returnGradeLevel::kPass;}returnGradeLevel::kFail;}std::stringToString(GradeLevel level){switch(level){caseGradeLevel::kExcellent:returnexcellent;caseGradeLevel::kGood:returngood;caseGradeLevel::kPass:returnpass;caseGradeLevel::kFail:returnfail;}returnunknown;}}// namespace gradeintmain(){intscore86;grade::GradeLevel levelgrade::GetLevel(score);std::coutgrade::ToString(level)\n;return0;}在这段代码中GradeLevel枚举将成绩等级收拢为一组固定值grade命名空间将等级相关的函数组织在一起grade::GetLevel和grade::ToString的调用方式清楚地说明了来源归属。阅读这段代码时重点看三条边界线也可以从调用点出发反向拆解信息传递grade::前缀说明来源ToString说明动作level说明操作对象。命名空间和函数名配合调用点就能传达意图信息。今后在代码中遇到::养成先看左边确认范围、再看右边解读名字的习惯。这个模式不仅适用命名空间和枚举后续在类、静态成员、嵌套类型和标准库中同样有效。枚举和命名空间让小程序有了工程代码的基本结构。下一章会把前面的基础语法串联起来用变量保存数据用表达式计算用控制流判断用函数拆分逻辑用const引用传递参数用枚举表达状态用命名空间组织函数。这一章的核心概念是状态通过枚举表达模块名字通过命名空间组织。这种写法会增加一些类型名和前缀但能省去阅读者的猜测成本。在基础阶段养成习惯后面进入类体系、STL、工程项目时代码结构会更稳固。枚举和命名空间在本质上做的是同一件事给边界命名。枚举给哪些值合法命名命名空间给这些名字属于哪里命名。项目变大后难的不是写一条语句而是让读者判断出每个值、每个函数、每个类型归属哪里。边界清楚后续的维护成本会大幅降低。码字不易欢迎大家点赞关注评论谢谢