C++基础入门教程(四):枚举和指针

发布时间:2026/10/20 20:12:42

C++基础入门教程(四):枚举和指针 前言枚举和指针是 C 入门阶段两块看似无关、实则都很容易会用但用不对的内容。枚举的坑集中在类型安全传统枚举的枚举值会泄漏到外层作用域还能悄悄转换成整数两个不同枚举之间也能互相比较编译器不会提醒你。指针的坑集中在生命周期和边界未初始化、悬垂、越界三者都是未定义行为undefined behavior, UB程序可能崩溃也可能看起来一切正常。本文按先建立模型、再写代码的顺序讲。枚举部分从传统枚举讲到 C11 的enum class说明底层类型和作用域规则指针部分讲地址、类型、解引用、const修饰位置、指针运算的合法边界最后给一个把两者结合起来的完整示例。示例以 C17 为准涉及 C11 特性的地方会注明。阅读时请记住一条底线本文的代码没有被编译或运行过所有结论都来自语言标准的语义推导。真实行为请以你自己的编译器和标准文档为准。一、传统枚举与枚举类传统枚举unscoped enumeration用enum定义// 适用于 C17enum LegacyColor { kRed, kGreen, kBlue };它有三个特点。第一枚举值kRed、kGreen、kBlue直接进入外层作用域相当于在这里声明了三个常量所以同一作用域内不能再定义同名标识符。第二枚举值可以隐式转换为整数int x kGreen;合法反过来整数转枚举则需要显式转换。第三不同枚举类型的值可以互相比较因为比较前都先转成了整数这让把颜色和状态拿来比较这种错误无法被类型系统拦住。C11 引入的枚举类scoped enumeration解决了这些问题// 适用于 C17enum class Color : std::uint8_t { Red, Green, Blue };枚举值必须写成Color::Red不会污染外层作用域不会隐式转换为整数不同枚举类之间不能直接比较。冒号后的std::uint8_t指定底层类型这在需要固定内存布局、与协议字段对接时很有用。不指定时枚举类的底层类型默认是int传统枚举的底层类型由实现决定只要足以容纳所有枚举值即可。特性传统enumenum class枚举值作用域泄漏到外层限定在枚举名内隐式转整数允许禁止不同枚举间比较允许都转整数禁止指定底层类型C11 起可指定可指定默认int前置声明需指定底层类型可直接前置声明二、枚举的取值、底层类型与位掩码枚举值默认从 0 开始依次加一也可以显式赋值。允许重复值也允许跳过某些值但要注意枚举的合法值范围不是最小到最大这么简单标准规定的是一个可以用若干位表示的区间把任意整数强制转换成枚举类型时落在区间外的值属于未定义行为。因此不要用static_castColor(99)去试探。枚举类做位掩码时要自己提供运算符因为标准不提供按位运算// 适用于 C17enum class Permission : unsigned {None 0u,Read 1u 0,Write 1u 1,Execute 1u 2};constexpr Permission operator|(Permission a, Permission b) {return static_castPermission(static_castunsigned(a) | static_castunsigned(b));}constexpr bool has(Permission set, Permission bit) {return (static_castunsigned(set) static_castunsigned(bit)) ! 0u;}constexpr让这些函数在编译期也能求值。注意operator|返回的是Permission这样has()的第二个参数类型才不会错。位掩码枚举通常把None设为 0并保证各标志位互不重叠。switch配合枚举时建议把每个枚举值都列出来。部分编译器能对遗漏的枚举值给出警告但这依赖编译选项不是标准要求。不要用default草草了事否则将来新增枚举值时编译器帮不上忙。三、指针保存地址的变量指针是一个保存地址的变量它的类型决定了解引用时按什么类型解释这块内存。声明int* p表示p里存的是一个int对象的地址。取地址用解引用用*// 适用于 C17int value 42;int* p value; // p 指向 value*p 43; // 通过 p 修改 valueint copy *p; // 读取 valuevalue的类型是int**p的类型是int左值。指针本身也是一个对象它有自己的地址所以可以有指向指针的指针int**但这在入门阶段很少需要。空指针用nullptr表示C11。NULL在多数实现里是整数常量0在重载解析时可能被当成int从而选错函数nullptr的类型是std::nullptr_t能隐式转换为任意指针类型但不会转成整数。新代码一律用nullptr。未初始化的指针里是 indeterminate 值读取它本身就是未定义行为更不用说解引用。养成声明即初始化的习惯暂时没有目标就写int* p nullptr;。需要区分指针指向的对象是否可改和指针本身是否可改这由const的位置决定写法指向的对象指针本身记忆方法const int* p不可改可改const 在*左边约束所指对象int* const p可改不可改const 在*右边约束指针const int* const p不可改不可改两边都有int const* p不可改可改与第一种等价从右往左读声明通常不会错int* const p读作p 是一个 const 指针指向 int。四、指针运算与数组边界数组名在多数表达式里会退化为指向首元素的指针所以可以用指针遍历数组// 适用于 C17int arr[5] {10, 20, 30, 40, 50};int* begin arr;int* end arr 5; // 指向末尾后一位合法但不可解引用for (int* it begin; it ! end; it) {std::cout *it \n;}指针算术只在同一数组或同一个完整对象内部有定义。arr 5是合法的尾后指针one-past-the-end可以用作循环终止条件但不能解引用。arr 6或arr - 1的运算本身就是未定义行为哪怕不 dereference。两个指针相减只有在指向同一数组时才有定义结果的类型是std::ptrdiff_t。sizeof(arr)得到整个数组的字节数sizeof(begin)得到指针本身的字节数后者由实现决定64 位平台通常是 8。把数组传给函数后形参类型退化为指针函数内部再也拿不到数组长度这是初学者最常见的困惑之一。解决办法是额外传长度或用std::array、std::vector、std::spanC20。完整示例状态机与权限表下面把枚举与指针放在一个示例里用枚举表示状态用指向函数的指针数组做状态处理表。示例只演示语法组织不含平台相关代码。// 适用于 C17#include cstddef#include iostreamenum class State { Idle, Running, Paused, Stopped };const char* to_string(State s) {switch (s) {case State::Idle: return Idle;case State::Running: return Running;case State::Paused: return Paused;case State::Stopped: return Stopped;}return Unknown;}// 函数指针类型接收 State返回 voidusing Handler void (*)(State);void on_idle(State) { std::cout waiting\n; }void on_running(State) { std::cout working\n; }void on_paused(State) { std::cout suspended\n; }void on_stopped(State) { std::cout done\n; }int main() {// 下标与 State 的整数值一一对应Handler table[] {on_idle, on_running, on_paused, on_stopped};State current State::Running;const std::size_t index static_caststd::size_t(current);if (index sizeof(table) / sizeof(table[0])) {table[index](current); // 通过函数指针调用}std::cout to_string(current) \n;int* nothing nullptr;if (nothing ! nullptr) {std::cout *nothing; // 只有非空才解引用}return 0;}这里用static_caststd::size_t把枚举转成下标并且在使用前检查了边界。函数指针数组的类型必须完全一致on_idle的签名是void(State)放进Handler数组才不会出错。sizeof(table) / sizeof(table[0])是 C 风格数组求长度的经典写法对指针不适用。常见坑点❌ 把enum class的值直接赋给int。✅ 必须static_castint(value)这正是枚举类防止误用的设计目的。❌ 用NULL初始化或比较指针。✅ 用nullptrNULL可能是整数0会在重载解析中产生意外。❌ 声明int* p;后直接*p 1;。✅ 未初始化指针的值不确定解引用是 UB声明时就初始化为nullptr或有效地址。❌ 用const int* p试图说明指针不能改。✅ 那是int* const pconst 在*左侧约束所指对象右侧约束指针本身。❌ 返回函数内局部变量的地址。✅ 函数返回后局部对象生命周期结束指针悬垂返回值、返回静态对象或由调用方提供缓冲区。❌ 认为arr n只要不解引用就没事。✅ 指针算术本身必须落在同一数组的[begin, end]闭区间内越界运算即 UB。❌ 对new[]分配的内存用delete释放。✅ 必须delete[]两者不匹配是 UB且通常伴随堆损坏。❌ 在switch里用default吞掉所有未列出的枚举值。✅ 逐个列出枚举值让编译器在新增枚举值时给出警告。总结主题要点传统枚举枚举值泄漏作用域可隐式转整数枚举类强作用域、强类型可指定底层类型枚举转换用static_cast不要越界构造非法值空指针一律nullptr解引用前判空const 位置左约束值右约束指针指针运算仅限同一数组尾后指针可比较不可解引用生命周期悬垂指针解引用是 UB返回局部地址必错枚举负责把一组有限的取值表达成类型指针负责间接访问对象两者本身都不复杂。真正需要训练的是边界意识枚举值是否合法、指针是否有效、下标是否越界。把这三问养成习惯入门阶段的大部分崩溃都可以提前避免。
延伸阅读

更多相关文章

2026/10/20 20:07:41

5G NR下行链路数据封装:HTTP到空口的协议栈解析

简介:关于5G NR下行数据传输流程的图形化资料,以图文配合方式拆解从应用到物理层的完整封装过程,适合5G NR入门学习者、通信工程学生以及网优/协议栈从业人员作为复习材料。内容从CU-DU高层拆分架构讲起,说明SDAP/PDCP与RLC/MAC/物…

2026/10/20 20:07:41

大厂Java面试实录:Spring Boot微服务与Kafka核心原理实战

互联网大厂Java求职者面试实录:Spring Boot、微服务与Kafka核心技术解析这几个月我陆续帮几位朋友做了模拟面试,发现一个挺有意思的现象:不管他们投的是哪家平台型公司、业务型团队还是中间件部门,面试官绕来绕去,最后…

2026/10/20 20:07:41

Windows内核无模块驱动开发:ObCallback与共享内存实践

简介:本资源是一套面向Windows内核安全与高级注入技术学习者的驱动级无模块注入实战工程,适用于具备C/C基础、熟悉Win32编程并希望深入系统底层安全机制的中高级开发者及安全研究人员。项目聚焦于绕过传统DLL注入检测的内核驱动方案,通过纯内…

2026/10/20 21:08:09

开源视频生成模型H3-Base优化实战:VDN-H3、FastH3与QuantFunc对比

H3-Base 开源那天,我工作室的群一整天都没消停。大家兴奋的点倒不只是"又多了一个开源视频生成模型",而是这个基础模型在前后文一致性和镜头运动连续性上,确实做出了不少闭源方案都未必搞定的效果。紧接着社区传统节目就上演了&…

2026/10/20 21:08:09

队列与约瑟夫环:顺序队列、链式队列及环形逻辑的C++实现

1. 队列与约瑟夫环:从线性结构到环形逻辑的完整实现队列这个结构,很多人第一次学的时候觉得“这不就是排队吗”,然后写了个数组加两个下标就交差了。但真到用的时候,比如做任务调度、消息缓冲、BFS 遍历,才发现边界处理…

2026/10/20 21:08:09

iOS图片选取避坑指南:UIImagePickerController实战与迁移

1. 为什么UIImagePickerController至今仍是绕不开的坎做iOS开发的人,几乎都经历过这样一个阶段:产品经理跑过来跟你说“加个选照片的功能,要能拍照、能选相册、能裁剪”,你心想这不就是调个系统API的事,半小时搞定。结…

2026/10/20 21:08:09

纯前端实现视频首帧截取:canvas与video配合的完整方案与避坑指南

1. 从一个真实需求说起:为什么视频封面不能靠手动截图做过内容管理后台或者视频上传功能的人,大概率都遇到过这个场景:用户上传一个视频文件,列表页需要展示一张封面图。最原始的做法是让用户自己截好图再上传,但实际用…

2026/10/20 21:08:09

OpenJDK 11.0.10 ARM64 Linux 部署指南:架构匹配、环境配置与生产验证

简介:本资源是面向ARM64架构Linux服务器与嵌入式开发者的OpenJDK 11.0.10官方构建版,专为aarch64平台深度适配,解决Java开发者在国产化ARM服务器、云原生边缘计算及信创环境下的JDK运行与开发需求。压缩包共508个文件,涵盖72个jmo…

2026/10/20 21:03:06

技能图谱构建:从数据提取到可视化实践

我无法根据当前输入内容生成符合要求的博文。原因如下:输入中仅提供了项目标题"skills",未提供任何实质性的【项目正文】、【关键词】或【摘要描述】;所谓“相关热搜词”和“最新网络热词”部分为空,未给出具体词汇&…

2026/10/19 18:06:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/20 1:05:37

单片机C运行时从启动到多任务:中断安全与共享资源冲突

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:05:37

GCC编译四步详解:预处理、编译、汇编、链接实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:05:37

FIW线是什么?FEP注塑线与普通氟塑料线的区别及应用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑