资讯动态

C语言循环嵌套详解:九九乘法表与break/continue用法

发布时间:2026/10/13 20:20:01 来源:尧图企业网站定制
这一节我们接着循环结构往下走。前两节把单层循环的 for、while、do-while 都过了一遍不少同学心里其实还憋着一个问题循环能不能一层套一层答案是当然能而且嵌套循环才是循环真正发力的地方。这一节的内容很简单也很具体循环嵌套、九九乘法表、break、continue、while(1)五个关键词串起来。文章里我会把九九乘法表的代码逐行拆开讲也会专门说清楚 break 和 continue 在使用时最容易踩的坑适合刚学完单层循环、正在为“什么时候该用嵌套”“跳出去到底跳出哪层”发愁的同学参考。我按以往课堂上的经验说一句实话这一节的内容语法上没有任何新东西难的是“怎么把一层循环的逻辑扩展到两层”。很多同学卡住不是不会写 for而是不知道内外两层循环各自该管什么。下面我们从最直的一个问题开始。1. 循环嵌套的分工逻辑外层管“行”内层管“列”1.1 从打印星星开始为什么一个 for 解决不了先看一个很经典的课堂练习用星号打印一个直角三角形第一行 1 个星第二行 2 个星第三行 3 个星一共五层。如果你试着用单层循环去写写到最后会发现一个尴尬的问题每行打印的星的数量是变化的不是固定的 5 个或 10 个。你要先打印 1 个换行再打印 2 个再换行……这个“每行打印几个”本身就需要一个变量来控制。而控制变量正是循环最擅长的。于是自然想到用外层循环控制一共有多少行用内层循环控制当前这一行打印多少个星。也就是说外层循环走一次 处理一行内层循环走一次 在这一行里打印一个星代码长这样#include stdio.h int main() { for (int i 1; i 5; i) { // 外层控制行数 for (int j 1; j i; j) { // 内层控制每行的星数 printf(* ); } printf(\n); // 每行结束要换行 } return 0; }运行结果* * * * * * * * * * * * * * *重点看内层循环的条件j i。当 i 等于 1 时内层只执行 1 次当 i 等于 3 时内层执行 3 次。这就是“外层变量影响内层执行次数”的典型写法也是嵌套循环最核心的机制内层循环的循环条件可以直接引用外层循环的变量。很多同学第一次看到这段代码会想i 和 j 都是从 1 开始凭什么内层打印的数量跟着外层走原因就在j i这个条件上。i 每变化一次内层循环的循环次数就重新计算一遍。1.2 内外层变量写反了通常会发生什么嵌套循环写起来容易翻车也容易。我见过最多的错误就是把行数和列数搞反。比如有人会这么写for (int i 1; i 5; i) { for (int j 1; j 5; j) { printf(* ); } printf(\n); }这样打印出来的是 5 行 5 列的矩形因为内层的执行次数和外层变量 i 毫无关系永远都是 5 次。如果你的目标是矩形这么写没错但目标如果是三角形这个形状就不对了。另一个常犯的错是把换行printf(\n)放到了内层循环里面。一旦换行执行了 5 次输出就变成每个星后面跟一个换行三角形瞬间变成一列竖线。记住换行是“一行结束”的动作属于外层循环管的范围不在内层循环里面。补充一个判断技巧看见嵌套循环先回答两个问题——外层循环每转一圈我要完成什么内层循环每转一圈我要完成什么答案分别是“完成一整行”和“完成一行里的一个元素”那么这个分工就不会乱。2. 九九乘法表的三步拆解先画表格再写循环骨架最后调对齐2.1 先把九九乘法表“画”出来九九乘法表本质上是 9 行 9 列表格的下三角部分。所谓下三角就是行数 i 和列数 j 满足j i的那些格子。如果你现在拿张纸把完整乘法表手写一遍就会发现每一行的公式数量是递增的第一行 1 个公式第二行 2 个公式……第九行 9 个公式。这和上面打印三角形的结构完全一致区别只在于把“星号”换成了“乘法算式”。而且公式的顺序也有讲究。常规教科书版本是先写外层行号再写内层列号比如第一行是1*11第二行是2*12 2*24。注意第二行不是从 1 列开始判断而是每一列 j 从 1 一直取到当前的 i。这正好可以用j i来表达。2.2 用嵌套循环还原表格根据上面的分析循环骨架应该是外层循环 i 从 1 到 9负责每一行 内层循环 j 从 1 到 i负责这一行里的每个公式 一行结束后换行。代码写出来就是#include stdio.h int main() { for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%d , i, j, i * j); } printf(\n); } return 0; }运行之后你会发现输出确实有 9 行乘法公式也一应俱全但排版很乱。111 和 199 占的宽度不一样后面的公式挤在一起根本看不出表格的样子。这一步几乎是所有人都会经历的算法对了格式不对结果还是没法用。2.3 格式对齐与扩展%2d、间距、变体问题出在printf(%d*%d%d , ...)里的%d没有指定宽度。数字 1 和 数字 12 在屏幕上的实际宽度不同后面的空格却只有一个。解决办法是给乘积部分指定最小宽度也就是改成%2d或%-2d。%2d表示右对齐占 2 格%-2d表示左对齐占 2 格。教科书里常用左对齐因为看起来更规整像下面这样#include stdio.h int main() { for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%-2d , i, j, i * j); } printf(\n); } return 0; }输出效果1*11 2*12 2*24 3*13 3*26 3*39 4*14 4*28 4*312 4*416 ...我再顺手给你两个变体理解了它们嵌套循环基本就通了把内层条件改成j 9并且在 j i 时输出空格可以打印右上三角把外层循环从 9 倒着走到 1内层条件不变可以打印倒三角。比如倒三角版本for (int i 9; i 1; i--) { for (int j 1; j i; j) { printf(%d*%d%-2d , i, j, i * j); } printf(\n); }你会发现代码几乎没变只是外层循环的初值和条件换了个方向输出的顺序就完全不同了。这正是嵌套循环的魅力之一结构不变调整边界条件输出形态就跟着变。还有一个小细节值得提醒九九乘法表里教科书一般写作i*j而不是j*i也就是乘号前的数字是当前行号。这本来无所谓但如果你按计算机阅卷的要求交作业最好和题目的输出样例保持一致免得因为顺序问题扣分。3. break 的使用边界跳出哪一层、在哪里生效、哪些地方别乱用3.1 实测 break 到底跳出哪一层break 的中文意思是“打破”放在循环里就是“中断当前这一层循环”。很多初学者把它理解成“结束整个程序”或者“跳出所有循环”这两个理解都不对。直接看例子#include stdio.h int main() { for (int i 1; i 3; i) { for (int j 1; j 3; j) { if (j 2) { break; } printf(i%d, j%d\n, i, j); } } return 0; }这段代码中当 j 等于 2 时执行 break。输出是什么样是直接跳出全部两层还是只跳出内层运行一下结果是这样的i1, j1 i2, j1 i3, j1注意i 仍然从 1 循环到了 3说明 break 只中断了内层 j 的循环外层 i 的循环还在继续。这就是 break 最重要的行为特征break 只作用于它所在的那一层循环不能远程终止外层循环。同理switch 里的 break 也是这个逻辑它只跳出 switch 结构本身不会跳出外面的循环。有一个很经典的组合坑是这样的for (int i 0; i 5; i) { switch (i) { case 1: printf(%d , i); break; // 这个 break 只跳出了 switch default: printf(* ); } }不少人以为 case 1 打印完之后就 break 出循环了程序应该只打印一个 1。实际结果却是* 1 * * *因为 switch 里的 break 只管不让后续 case 继续执行根本不关心外面的 for 循环。3.2 想跳两层怎么办标志变量是最容易理解的过渡方案既然 break 只能跳出一层那遇到“我想从最内层直接全部退出”的需求怎么办正经的做法是使用标志变量。思路很简单定义一个 int 变量 flag初始为 0。在需要退出两层循环的时候把 flag 设为 1然后内层立即 break内层 break 之后再判断 flag如果为 1外层也 break。#include stdio.h int main() { int flag 0; for (int i 1; i 5 !flag; i) { for (int j 1; j 5; j) { if (i * j 10) { flag 1; break; } printf(%d*%d%d\n, i, j, i * j); } } return 0; }用标志变量的好处是思路直观即使你第一次写嵌套循环也能靠它控制复杂退出逻辑。从我个人讲公开课的经验看比起上来就教 goto 或者 return 的写法标志变量对初学者反而更友好。先理解“break 只跳一层”这个事实再用额外的变量补上“多跳几层”的缺口后面学函数、学其他语言时才不会混乱。4. continue 的“跳过本轮”逻辑它和 break 不是反义词4.1 用打印奇数的小例子直观感受 continue 的跳转方向break 是彻底结束整个循环continue 则是“跳过当前这一次循环的剩余部分直接进入下一次迭代”。它并不退出循环只是把这一轮还没执行完的代码略过。最简单的例子是打印 1 到 10 之间的奇数#include stdio.h int main() { for (int i 1; i 10; i) { if (i % 2 0) { continue; // 偶数时跳过后面的打印直接进入下一轮 } printf(%d , i); } return 0; }结果会输出1 3 5 7 9。仔细对照执行流程i 等于 2 时i % 2 0成立continue 把后面的 printf 跳过了然后循环自动执行 i 变成 3接着打印 3。continue 在这里承担的角色比较像“过滤器”——把不符合条件的分支直接放过去不再往下执行。我还见过一个很形象的比喻break 是跑步中途退场回家continue 是跑步时遇到障碍绕过去继续跑比赛本身没有结束。这个比喻虽然简单但记住“继续跑”这个意思就不会把 continue 误解成结束循环。4.2 while 循环里 continue 最容易翻车别把循环变量跳过去同样是过滤continue 放在 for 循环里比较安全因为 for 会在每次循环结束时自动执行自增操作但放在 while 循环里就有一个非常隐蔽且高频的 bug。看这段代码#include stdio.h int main() { int i 1; while (i 10) { if (i % 2 0) { continue; // 问题就出现在这里 } printf(%d , i); i; } return 0; }理论上你想打印奇数实际上程序会陷入死循环。原因在于当 i 等于 2 时满足i % 2 0continue 立刻跳到 while 的条件判断但问题来了i 并没有自增它还是 2。于是下一轮循环依然满足条件依然 continue永远到不了i也无法退出循环。程序会一直卡在这一行 continue 上。解决方式是把i挪到循环体最前面或者干脆不用 while改用 for。这也是我说“for 循环和 continue 搭配更顺手”的原因因为 for 的自增写在语法结构里想跳都跳不过去。给初学者的建议是以后写 continue 之前先确认你用的循环类型会不会自动更新循环变量。while 循环里用了 continue十有八九会忘记这个细节。排查死循环的时候优先看 continue 前面的代码有没有把循环条件相关的变量改掉。5. while(1) 的正确打开方式菜单循环、持续输入、以及怎么安全退出5.1 一个几乎天天能见到的 while(1) 框架while(1) 的含义就是“条件永远为真循环永远不结束”所以它的名字通常叫无限循环。很多人一听到“无限”两个字就害怕觉得这是程序 bug。实际上工程里 while(1) 非常常见它的标配是循环体内部用 break或者 return在满足特定条件时主动退出。最典型的场景是菜单程序程序一启动就显示菜单用户选 1 执行一个操作选 2 执行另一个操作选 0 退出。这个“退出”之前程序要一直等待用户输入换成 while(1) 再合适不过。#include stdio.h int main() { int choice; while (1) { printf( 菜单 \n); printf(1. 重新开始\n); printf(2. 查看帮助\n); printf(0. 退出\n); printf(请输入你的选择); scanf(%d, choice); if (choice 1) { printf(你选择了重新开始\n); } else if (choice 2) { printf(你选择了查看帮助\n); } else if (choice 0) { printf(程序结束再见\n); break; // 唯一的退出窗口 } else { printf(输入无效请重新输入\n); } } return 0; }这段代码的逻辑是只要 choice 不是 0循环就会一遍又一遍弹出菜单。真正让循环结束的只有那一行 break。这就界定了 while(1) 的安全使用规范——写 while(1) 的时候循环体里必须存在一个能够到达的 break 或 return否则就是真死循环。5.2 读入失败的坑scanf 失败后程序卡死在 while(1) 里while(1) 配合 scanf 使用时会遇到一个很有意思的经典问题如果你在菜单里输入了一个字母而不是数字程序会瞬间进入死循环界面疯狂刷屏或者一直停住不响应。原因是 scanf(%d, choice) 在读入字母时失败了但字母仍然残留在输入缓冲区里。下一次循环继续调 scanf看到缓冲区里还是那个字母再次失败如此反复while(1) 永远等不到一个合法的 choice。我上课演示这个坑时全班都能看见同样的现象输入a回车屏幕上像抽风一样循环打印菜单。解决思路是读取失败时主动把缓冲区里的残留内容清空。可以用 getchar() 配合循环清空也可以用其他方式强制清空输入流。一个简单可用的修正版如下#include stdio.h int main() { int choice; int result; while (1) { printf(请输入选择); result scanf(%d, choice); if (result ! 1) { // 读入失败 while (getchar() ! \n); // 清空缓冲区 printf(输入不合法请重新输入\n); continue; // 跳过本次循环重新显示菜单 } if (choice 0) { break; } printf(你输入的是%d\n, choice); } return 0; }这段代码把“读入是否成功”作为判断依据。scanf的返回值等于 1说明成功读入了一个整数不等于 1说明输入不是数字此时清空缓冲区再 continue让用户重新输入。这个模式在写菜单程序、交互式程序时非常实用。5.3 while(1) 不适合用在哪里最后说一句边界问题。while(1) 不是哪里都该用。如果你的循环次数在进入循环前就已经知道了比如遍历数组、遍历 1 到 100那么老老实实用 for可读性更高也不容易出事故。while(1) 适合的场景是“循环什么时候结束取决于运行过程中动态出现的条件”比如用户输入、网络消息、传感器数据等。判断标准很简单你写循环之前能不能确定循环要执行几次能确定用 for不能确定才考虑 while(1)。别为了炫技把普通循环改成无限循环再 break那样只会让代码更难读。从这一节的完整逻辑串下来你会发现 break、continue、while(1) 三者其实是配合使用的while(1) 负责搭建“一直运行”的框架break 负责提供“出口”continue 负责“跳过不合理输入后继续兜底”。搞懂这三者的边界再去写循环嵌套就会顺手很多。最后分享一个我经常跟学员说的经验学循环嵌套不要光看代码一定要自己在纸上把外层变量 i 等于 1、2、3 时内层 j 各跑了几次一行一行推演出来。九九乘法表是最适合做这个练习的素材因为它每一行的列数和行号有明确的对应关系你推一遍表格比看十遍视频都管用。推完之后再随手改改内层条件、换换对齐格式这一节课的内容就实打实吃透了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑