
1. 循环到底练什么从一道题看透C语言循环的本质很多人学C语言学到循环这一章就开始犯迷糊。for、while、do-while三个关键字背得滚瓜烂熟一到自己写题就卡壳。我刚开始学的时候也一样看别人的代码觉得理所当然自己动手就不知道从哪下笔。后来带过几批新人我发现问题不在语法本身而在于没有搞清楚循环到底在解决什么问题。循环的本质就一句话让计算机重复做一件有规律的事直到满足某个条件才停下来。你想想如果让你打印1到100你不可能写一百行printf。但用循环三行代码就搞定。这就是循环存在的意义——把重复劳动交给机器把规律提炼留给人。那“简单C语言练习题循环”这个主题核心训练目标是什么我总结下来是四个层次第一层能写出正确的循环结构。知道for的括号里三个表达式分别干什么while什么时候用do-while和while的区别在哪。第二层能把实际问题翻译成循环逻辑。比如“求1到100的和”“判断一个数是不是素数”“打印九九乘法表”这些问题的共同点是都有重复操作的规律。第三层能控制循环的边界和终止条件。这是最容易出错的地方差一就多算一次或者少算一次俗称“off-by-one”错误。第四层能嵌套循环解决二维问题。比如打印图形、冒泡排序、矩阵遍历这些都需要两层甚至多层循环配合。这篇文章我会从最基础的循环结构讲起然后带你过一遍经典的循环练习题每一道都拆解思路、给出代码、分析易错点。最后还会分享一些我在实际教学和项目中总结的避坑经验。不管你是刚接触C语言的新手还是想复习一下循环知识的老手应该都能从中找到有用的东西。提示本文所有代码均可在标准C环境下编译运行建议你边看边动手敲一遍光看是学不会循环的。2. 三种循环结构怎么选for、while、do-while的适用场景2.1 for循环次数明确的重复任务首选for循环是练习题里出现频率最高的结构。它的语法形式是for (初始化表达式; 循环条件表达式; 循环变量更新表达式) { // 循环体 }执行顺序是这样的先执行初始化表达式只执行一次然后判断循环条件如果为真就执行循环体循环体执行完后执行更新表达式然后再判断条件如此反复直到条件为假。为什么for循环最适合次数明确的场景因为它把循环的三个关键要素——起点、终点、步长——都集中在一行里一目了然。比如“求1到100的和”int sum 0; for (int i 1; i 100; i) { sum i; } printf(1到100的和是%d\n, sum);这段代码里int i 1是起点i 100是终点条件i是步长。三个要素清清楚楚读代码的人一眼就能看出循环要跑100次。我在实际写代码时有个习惯只要循环次数在写代码时就能确定优先用for。比如遍历数组、打印固定行数的图形、计算阶乘这些都是for的主场。2.2 while循环条件驱动次数未知时更灵活while循环的语法更简单while (循环条件) { // 循环体 }它只有一个条件表达式条件为真就执行循环体为假就退出。看起来和for差不多但适用场景不同。while最适合循环次数不确定但知道什么时候该停的场景。举个经典例子输入一个正整数不断取出它的各位数字直到这个数变成0。int n; scanf(%d, n); while (n 0) { printf(%d , n % 10); n / 10; }你事先不知道这个数有几位所以没法用for来指定次数。但你知道循环该在n变成0时停止这就是while的典型用法。还有一个常见场景是输入验证。比如要求用户输入一个1到100之间的数如果输入不合法就让他重新输int num; printf(请输入1-100之间的整数); scanf(%d, num); while (num 1 || num 100) { printf(输入不合法请重新输入); scanf(%d, num); }这种“不知道用户要错几次才能输对”的情况用while就非常自然。2.3 do-while循环至少执行一次的保证do-while是三种循环里存在感最低的但有些场景非它不可do { // 循环体 } while (循环条件);注意末尾的分号这是初学者最容易漏掉的。do-while的特点是先执行一次循环体再判断条件。也就是说不管条件是否成立循环体至少会执行一次。什么时候需要这个特性最典型的是菜单程序int choice; do { printf(1. 开始游戏\n); printf(2. 查看规则\n); printf(0. 退出\n); printf(请选择); scanf(%d, choice); // 根据choice执行对应操作 } while (choice ! 0);菜单至少要显示一次用户才能做出选择。如果用while就得在循环外面先写一遍菜单代码显得冗余。do-while天然适合这种“先做一次再判断”的逻辑。2.4 三种循环的对比与选择建议对比维度forwhiledo-while循环次数事先已知事先未知事先未知条件判断时机每次循环体执行前每次循环体执行前每次循环体执行后最少执行次数0次0次1次典型场景遍历、计数、固定次数条件驱动、输入验证菜单、至少执行一次易错点分号位置、边界条件忘记更新循环变量末尾漏写分号我个人的选择优先级是能用for就用for需要条件驱动就用while需要至少执行一次就用do-while。三种循环在功能上可以互相转换但选对结构能让代码更清晰、更不容易出错。注意不管用哪种循环都要确保循环变量在循环体内有变化否则就会变成死循环。这是新手最常踩的坑之一。3. 经典循环练习题拆解从入门到进阶3.1 基础题累加、累乘与计数题目一求1到100所有偶数的和这道题考察的是循环变量的步长控制。最直接的写法是int sum 0; for (int i 2; i 100; i 2) { sum i; } printf(1到100所有偶数的和是%d\n, sum);这里i 2让循环变量每次增加2直接跳过奇数。另一种写法是遍历1到100用if判断偶数int sum 0; for (int i 1; i 100; i) { if (i % 2 0) { sum i; } }两种写法结果一样但第一种更高效因为循环次数少了一半。在实际写代码时能通过调整步长减少循环次数的就不要用if去过滤。题目二计算n的阶乘阶乘是累乘的典型代表int n; scanf(%d, n); long long fact 1; for (int i 1; i n; i) { fact * i; } printf(%d的阶乘是%lld\n, n, fact);这里有个细节fact的类型用了long long而不是int。因为阶乘增长非常快13!就已经超过int的表示范围了。这是很多初学者容易忽略的问题——累乘的结果类型要足够大。题目三统计一个整数各位数字之和这道题需要用到while循环因为数字的位数不确定int n, sum 0; scanf(%d, n); while (n 0) { sum n % 10; n / 10; } printf(各位数字之和是%d\n, sum);n % 10取出最后一位n / 10去掉最后一位循环直到n变成0。这个模式在处理数字问题时非常常用值得记住。3.2 进阶题嵌套循环与图形打印题目四打印九九乘法表嵌套循环是循环练习的重点和难点。九九乘法表是理解嵌套循环的最佳入门题for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%-2d , j, i, i * j); } printf(\n); }外层循环控制行数内层循环控制每行打印的列数。关键点在于内层循环的终止条件是j i而不是固定的9。这样第一行打印1列第二行打印2列形成三角形。%-2d的意思是左对齐、占2个字符宽度这样个位数和十位数的乘积能对齐。这种格式化输出的技巧在实际开发中很实用。题目五打印菱形图案打印菱形需要把图形分成上半部分和下半部分int n 4; // 上半部分行数 // 上半部分 for (int i 1; i n; i) { for (int j 1; j n - i; j) printf( ); for (int k 1; k 2 * i - 1; k) printf(*); printf(\n); } // 下半部分 for (int i n - 1; i 1; i--) { for (int j 1; j n - i; j) printf( ); for (int k 1; k 2 * i - 1; k) printf(*); printf(\n); }这道题的核心是找到每行空格数和星号数与行号之间的数学关系。上半部分第i行有n-i个空格和2i-1个星号。找到这个规律代码就水到渠成了。3.3 算法题素数判断与冒泡排序题目六判断一个数是否为素数素数是只能被1和自身整除的大于1的整数。最朴素的判断方法是试除int n, isPrime 1; scanf(%d, n); if (n 1) { isPrime 0; } else { for (int i 2; i * i n; i) { if (n % i 0) { isPrime 0; break; } } } printf(%d%s素数\n, n, isPrime ? 是 : 不是);这里有两个优化点值得注意第一循环条件用i * i n而不是i n / 2因为如果n有因子必然有一个小于等于sqrt(n)。第二一旦找到因子就用break跳出循环避免无意义的后续判断。题目七冒泡排序冒泡排序是循环嵌套的经典应用int arr[] {5, 3, 8, 1, 9, 2, 7, 4, 6}; int n sizeof(arr) / sizeof(arr[0]); for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } }外层循环控制排序的轮数内层循环负责每轮的比较和交换。注意内层循环的终止条件是n - 1 - i因为每轮结束后最大的元素已经“冒泡”到了末尾不需要再比较。提示冒泡排序的时间复杂度是O(n²)实际项目中不会用它来排序大数据集但作为理解循环嵌套和算法思想的练习题它的价值不可替代。4. 循环练习中的常见坑与排查技巧4.1 死循环原因分析与预防死循环是循环练习中最常见的问题。所谓死循环就是循环条件永远为真程序永远跳不出来。常见原因有忘记更新循环变量while (i 10) { printf(%d, i); }i永远是初始值条件永远成立。循环变量更新方向错误for (int i 10; i 0; i)i越来越大永远大于0。条件表达式写反while (i 10)注意这里是赋值不是比较i被赋值为10条件永远为真。浮点数比较精度问题for (double d 0.0; d ! 1.0; d 0.1)由于浮点数精度问题d可能永远不等于1.0。预防死循环的方法很简单写完循环后在脑子里模拟执行一遍看看循环变量是否在向终止条件靠近。如果拿不准可以在循环体里加一句printf输出循环变量运行一下看看。4.2 边界条件差一错误怎么破差一错误off-by-one error是循环练习中的另一个高频问题。比如“求1到100的和”写成i 100就少加了100写成i 101就多加了101。破解差一错误的方法用具体数字代入验证。比如循环条件是i n你就假设n 3手动列出i的取值1、2、3正好三次说明条件正确。如果列出的是1、2、3、4那就多了一次。另一个技巧是用“最小案例”测试。比如写一个循环处理数组先用一个只有1个元素的数组测试再用2个元素的数组测试看看边界情况是否处理正确。4.3 嵌套循环的效率问题嵌套循环虽然强大但效率问题不容忽视。两层循环的时间复杂度是O(n²)三层就是O(n³)。在实际项目中如果数据量较大嵌套循环可能会成为性能瓶颈。优化嵌套循环的常见思路减少内层循环的次数比如冒泡排序中内层循环的终止条件从n-1逐步减小到1避免重复比较已经排好的元素。提前终止如果在内层循环中已经找到了满足条件的元素用break跳出不要继续无意义的遍历。用空间换时间比如用哈希表代替内层循环的查找操作把O(n²)降到O(n)。注意在练习题阶段优先保证逻辑正确效率优化可以放在后面考虑。但养成关注效率的习惯对以后写实际项目大有好处。4.4 常见问题速查表问题现象可能原因排查方法解决方案程序卡死无输出死循环在循环体加printf输出循环变量检查循环变量是否更新、条件是否合理结果多算/少算一次边界条件错误用最小案例手动模拟调整和、起始值和终止值嵌套循环结果不对内外层循环变量混淆分别打印内外层变量给变量起有意义的名字如row、col累乘结果溢出数据类型范围不够打印中间结果观察改用long long或double循环体一次都没执行初始条件就不满足检查循环条件确认是否需要改用do-while5. 从练习题到实际项目循环的真实应用场景5.1 单片机开发中的循环应用在单片机C语言开发中循环无处不在。比如按键消抖就需要用循环来延时void delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) { for (j 0; j 120; j) { // 空循环消耗时间 } } }这种延时函数虽然简单但在资源受限的单片机上非常实用。不过要注意空循环的延时时间受编译器优化和主频影响实际项目中更推荐用定时器。另一个典型场景是主循环。单片机的程序结构通常是一个无限循环while (1) { // 读取传感器数据 // 处理数据 // 控制执行器 }这个while (1)就是典型的死循环但在这里是故意为之——单片机程序本来就应该一直运行直到断电。5.2 数据处理中的循环模式在实际的数据处理任务中循环的模式通常比练习题复杂得多。比如从一个数组中筛选出符合条件的数据int data[100]; int count 0; for (int i 0; i 100; i) { if (data[i] threshold data[i] upperBound) { result[count] data[i]; } }这里用count作为结果数组的索引每找到一个符合条件的元素就存入并递增。这种“筛选收集”的模式在数据处理中非常常见。还有累加器模式用于计算统计量double sum 0.0; double max data[0]; double min data[0]; for (int i 0; i n; i) { sum data[i]; if (data[i] max) max data[i]; if (data[i] min) min data[i]; } double average sum / n;一次遍历同时计算和、最大值、最小值效率比多次遍历高得多。这种“一次遍历多个统计”的思路在实际项目中很实用。5.3 循环与算法的结合循环是几乎所有算法的基础构件。比如二分查找int binarySearch(int arr[], int n, int target) { int left 0, right n - 1; while (left right) { int mid left (right - left) / 2; if (arr[mid] target) return mid; else if (arr[mid] target) left mid 1; else right mid - 1; } return -1; }这里的while循环没有固定的执行次数而是根据查找范围不断缩小来驱动。循环条件和边界更新是二分查找的核心写错一个地方就会导致死循环或漏查。再比如字符串处理中的循环char str[100]; fgets(str, sizeof(str), stdin); int len 0; while (str[len] ! \0 str[len] ! \n) { len; }这段代码用while循环计算字符串长度直到遇到结束符或换行符。fgets会读入换行符所以需要额外判断。6. 循环练习的进阶路线与学习建议6.1 从模仿到独立练习的正确姿势我见过很多初学者看代码能看懂自己写就写不出来。问题出在练习方式不对。正确的练习姿势应该是先自己想再看答案。拿到一道题先花10分钟自己尝试写哪怕写错了也没关系。思考的过程比正确答案更重要。对比答案找差距。写完后和参考代码对比看看哪里不一样为什么别人那样写更好。不看答案重写一遍。合上答案从头再写一遍这次争取一次写对。变式练习。把题目改一改比如把“求1到100的和”改成“求1到100的平方和”看看自己能不能举一反三。这个流程看起来慢但效果比刷十道题都强。循环这种编程思维只能通过动手写来掌握光看是看不会的。6.2 推荐练习题目清单如果你正在找循环练习题我整理了一份从易到难的清单可以按顺序刷难度题目核心考点入门求1到100的和for循环基本用法入门打印1到100的偶数循环步长控制入门计算n的阶乘累乘、数据类型基础统计数字位数while循环、取模运算基础判断素数循环break、优化条件基础打印九九乘法表嵌套循环、格式化输出进阶打印菱形嵌套循环、数学规律进阶冒泡排序嵌套循环、数组操作进阶求最大公约数辗转相除法、while循环挑战打印杨辉三角二维数组、嵌套循环6.3 调试循环代码的实用技巧调试循环代码时最有效的方法是打印中间状态。比如for (int i 0; i n; i) { printf(i %d, arr[i] %d\n, i, arr[i]); // 调试输出 // 实际逻辑 }通过观察每次循环的变量值可以快速定位问题。如果循环次数太多可以加一个计数器只打印前几次和最后几次。另一个技巧是用assert断言#include assert.h for (int i 0; i n; i) { assert(i 0 i n); // 确保索引不越界 // 实际逻辑 }assert会在条件不满足时终止程序并报错帮助快速发现问题。不过要注意assert只在调试模式下有效发布版本中会被禁用。提示如果你用VSCode写C语言可以在launch.json中配置调试环境设置断点后单步执行观察循环变量的变化。这比printf调试更直观。6.4 循环学习的常见误区最后说几个循环学习中的常见误区希望能帮你少走弯路误区一追求一行代码解决问题。有些人喜欢把循环写得很紧凑比如for(int i0;in;sumi);。这种代码虽然短但可读性极差。代码是给人看的顺便给机器执行清晰比简短重要。误区二忽略循环不变式。循环不变式是指在循环执行过程中始终保持为真的条件。比如在求和循环中“sum等于前i个数的和”就是一个循环不变式。理解循环不变式有助于写出正确的循环。误区三所有重复都用循环。有些场景用递归更自然比如树的遍历。循环和递归各有适用场景不要死守一种。误区四不重视边界测试。循环代码写完后至少要用三组数据测试最小值、最大值、中间值。比如处理数组的循环要测试空数组、单元素数组、多元素数组。我在实际带新人的过程中发现那些循环学得扎实的人后面学数组、指针、链表都会顺利很多。因为循环是编程思维的基础它训练的是“把复杂问题分解成重复步骤”的能力。这个能力一旦掌握学什么语言、做什么项目都会受益。如果你现在还在循环这里卡着别着急多写多练写错了就调试调通了就总结。十道题之后你会发现循环不过如此。二十道题之后你会开始享受用循环解决实际问题的快感。