资讯中心

C语言循环结构实战:从for/while到图形打印与数值计算

📅 2026/8/28 4:12:04
C语言循环结构实战:从for/while到图形打印与数值计算
1. 从“会看”到“会写”第六章练习题的核心价值学C语言很多人卡在了一个尴尬的阶段书上的例子都能看懂老师讲的语法也听明白了但一关上书面对空白的编辑器脑子也跟着一片空白。我自己刚开始学的时候也是这样尤其是学到《明解C语言》第六章“循环结构”这里感觉像是撞上了一堵墙。for、while、do-while这几个词都认识但怎么把它们组合起来去解决一个具体问题比如画个图形、算个数列就完全没思路了。这其实就是典型的“输入”和“输出”不匹配——我们看了很多代码输入但自己动手写的练习输出太少了。《明解C语言》这本书的好处就在于它的练习题不是简单的语法填空而是精心设计的小项目逼迫你去运用刚学的知识。第六章的练习题核心就是将“循环”这个抽象概念转化为解决具体问题的“肌肉记忆”。它不再是问你“for循环的语法是什么”而是让你“用循环打印一个金字塔”或者“找出所有水仙花数”。这个过程就是从“理解语法”到“掌握编程思维”的关键一跃。很多人会跳过练习题觉得把例题搞懂就行了这是最大的误区。例题是老师手把手带着你走一遍而练习题是让你自己独立走一遍。只有自己走你才会遇到那些坑循环条件设错了导致死循环、计数器初始化位置不对、边界条件没处理好多打了一行或少打了一个数……这些细节看书是看不出来的必须亲手写错、调试、再改对才能真正内化。所以今天这篇内容我们就来彻底啃下《明解C语言第3版入门篇》第六章的练习题。我不会仅仅给出答案那样意义不大。我会带你拆解每一道题背后的“解题逻辑”告诉你为什么这道题要考察这个点常见的错误写法有哪些以及如何从问题描述一步步推导出代码结构。我们的目标不是“得到答案”而是“获得自己推导出答案的能力”。2. 循环基石再巩固for、while与do-while的选用心法在动手解题之前我们必须把工具打磨锋利。第六章介绍了三种循环结构for、while和do-while。很多初学者只是死记硬背语法但一到实际场景就懵不知道用哪个好。这里我结合多年编程和教学的经验给你一个清晰的选用指南这比任何语法书上的定义都实用。for循环当你“事先知道要跑多少圈”时它是首选。这里的“知道”不一定是精确的数字而是“可以明确计算出来”的迭代次数。比如遍历数组for(i0; i10; i)你知道要遍历10个元素。打印图形打印一个5行5列的星号矩阵你知道外层循环5次行内层循环5次列。执行固定次数的操作让用户最多输入3次密码for(int try0; try3; try)。它的结构for(初始化; 循环条件; 更新)把所有循环控制要素都放在了一行非常紧凑意图清晰。在第六章的图形题里for循环是绝对的主力因为图形的行数、每行的字符数都是可以预先确定的。while循环当循环条件是一个“状态”而这个状态在循环开始前就需要判断且迭代次数不确定时使用。它更像一个“当…时就一直做…”的守卫。读取用户输入直到特定值while(scanf(“%d”, num), num ! -1)只要输入的不是-1就一直读。处理未知长度的数据while((ch getchar()) ! EOF)读到文件结尾为止。等待某个条件成立while(设备未就绪) { 等待; }。do-while循环当你“无论如何都需要先执行一次循环体”然后再判断是否继续时使用。这是它和while最本质的区别。菜单驱动程序显示一个菜单用户选择执行操作然后问“是否继续(Y/N)”。你总得先显示一次菜单吧这就是do-while的典型场景。输入验证至少验证一次要求用户输入一个有效范围内的数字如果无效就提示重新输入。你总得先让用户输入一次才能判断吧一个极易踩坑的点循环控制变量的作用域。在C99标准之前for循环内定义的变量如for(int i0; ...)在循环体外是不可见的。虽然现在大多数编译器如gcc在C99模式下都支持但如果你在一些老旧环境或严格遵循C89的项目中这样写会报错。更稳妥、兼容性更好的做法是在循环外部定义int i; for(i0; ...)。在练习题中为了清晰我们可以使用C99方式但心里要明白这个区别。如何选择一个简单的决策流循环次数是否明确或可计算 -是用for。循环体是否至少必须执行一次 -是用do-while。以上都不是 -用while。掌握了这个心法再看题目要求你就能瞬间抓住该用哪种循环结构这是写出清晰代码的第一步。3. 图形打印题精解将数学规律转化为嵌套循环第六章的图形题如练习6-1, 6-2, 6-3等是循环部分最经典的练习也是初学者感觉最难的部分。难的不是代码而是如何将视觉上的图形规律用数学关系描述出来再翻译成循环的层次和边界条件。我们以最常见的“打印一个左下角直角三角形星号阵”为例拆解这个思考过程。题目示例思路推导打印一个n行每行星号数递增的直角三角形。第一步观察与建模用纸笔画出来假设 n5图形应该是* ** *** **** *****我们需要找到两个变量之间的关系行号i(从1到5) 和该行应打印的星号数。第二步寻找数学关系一眼就能看出第1行1个星第2行2个星……第i行i个星。所以关系是星号数 当前行号i。第三步转化为循环结构外层循环控制行数这很明确循环n次。for(i1; in; i)。这里i从1开始是为了让关系星号数i更直观。从0开始也可以但关系会变成星号数i1稍微绕一点。内层循环控制每行的星号数根据第二步的关系第i行需要打印i个星号。所以内层循环for(j1; ji; j)。注意内层循环的终止条件ji是依赖于外层循环变量i的这正是嵌套循环的精髓——内外联动。第四步代码实现与关键细节#include stdio.h int main() { int n, i, j; printf(“请输入行数 “); scanf(“%d”, n); for(i 1; i n; i) { // 控制行 for(j 1; j i; j) { // 控制第i行的星号数 putchar(‘*’); // 打印一个星号 } putchar(‘\n’); // 每行结束后换行这个千万别丢 } return 0; }避坑指南换行符的位置内层循环负责打印一行内的所有星号换行符必须放在内层循环结束之后、外层循环体内。如果放错了位置图形会全乱。循环变量的初始值这里i和j从1开始让逻辑更符合人类直觉。你也可以从0开始但循环条件和打印数量的计算就要调整为for(i0; in; i)和for(j0; ji; j)。关键是保持逻辑自洽。使用putchar还是printf打印单个字符时putchar(‘*’)比printf(“*”)效率稍高因为printf需要解析格式字符串。在简单练习中区别不大但养成好习惯。举一反三打印一个右下角直角三角形星号阵图形如下n5* ** *** **** *****思考逻辑关系分析第i行需要先打印一些空格再打印一些星号。空格数规律第1行需要4个空格第2行需要3个……第i行需要(n - i)个空格。星号数规律和左下角三角形一样第i行需要i个星号。循环结构外层循环i控制行。内层需要两个并列循环第一个内循环打印空格for(j1; jn-i; j)。第二个内循环打印星号for(j1; ji; j)。关键两个内循环是顺序执行先打完所有空格再打所有星号然后换行。通过这样的拆解任何规则图形金字塔、菱形、数字矩阵都可以用同样的“观察-找关系-定循环”三板斧来解决。这就是将抽象思维具体化的过程。4. 数值计算题实战循环与条件判断的协同除了图形第六章另一类重点题是利用循环进行数值计算例如判断素数、求最大公约数、计算数列和等。这类题目的核心是在循环流程中嵌入条件判断从而筛选或累积我们需要的数据。我们挑两个最经典的判断素数和求水仙花数来看看如何把数学逻辑严谨地转化为代码。4.1 判断一个数是否为素数解题逻辑拆解素数的定义是在大于1的自然数中除了1和它自身外不能被其他自然数整除的数。边界处理小于等于1的数不是素数。这是首先要判断的。寻找整除因子如何判断数n能否被某个数整除用求余运算符%。如果n % i 0说明i是n的因子。循环范围优化最笨的方法是从2试到n-1。但数学上有一个重要优化只需要试到sqrt(n)n的平方根即可。因为如果n有一个大于sqrt(n)的因子a那么它必然对应一个小于sqrt(n)的因子b即a*bn我们在试到b时就已经发现了。这个优化在n很大时能极大提升效率是体现编程思维的关键点。提前终止一旦在循环中找到任何一个因子就可以立刻断定n不是素数使用break跳出循环无需继续尝试。代码实现与注释#include stdio.h #include math.h // 为了使用sqrt函数 int main() { int n, i, is_prime 1; // 先假设是素数is_prime1代表是 printf(“请输入一个正整数 “); scanf(“%d”, n); // 边界条件判断 if (n 1) { is_prime 0; // 小于等于1不是素数 } else { // 循环从2尝试到sqrt(n) // 注意sqrt返回double这里强制转换为int循环到其整数部分即可 int limit (int)sqrt(n); for (i 2; i limit; i) { if (n % i 0) { // 找到因子 is_prime 0; // 不是素数 break; // 提前结束循环 } } } // 根据标志变量输出结果 if (is_prime) { printf(“%d是素数。\n”, n); } else { printf(“%d不是素数。\n”, n); } return 0; }经验之谈使用标志变量flagis_prime这种变量非常有用。它像一个开关初始设为“是”1一旦发现反例就拨到“否”0。最后根据这个开关状态输出结果逻辑清晰。break的合理使用在确定结果后立刻跳出循环避免无谓的计算这是一种良好的编程习惯。sqrt的引入与类型处理这是从数学思维到编程思维的跨越。注意sqrt()函数参数和返回值都是double型与整型n一起使用需要类型转换。虽然对于小的n不优化也可以但理解这个优化原理至关重要。4.2 求解水仙花数阿姆斯特朗数题目回顾一个n位正整数其各位数字的n次方之和等于它本身。例如153 1^3 5^3 3^3。解题逻辑拆解这题比素数判断多了一个步骤如何分离出一个整数的每一位数字确定范围比如找出100-999之间的所有水仙花数三位数。分离各位数字对于一个数num用% 10操作可以得到它的个位数用/ 10操作可以去掉它的个位数在整数除法中。通过循环可以分离出所有位。个位digit num % 10去掉个位后num num / 10重复直到num变为0。计算幂次和在分离每一位的同时计算该位数字的n次方这里n3并累加。判断与输出比较累加和与原数是否相等。代码实现与注释#include stdio.h int main() { int num, original_num, remainder, sum, digits; printf(“100到999之间的水仙花数有\n”); for (num 100; num 999; num) { original_num num; // 保存原数因为后面num会被修改 sum 0; // 临时变量计算位数三位数已知此步可省略但方法通用 int temp num; digits 0; while (temp ! 0) { temp / 10; digits; } // 已知是三位数所以 digits3这里为了演示通用分离方法 digits 3; temp num; // 重新赋值给temp用于分离各位 while (temp ! 0) { remainder temp % 10; // 获取当前最后一位 // 计算 remainder^digits。对于三位数就是 remainder*remainder*remainder int power 1; for (int i 0; i digits; i) { power * remainder; } sum power; // 累加 temp / 10; // 去掉最后一位 } // 判断 if (sum original_num) { printf(“%d “, original_num); } } printf(“\n”); return 0; }更简洁的写法针对已知的三位数for(num100; num999; num){ int a num / 100; // 百位 int b (num / 10) % 10; // 十位 int c num % 10; // 个位 if(num a*a*a b*b*b c*c*c){ printf(“%d “, num); } }核心技巧/和%的配合使用这是处理数字位运算的基石。/10用于“右移”%10用于“取余”。熟练掌握它们可以解决一大类数字处理问题。保存原始值在循环中修改了用于计算的变量num或temp后如果需要和原值比较一定要提前用另一个变量如original_num保存好原值。通用性与特例上面的通用方法用循环分离位数可以处理任意位数的水仙花数判断而简洁写法则只针对特定位数。在解题时先想通用逻辑再根据题目要求看能否简化。5. 流程控制综合应用break与continue的精准投放break和continue是循环体内的“交通管制”用好了代码简洁高效用错了就逻辑混乱。练习题中会设计一些场景来考察你对它们的理解。关键在于理解它们在多层嵌套循环中的行为。break立刻终止当前所在的这一层循环跳出该循环体。常见用途在搜索、判断场景中一旦找到目标或条件不满足就立即结束循环避免无效遍历。如前文素数判断中的break。嵌套循环中的break它只跳出自己所在的那一层循环。如果你想在内层循环中直接跳出所有外层循环需要借助额外的标志变量。示例在一个二维数组中查找某个特定值找到后立即停止所有搜索。int found 0; // 标志变量 for(int i0; iROWS; i){ for(int j0; jCOLS; j){ if(array[i][j] target){ printf(“找到目标在(%d, %d)\n”, i, j); found 1; break; // 只跳出内层的j循环 } } if(found){ // 检查标志如果找到了再跳出外层的i循环 break; } }continue跳过当前循环体中剩余的语句直接进入本层循环的下一次迭代。常见用途过滤掉某些不需要处理的情况。例如循环处理1到100的数但只对奇数进行操作。for(int i1; i100; i){ if(i % 2 0){ // 如果是偶数 continue; // 跳过后面语句直接i } // 以下是只对奇数执行的代码 printf(“%d 是奇数\n”, i); }练习题常见陷阱一道题可能要求你“计算1到100中既能被3整除又能被5整除的数的和但如果遇到能被7整除的数则跳过不计”。这里就综合了循环、条件判断和continue。int sum 0; for(int i1; i100; i){ if(i % 7 0){ continue; // 遇到被7整除的数跳过 } if(i % 3 0 i % 5 0){ sum i; // 累加同时满足被3和5整除的数 } } printf(“和为%d\n”, sum);关键点continue的位置。你必须先判断是否要跳过被7整除再进行累加判断。如果顺序反了逻辑就错了。理解break和continue能让你写出更“聪明”、更高效的循环而不是一味地让循环跑完所有次数。这是从“写出能运行的代码”到“写出好代码”的进阶一步。6. 从练习题到项目思维代码组织的初步尝试做完单个练习题后我们可以尝试一点更有挑战性的事情把几个相关的练习题组合起来写一个功能更完整的小程序。这能初步锻炼你的“项目思维”——如何组织代码、设计交互、管理功能模块。例如我们可以把判断素数、判断水仙花数、打印图形这几个功能整合到一个简单的菜单驱动程序中。设计思路主框架使用一个do-while循环作为主控因为程序至少要显示一次菜单并执行一次用户选择。菜单显示每次循环开始打印出几个选项。用户输入读取用户的选择。功能分发使用switch-case语句根据用户输入调用不同的函数或直接写代码块执行相应功能。退出机制设置一个“退出”选项选择后改变循环条件结束程序。代码框架示例#include stdio.h #include math.h #include stdbool.h // 使用bool类型更直观 // 函数声明 bool is_prime(int n); void print_triangle(int n); void find_narcissistic(void); int main() { int choice; do { // 显示菜单 printf(“\n 第六章练习综合程序 \n”); printf(“1. 判断素数\n”); printf(“2. 打印左下角直角三角形\n”); printf(“3. 查找水仙花数100-999\n”); printf(“4. 退出\n”); printf(“\n”); printf(“请选择1-4: “); scanf(“%d”, choice); switch (choice) { case 1: { int num; printf(“请输入要判断的整数 “); scanf(“%d”, num); if (is_prime(num)) { printf(“%d 是素数。\n”, num); } else { printf(“%d 不是素数。\n”, num); } break; } case 2: { int rows; printf(“请输入三角形的行数 “); scanf(“%d”, rows); print_triangle(rows); break; } case 3: find_narcissistic(); break; case 4: printf(“程序退出感谢使用\n”); break; default: printf(“输入错误请重新选择\n”); break; } // 暂停一下让用户看清结果 if(choice ! 4){ printf(“\n按回车键继续…”); while(getchar() ! ‘\n’); // 清空输入缓冲区 getchar(); // 等待用户按回车 } } while (choice ! 4); // 选择4时退出循环 return 0; } // 以下是函数定义之前练习题的代码封装成函数 bool is_prime(int n) { if (n 1) return false; int limit (int)sqrt(n); for (int i 2; i limit; i) { if (n % i 0) return false; } return true; } void print_triangle(int n) { for (int i 1; i n; i) { for (int j 1; j i; j) { putchar(‘*’); } putchar(‘\n’); } } void find_narcissistic(void) { printf(“100到999之间的水仙花数有\n”); for (int num 100; num 999; num) { int a num / 100; int b (num / 10) % 10; int c num % 10; if (num a*a*a b*b*b c*c*c) { printf(“%d “, num); } } printf(“\n”); }这样做的好处模块化每个功能被封装成独立的函数main函数变得非常清晰只负责流程控制。这符合良好的编程习惯。可复用is_prime、print_triangle这些函数可以在其他地方被直接调用。可维护如果想修改某个功能的实现比如优化素数判断算法只需要修改对应的函数不会影响其他部分。用户体验菜单交互让程序更像一个真正的工具而不是一次性的习题解答。虽然这只是一个非常小的“项目”但它引入了函数封装、菜单驱动、循环控制综合应用这几个非常重要的概念。把分散的练习题通过这种方式串联起来你对C语言的理解就不再是孤立的语法点而是一个可以构建简单系统的整体了。这才是通过练习题应该达到的最终目的。