首页 / 资讯中心 / 文章详情

c++ For循环执行顺序流程图解

c++ For循环执行顺序流程图解 ★ FEATURED ARTICLE
前言for是 C 里最常用的控制流语句之一但它的执行顺序经常被记成「初始化、判断、自增、循环体」。正确的顺序是「初始化、判断、循环体、自增、再判断」。把自增放到循环体之前会把第一次迭代的语义整体错位尤其在用下标访问数组时直接造成越界或漏掉首元素。另一个常见误解是「for的初始化部分每轮都会执行」。实际上初始化语句只在进入循环时执行一次它声明的变量作用域覆盖条件、循环体和自增表达式循环结束后即销毁。理解这一点才能解释为什么循环变量在循环外不可见以及为什么在循环内重复定义同名变量会遮蔽它。本文用一张文本流程图把执行顺序固定下来再讨论作用域、continue的特殊跳转、等价while写法以及 C11 范围 for 的展开方式。所有结论以 C17 为准范围 for 本身自 C11 起可用其中用到的结构化绑定需要 C17。一、语法结构与执行顺序标准形式是// 适用于 C17for (init-statement; condition; iteration-expression) {statement}四个部分的职责部分何时执行执行次数初始化语句进入循环前恰好 1 次条件表达式每轮循环体之前至少 1 次最多 n1 次循环体条件为真时0 到 n 次自增表达式每轮循环体之后与循环体成功执行次数相同对应的流程图如下箭头方向就是控制流方向┌──────────────────────┐│ init-statement │ 只执行一次└──────────┬───────────┘│v┌──────────────────────┐│ 计算 condition │└──────────┬───────────┘│false │ true┌────────────────┴────────────────┐│ │v v┌─────────┐ ┌──────────────┐│ 退出循环 │ │ 执行循环体 │└─────────┘ └──────┬───────┘│v┌──────────────┐│ iteration- ││ expression │└──────┬───────┘│└──── 回到 condition以一个具体循环为例// 适用于 C17for (int i 0; i 3; i) {std::cout i \n;}执行序列是i 0一次判断0 3成立输出 0执行i得 1判断1 3成立输出 1i得 2判断成立输出 2i得 3判断3 3不成立退出。循环体执行 3 次条件求值 4 次。如果条件一开始就为假循环体一次也不执行但初始化语句仍然执行过。这一点在初始化带有副作用时很重要例如在初始化里调用了一个记录日志的函数。二、作用域、生命周期与同名遮蔽初始化语句中声明的变量其作用域从声明处开始一直延伸到循环语句结束包括条件、循环体和自增表达式。循环结束后该变量销毁循环外不可见。这就是下面这段代码无法通过编译的原因// 适用于 C17for (int i 0; i 3; i) {// ...}// std::cout i; // 错误i 不在作用域内在循环体内再声明一个同名变量是允许的它会在内层作用域遮蔽外层的循环变量// 适用于 C17for (int i 0; i 3; i) {int i 100; // 遮蔽外层的 istd::cout i; // 输出 100}这种写法几乎总是笔误编译器通常只给警告。实际编码中应避免同名遮蔽必要时打开-Wshadow一类的警告选项。初始化部分可以声明多个同类型变量用逗号分隔自增部分可以用逗号表达式串联多个动作// 适用于 C17for (int i 0, j 10; i j; i, --j) {std::cout i j \n;}注意条件里只能有一个表达式不能写int i 0, j 10之外的多条语句逗号在这里是运算符不是语句分隔符。三、与 while 的等价关系以及 continue 的特殊行为把for改写成while最直观的形式是// 适用于 C17{init-statement;while (condition) {statementiteration-expression;}}外层花括号用来复现初始化变量的作用域。但这个改写对continue不成立while里的continue会直接跳回条件判断跳过放在循环体末尾的自增表达式可能造成死循环而for里的continue会先执行自增表达式再判断条件。这是两种循环在语义上唯一的实质性差异。用for写「跳过偶数」// 适用于 C17for (int i 0; i 6; i) {if (i % 2 0) {continue; // 跳到 i不会死循环}std::cout i ;}若改写成while且在continue前忘了自增i永远停在 0程序陷入死循环。因此凡是用到continue的循环优先用for把自增放在头部由语法保证执行。break的行为在两种循环里一致立即退出整个循环自增表达式不再执行。嵌套循环中的break只退出最内层需要跳出多层时应重构函数或使用带标志的循环。四、范围 for 的展开C11 引入范围 for// 适用于 C17std::vectorint v{1, 2, 3};for (int x : v) {std::cout x \n;}标准给出的等价展开大致是// 适用于 C17示意{auto __range v;auto __begin begin(__range);auto __end end(__range);for (; __begin ! __end; __begin) {int x *__begin; // 按值复制// 循环体}}由此可以推出几个要点。第一范围 for 只要求类型能提供begin()/end()原生数组、std::vector、std::string、std::map都支持自己写的类只要提供这两个成员或自由函数也可以。第二int x是按值复制修改x不影响容器要修改元素必须写int x或auto x。第三只读遍历用const auto避免复制且表明意图。// 适用于 C17#include iostream#include string#include vectorint main() {std::vectorstd::string names{alpha, beta, gamma};for (const auto name : names) { // 只读不复制std::cout name.size() ;}std::cout \n;for (auto name : names) { // 引用可修改name !;}for (const auto name : names) {std::cout name ;}std::cout \n;}C17 的结构化绑定可以和范围 for 结合遍历std::map时尤其方便// 需要 C17#include map#include stringstd::mapstd::string, int scores{{a, 1}, {b, 2}};for (const auto [key, value] : scores) {// key 的类型是 const std::stringvalue 的类型是 const int}这里的[key, value]是结构化绑定不是数组或 lambda 捕获列表。它需要 C17 及以上C17 之前只能写for (const auto kv : scores)再用kv.first、kv.second。范围 for 里的continue与普通 for 一样跳到下一次迭代break退出循环。不要在范围 for 中增删容器元素迭代器可能失效行为是未定义的。五、常见写法的取舍for (;;)是标准认可的无限循环写法条件为空等价于恒真它与while (true)语义相同选哪个只看可读性。for (int i n; i-- 0; )把自减放进条件循环体执行 n 次且i在体内是n-1到 0这种写法依赖i--返回旧值可读性较差除非团队约定否则不推荐。关于i与i对int这样的内置类型两者在语义上都在表达式结束后让i加一编译器生成什么指令由优化决定不能凭口诀断言谁更快。对迭代器这类类类型后置it需要返回自增前的副本前置it返回自身引用前者可能多一次对象复制。因此在泛型代码里习惯写it是合理的但不必把它当成性能优化技巧到处宣讲。遍历容器时i v.size()中的i若是int会与无符号的size_t比较产生符号转换警告应统一用std::size_t或在 C20 中使用std::ssize(v)取得有符号长度。v.size()每次迭代都会求值但它通常是内联的常数时间操作不构成算法复杂度问题真正的 O(n²) 风险来自在循环条件里调用strlen这类线性函数。常见坑点❌ 认为for的执行顺序是「初始化、条件、自增、循环体」。✅ 自增在循环体之后、下一次条件之前continue也会先执行自增。❌ 在循环条件里写i v.size()配合int i。✅ 下标从 0 开始时应为i v.size()同时统一类型避免有符号/无符号混比。❌ 范围 for 中写for (auto x : v)却试图修改元素。✅auto按值复制要修改写auto只读写const auto。❌ 在范围 for 内部push_back或erase容器元素。✅ 增删元素会使迭代器失效行为未定义先收集下标循环结束后统一修改。❌ 用while改写带continue的for却把自增放在循环体末尾。✅continue会跳过末尾自增导致死循环改用for或用do { ... } while重构。❌ 在for头部初始化里声明多个不同类型的变量。✅ 初始化语句只能声明同一种类型需要不同类型时在循环外声明头部留空或只写赋值。❌ 循环体只有一条语句就省略花括号之后又在下面补一行。✅ 始终写花括号省略花括号时新增的语句会落到循环外逻辑悄然改变。❌ 认为i一定比i快。✅ 对内置类型无实质差别对迭代器前置形式可避免临时副本但应以代码清晰为先。总结阶段执行时机备注初始化进入循环时一次变量作用域覆盖整个循环条件求值每轮循环体前为假则退出可能一次都不进循环体循环体条件为真时break直接退出自增循环体之后continue也会执行它回到条件自增之后形成下一轮把for记成「初始化一次、判断、循环体、自增、再判断」这条链就能解释continue为什么不会死循环、循环变量为什么在外部不可见、范围 for 为什么按值复制。写循环时优先用范围 for 表达「遍历」需要下标或精细控制时再用经典 for并让循环变量的类型与容器长度类型保持一致。
阅读完成 · 觉得有帮助?
咨询建站