新闻详情

新闻详情

首页 / 资讯中心 / 详情

C语言循环练习题精讲:从累加累乘到图形打印与字符串处理

发布时间:2026/9/27 1:42:28来源:尧图网络
C语言循环练习题精讲:从累加累乘到图形打印与字符串处理
循环是C语言里第一个真正让人“开窍”的东西。我见过太多人卡在循环这一关语法背得滚瓜烂熟一到自己写就不知道从哪下手或者写出来跑一遍结果不对盯着屏幕半天找不出问题。其实循环本身不难难的是把“重复执行”这个抽象概念翻译成具体的代码逻辑。这篇内容就是围绕C语言循环练习题展开的我会把常见的循环题目类型、每类题目的解题思路、代码实现、以及我在教学和实际编码中踩过的坑全部拆开揉碎讲清楚。不管你是刚学完for和while语法的新手还是想找一批练习题巩固基础的进阶者都能从这里找到可以直接上手的内容。1. 循环练习题到底在练什么1.1 循环的本质把重复劳动交给机器很多人做循环练习题的时候容易陷入一个误区——把它当成数学题来解。比如打印九九乘法表有人会去推导每一行和列的关系然后试图用一个公式直接算出来。这个方向不能说错但偏离了循环练习的核心目的。循环的本质是控制重复。你需要练的是三件事第一识别出哪些操作在重复第二找到重复的起点和终点第三确定每次重复之间什么在变、什么不变。这三件事想清楚了代码自然就出来了。拿一个最简单的例子来说计算1到100的和。重复的操作是“加法”起点是1终点是100每次变化的是加数本身。用for循环写就是int sum 0; for (int i 1; i 100; i) { sum i; }这段代码谁都写得出来。但如果换成“计算1到100之间所有偶数的和”变化的是什么是步长。i每次不再加1而是加2。或者你保留i但在循环体里加一个判断。两种写法都对但前者更简洁后者更通用。这就是循环练习题要训练的判断力——在多种可行方案中选择最合适的那一种。1.2 循环练习题的四大类型我把常见的C语言循环练习题分成四类每类的训练目标不同类型典型题目训练目标累加累乘型求1到n的和、阶乘、斐波那契数列循环变量的初始化和更新图形打印型打印三角形、菱形、九九乘法表嵌套循环的层次控制数值判断型素数判断、水仙花数、完数循环与条件判断的配合字符串处理型字符串逆序、统计字符个数循环与数组/指针的结合这四类题目覆盖了循环的绝大多数应用场景。你不需要刷几百道题每类认真做透五六道循环就算过关了。1.3 新手最容易踩的三个坑在展开具体题目之前先把最常见的三个坑说清楚后面看例题的时候你会更有感觉。第一个坑循环变量在循环体外被修改。比如你在for循环里又写了一个i或者在循环体里调用了会改变i的函数。这种bug很隐蔽因为语法上完全合法但逻辑上循环次数就乱了。第二个坑边界条件搞错。到底是i n还是i n差一个等号结果可能完全不同。我的习惯是如果循环变量代表“第几次”用1起始配如果代表数组下标用0起始配。第三个坑嵌套循环的变量混用。外层用i内层也用i编译器不会报错但逻辑全乱。嵌套循环的变量名一定要区分开i、j、k是约定俗成的选择。提示写完循环后用n1、n2这样的小值手动跑一遍比盯着代码看十分钟都管用。2. 累加累乘型题目精讲2.1 从1加到n最基础但最容易写错这道题几乎是所有C语言教材的第一个循环例题。看起来简单但我见过至少三种典型错误写法。错误写法一int sum 0; for (int i 1; i n; i); { sum i; }注意for后面那个分号。它让循环体变成了空语句下面的花括号块只执行一次。这个错误编译器不会报错但结果永远是sum等于n1因为i在循环结束后等于n1。错误写法二int sum; for (int i 1; i n; i) { sum i; }sum没有初始化。在C语言里局部变量的初始值是随机的sum可能从任意值开始累加。这个错误的结果不可预测有时候碰巧对有时候错得离谱。错误写法三int sum 0; for (int i 1; i n; i) { sum i; }边界写成了i n少加了最后一项。如果n100实际只加到99。正确的写法前面已经给过了。我想强调的是累加型题目的标准模板是初始化累加器为0循环变量从起点到终点循环体里做累加。这个模板可以套用到求和、计数、求平均值等所有类似场景。2.2 阶乘计算注意数据溢出阶乘的循环结构和求和几乎一样只是把加法换成乘法累加器初始值从0换成1。long long factorial(int n) { long long result 1; for (int i 2; i n; i) { result * i; } return result; }这里有两个细节值得说。第一循环从2开始而不是从1开始因为乘1没有意义少一次循环少一次开销。第二返回值用long long而不是int因为13的阶乘就已经超过int的范围了int最大约21亿13!约62亿。实际测试一下12! 479001600在int范围内13! 6227020800溢出了。如果你用int接收会得到一个负数或者错误的值。这种溢出问题在累乘型题目里非常常见养成用long long的习惯能省很多调试时间。2.3 斐波那契数列循环替代递归的经典案例斐波那契数列的定义是前两项为1从第三项开始每项等于前两项之和。用递归写很直观但效率极低用循环写效率高得多。long long fib(int n) { if (n 2) return 1; long long a 1, b 1, c; for (int i 3; i n; i) { c a b; a b; b c; } return b; }这段代码的核心是三个变量的滚动更新。a和b始终保存相邻的两项每次循环计算出下一项c然后把b赋给a、c赋给b。循环结束时b就是第n项。我特意用long long是因为斐波那契数列增长极快第50项就已经超过int范围了。另外注意n2的边界处理如果不处理n1或n2时循环不执行返回的b是初始值1恰好正确但逻辑上不够清晰。实操心得滚动更新的思路在动态规划、滑动窗口等算法里反复出现把这个模式练熟后面学更复杂的内容会轻松很多。3. 图形打印型题目精讲3.1 打印直角三角形理解嵌套循环的执行顺序打印图形的题目是训练嵌套循环最好的方式。先看最简单的直角三角形* ** *** **** *****代码for (int i 1; i 5; i) { for (int j 1; j i; j) { printf(*); } printf(\n); }外层循环控制行数内层循环控制每行打印几个星号。关键在于内层循环的终止条件是j i也就是说第i行打印i个星号。新手常犯的错误是把内层写成j 5结果打印出一个5x5的方阵。还有人把printf(\n)写在内层循环里导致每个星号后面都换行。记住换行是行级别的操作必须放在内层循环外面、外层循环里面。3.2 打印等腰三角形空格的处理是关键等腰三角形比直角三角形多了一个空格的控制* *** ***** ******* *********代码int n 5; 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); }这里有两个内层循环第一个打印空格第二个打印星号。空格数的规律是n - i星号数的规律是2 * i - 1。这两个公式不是背下来的是观察出来的第1行4个空格1个星第2行3个空格3个星第3行2个空格5个星……每行空格递减1星号递增2。我建议你在纸上画出n5的图形然后逐行数空格和星号的数量自己推导公式。推导一遍比看十遍代码印象深得多。3.3 打印九九乘法表格式控制的实战九九乘法表是图形打印和数值计算的结合for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%-2d , j, i, i * j); } printf(\n); }这段代码的结构和直角三角形完全一样只是把打印星号换成了打印乘法表达式。%-2d的意思是左对齐占2个字符宽度这样每列的乘积对齐看起来整齐。如果你想让乘法表呈下三角排列第一行9个最后一行1个把内层循环改成j 10 - i即可。这种变体练习能帮你理解循环边界和图形形状之间的对应关系。注意printf里的格式控制符要和参数类型匹配。用%d打印long long会出错应该用%lld。这个坑在图形打印题里不常见但在数值计算题里经常遇到。4. 数值判断型题目精讲4.1 素数判断循环提前退出的典型应用判断一个数n是否为素数最朴素的做法是检查2到n-1之间有没有能整除n的数int isPrime(int n) { if (n 2) return 0; for (int i 2; i n; i) { if (n % i 0) return 0; } return 1; }这段代码里return 0就是提前退出循环。一旦找到一个因子就不需要继续检查了直接返回“不是素数”。这种“找到答案就退出”的模式在循环里非常常见。优化版本只需要检查到sqrt(n)#include math.h int isPrime(int n) { if (n 2) return 0; for (int i 2; i sqrt(n); i) { if (n % i 0) return 0; } return 1; }为什么是sqrt(n)因为如果n有一个大于sqrt(n)的因子那必然有一个小于sqrt(n)的对应因子。比如100的因子对(2,50)、(4,25)、(5,20)、(10,10)。检查到10就够了后面的因子对在前面已经出现过了。这个优化把时间复杂度从O(n)降到O(sqrt(n))对于n1000000这样的输入循环次数从一百万降到一千差距非常明显。4.2 水仙花数拆位运算与循环枚举水仙花数是指一个三位数其各位数字的立方和等于该数本身。比如153 1³ 5³ 3³。for (int n 100; n 999; n) { int a n / 100; int b (n / 10) % 10; int c n % 10; if (a*a*a b*b*b c*c*c n) { printf(%d , n); } }这道题练的是两件事用循环枚举所有可能的值用整除和取余拆出各位数字。拆位的公式值得记一下百位是n/100十位是(n/10)%10个位是n%10。对于四位数千位是n/1000百位是(n/100)%10以此类推。如果你想练得更深入可以扩展到四叶玫瑰数四位数各位四次方和等于本身或者自幂数n位数各位n次方和等于本身。自幂数的求解需要嵌套循环外层枚举位数内层枚举该位数的所有数是一个很好的综合练习。4.3 完数循环嵌套与因子求和完数是指一个数恰好等于它的所有真因子之和。比如6 1 2 3。for (int n 2; n 1000; n) { int sum 0; for (int i 1; i n; i) { if (n % i 0) { sum i; } } if (sum n) { printf(%d , n); } }外层循环枚举候选数内层循环找因子并累加。这道题的结构和素数判断很像区别在于素数判断找到因子就退出完数判断需要找完所有因子。优化思路和素数判断一样因子只需要找到sqrt(n)但要注意完全平方数的情况。比如36的因子有1、2、3、4、6、9、12、18其中6是sqrt(36)只能算一次。这个细节处理起来稍微麻烦一点但对于初学者来说先把朴素版本写对更重要。5. 字符串处理型题目精讲5.1 字符串逆序双指针与循环的结合字符串逆序是循环和数组结合的经典题目。给定一个字符串把它原地反转。#include string.h void reverseString(char *s) { int len strlen(s); for (int i 0; i len / 2; i) { char temp s[i]; s[i] s[len - 1 - i]; s[len - 1 - i] temp; } }循环只走到len/2因为每次交换涉及两个字符走一半就够了。如果走完整长度相当于交换两次又变回原样了。这里用strlen获取长度需要包含string.h。如果你不想用库函数可以自己写一个循环来算长度int len 0; while (s[len] ! \0) { len; }这个while循环利用了C语言字符串以\0结尾的特性。循环条件是当前字符不是结束符每次循环len加1。循环结束时len就是字符串长度。5.2 统计字符类型循环遍历与条件分支统计一个字符串中大写字母、小写字母、数字和其他字符的个数#include stdio.h int main() { char str[100]; fgets(str, sizeof(str), stdin); int upper 0, lower 0, digit 0, other 0; for (int i 0; str[i] ! \0; i) { if (str[i] A str[i] Z) { upper; } else if (str[i] a str[i] z) { lower; } else if (str[i] 0 str[i] 9) { digit; } else { other; } } printf(大写:%d 小写:%d 数字:%d 其他:%d\n, upper, lower, digit, other); return 0; }这里用fgets而不是scanf因为scanf遇到空格就停止读取了而fgets可以读入整行。fgets会把换行符也读进来所以统计结果里“其他”会多一个。如果不想统计换行符可以在循环里加一个判断跳过\n。字符比较用的是ASCII码。A到Z是65到90a到z是97到1220到9是48到57。用字符字面量比较比用数字更直观也不容易出错。5.3 字符串匹配嵌套循环的字符串版本判断一个字符串是否包含另一个子串int contains(char *str, char *sub) { int strLen strlen(str); int subLen strlen(sub); for (int i 0; i strLen - subLen; i) { int j; for (j 0; j subLen; j) { if (str[i j] ! sub[j]) { break; } } if (j subLen) { return 1; } } return 0; }外层循环遍历主串的每个可能的起始位置内层循环逐字符比较。如果内层循环正常结束j等于subLen说明找到了匹配。如果中途break说明当前位置不匹配继续尝试下一个位置。外层循环的终止条件是i strLen - subLen因为如果剩余字符数少于子串长度不可能匹配。这个边界条件很容易写错写成i strLen的话当i接近末尾时访问str[ij]会越界。实操心得字符串处理题目里越界访问是最常见的bug。养成检查循环边界的习惯特别是涉及ij这种复合下标的时候。6. 循环控制进阶技巧6.1 break和continue的正确使用场景break用于立即退出当前循环continue用于跳过本次循环的剩余部分、直接进入下一次迭代。// break示例找到第一个能被7整除的数 for (int i 1; i 100; i) { if (i % 7 0) { printf(找到了%d\n, i); break; } } // continue示例打印1到20之间所有不能被3整除的数 for (int i 1; i 20; i) { if (i % 3 0) { continue; } printf(%d , i); }break在嵌套循环里只退出最内层循环。如果你想一次退出所有层有两种做法一是用goto不推荐但确实有效二是设置一个标志变量每层循环都检查这个标志。int found 0; for (int i 0; i 10 !found; i) { for (int j 0; j 10; j) { if (someCondition) { found 1; break; } } }这种标志变量的写法比goto更清晰在实际工程中更常用。6.2 循环嵌套的执行次数计算嵌套循环的总执行次数是各层循环次数的乘积。比如for (int i 0; i n; i) { for (int j 0; j m; j) { // 循环体执行n*m次 } }如果内层循环的边界依赖外层变量for (int i 0; i n; i) { for (int j 0; j i; j) { // 循环体执行012...(n-1) n(n-1)/2次 } }这个求和公式在分析算法复杂度时经常用到。比如冒泡排序的比较次数就是n(n-1)/2选择排序也是。理解嵌套循环的执行次数是分析算法效率的基础。6.3 do-while循环的适用场景do-while和while的区别是do-while至少执行一次循环体然后再判断条件。int input; do { printf(请输入一个正数); scanf(%d, input); } while (input 0);这个场景用do-while很自然先让用户输入然后检查输入是否合法不合法就重新输入。如果用while需要先把input初始化成一个非法值代码会稍微别扭一点。do-while在菜单程序里也很常用int choice; do { printf(1. 添加 2. 删除 3. 退出\n); scanf(%d, choice); switch (choice) { case 1: /* 添加逻辑 */ break; case 2: /* 删除逻辑 */ break; } } while (choice ! 3);菜单至少显示一次用户选择退出才结束循环do-while的语义正好匹配。7. 常见问题与排查技巧实录7.1 死循环的排查方法死循环是循环题目里最让人头疼的问题。程序卡住不动CPU占用飙升但就是不出结果。排查死循环的第一步是检查循环变量是否在变化。比如int i 0; while (i 10) { printf(%d , i); // 忘记写i了 }这种错误很直观。更隐蔽的是循环变量在循环体里被意外修改for (int i 0; i 10; i) { i i * 2; // i的变化被干扰了 }或者循环条件永远为真int i 0; while (i 0) { // i是int永远可能为负但这里i递增最终会溢出变成负数 i; }排查方法在循环体里加一句printf打印循环变量的值跑几次就能看出问题。或者用调试器设断点单步跟踪。7.2 循环结果差一的排查“差一错误”off-by-one error是循环里第二常见的问题。结果总是差一个数或者多循环了一次、少循环了一次。现象可能原因修正方法结果少最后一项循环条件用了而不是检查边界确认是否包含终点结果多一项循环条件用了而不是同上反向检查数组越界循环变量从0到n含n数组下标应该是0到n-1累加结果偏大累加器初始值不是0检查累加器初始化我的经验是用n1和n2手动验证。如果n1时结果正确n2时也正确那n100大概率也正确。如果n1就错了那肯定是边界问题。7.3 嵌套循环变量混用的排查嵌套循环里内外层用了同一个变量名编译器不会报错但逻辑完全错误for (int i 0; i 5; i) { for (int i 0; i 3; i) { // 内层也用了i printf(*); } printf(\n); }这段代码的实际效果是外层i从0开始进入内层循环内层i从0到2打印3个星号内层结束后i3。回到外层i变成4再次进入内层i被重置为0……最终打印5行星号但外层的i被内层覆盖了逻辑上完全混乱。避免方法嵌套循环的变量名用i、j、k依次命名形成肌肉记忆。如果层数超过3层考虑把内层逻辑抽成函数。7.4 常见编译警告与循环相关的问题编译器的一些警告和循环直接相关不要忽略它们warning: suggest parentheses around assignment used as truth value你可能在while条件里写了赋值而不是比较。warning: comparison between signed and unsigned integer expressions循环变量类型和比较值类型不一致可能导致意外的循环次数。warning: variable i set but not used循环变量声明了但没用到可能是写错了变量名。把编译器的警告级别调到最高gcc用-Wall -Wextra能提前发现很多循环相关的潜在问题。提示如果你用的是VS Code配置C语言环境在tasks.json的args里加上-Wall和-Wextra编译时就能看到所有警告。8. 从练习题到实际应用的过渡8.1 循环在算法题中的典型模式刷算法题的时候循环很少单独出现通常和数组、字符串、递归结合。但底层逻辑还是那几样遍历、累加、查找、排序。以冒泡排序为例void bubbleSort(int arr[], int n) { 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因为每轮结束后最大的元素已经“冒”到末尾了不需要再比较。这个结构和前面打印三角形的嵌套循环本质相同只是循环体里的操作从打印变成了比较和交换。你把图形打印练熟了再看排序代码就不会觉得陌生。8.2 循环在嵌入式开发中的实际应用如果你学C语言是为了做单片机开发循环的使用场景会更具体。比如按键消抖int keyPressed() { if (KEY_PIN 0) { for (int i 0; i 1000; i); // 延时消抖 if (KEY_PIN 0) { return 1; } } return 0; }这个空循环就是用来延时的。在单片机里每条指令的执行时间固定空循环的次数乘以指令周期就是延时时间。这种用法在PC程序里很少见但在嵌入式里非常普遍。另一个常见场景是PWM调光for (int duty 0; duty 100; duty) { for (int i 0; i 100; i) { if (i duty) { LED_ON(); } else { LED_OFF(); } } }外层循环控制占空比从0到100内层循环在一个周期内控制LED的亮灭时间。这种嵌套循环在单片机编程里随处可见。8.3 循环性能优化的基本思路当循环次数很大时循环体的效率就很重要了。几个基本的优化原则减少循环体内的重复计算。比如// 优化前 for (int i 0; i strlen(s); i) { ... } // 优化后 int len strlen(s); for (int i 0; i len; i) { ... }strlen是O(n)的操作放在循环条件里每次迭代都要重新计算整体变成O(n²)。提前算好存到变量里就变成O(n)了。把不依赖循环变量的计算提到循环外。比如// 优化前 for (int i 0; i n; i) { result i * base_value * 2; } // 优化后 int factor base_value * 2; for (int i 0; i n; i) { result i * factor; }循环展开。对于循环体很小、循环次数很大的情况每次迭代手动处理多个元素减少循环控制的开销// 优化前 for (int i 0; i n; i) { sum arr[i]; } // 优化后每次处理4个 int i; for (i 0; i 3 n; i 4) { sum arr[i] arr[i1] arr[i2] arr[i3]; } for (; i n; i) { sum arr[i]; }循环展开在现代编译器里通常会自动做但理解这个思路对分析性能瓶颈有帮助。8.4 循环与递归的转换有些题目既可以用循环写也可以用递归写。理解两者的转换关系能加深对循环的理解。以阶乘为例递归版本long long factorial_recursive(int n) { if (n 1) return 1; return n * factorial_recursive(n - 1); }循环版本前面已经给过了。递归的终止条件对应循环的初始条件递归的递推关系对应循环的迭代更新。递归代码更简洁但每次调用都有函数栈开销循环代码稍长但效率更高。在实际工程中能用循环解决的问题尽量用循环因为递归深度过大会导致栈溢出。但在某些数据结构如树、图的遍历中递归的表达力更强这时候用递归更合适。我在实际教学中发现循环练习题做不出来的同学往往不是语法问题而是没有把“重复”这件事想清楚。拿到题目先别急着写代码用自然语言描述一下第一步做什么第二步做什么什么时候停。把这个流程写出来再翻译成C语言难度会降低很多。另外手动模拟循环的执行过程非常重要拿一张纸把每次循环变量的值写下来跑三五轮规律自然就出来了。这个笨办法比盯着代码看有效得多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

蓝牙调试器实战指南:从BLE服务发现到数据收发与踩坑经验 2026/9/27 2:37:05

蓝牙调试器实战指南:从BLE服务发现到数据收发与踩坑经验

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

阅读更多 →
Keil5三合一环境配置:MDK+C51+C251共存指南 2026/9/27 2:37:05

Keil5三合一环境配置:MDK+C51+C251共存指南

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

阅读更多 →
固态电池的迭代 2026/9/27 2:36:59

固态电池的迭代

在全球能源转型与双碳战略深入推进的时代背景下,锂离子电池作为新能源汽车、储能电站、智能终端的核心储能载体,已成为支撑新型能源体系建设的关键基础器件。但经过数十年发展,传统液态锂电池逐渐暴露技术天花板,安全性不足、能量…

阅读更多 →
eMMC存储区域与EXT_CSD[179]分区配置实战指南 2026/9/27 2:36:59

eMMC存储区域与EXT_CSD[179]分区配置实战指南

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

阅读更多 →
DDD 实战专栏:从业务到代码——导读 2026/9/27 2:36:59

DDD 实战专栏:从业务到代码——导读

一、为什么写这个专栏软件行业里,DDD 被谈论了很多年,但真正能把它落到代码里的团队并不多。不少团队尝试过 DDD。战略设计阶段通常还算顺利——限界上下文、聚合这些概念能理解,画出来的领域模型也有模有样。但一进入战术设计,问…

阅读更多 →
STM32F407总线架构详解:从51到AHB/APB与DMA的并行进阶 2026/9/27 2:36:59

STM32F407总线架构详解:从51到AHB/APB与DMA的并行进阶

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

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉