C语言自学秋招Day5:循环语法、算法题与避坑经验 今天是我冲击秋招的C语言自学第5天。说实话我依然是一只非常标准的菜鸟编译报错会慌指针还没碰很多术语听名字熟悉、问细节就露馅。但今天对我来说很特殊因为我终于把循环这个东西彻底想明白了。循环看起来只是让一段代码重复执行实际上它是我们第一次用程序员的思维去描述“规律”也是从“看懂别人代码”到“自己造代码”之间最重要的一道坎。今天这篇不是教程是我自己的学习记录和踩坑复盘。我学了for、while、do-while三种循环的语法和区别练了三道题100以内素数、十进制转二进制、斐波那契数列还第一次用printf大法解决了一个死循环问题。如果你也正在为秋招自学C语言进度跟我差不多那这篇文章应该能帮上忙——至少能让你知道Day5到底该练什么、怎么练、哪些坑可以提前绕开。时间上距离秋招正式批还有几个月我给自己定的计划是先用一个月把C语言基础打牢之后集中刷题和做小项目。Day5刚好走在这个计划的三分之一节点上进度确实不算快但我宁愿稳一点也不想把基础盖成危楼。1. 第5天在计划中的位置我的C语言自学路线与秋招目标1.1 前4天的学习路线从编辑器到if/else前4天我走得很常规但每一步都配了练习。Day1装环境一门编辑器加gcc编译器跑通Hello World。这东西看着简单实际上折腾了我大半天因为我不懂什么是“环境变量”也不懂编译和运行的区别。Day2开始学变量、数据类型、printf和scanf那天我踩的第一个坑就是scanf忘记写取地址符程序一运行就报错当时完全看不懂“段错误”是什么意思。Day3学运算符、表达式顺便把自增自减的坑趟了一遍a和a的区别我背了半天。Day4学分支结构用if/else写了一个简易计算器菜单。进度谈不上快但我刻意保持了“每学一个点就写代码验证”的节奏。前几天的内容相对简单很容易产生一种虚假的成就感——好像自己什么都会了可一旦合上书自己上手写就开始卡壳。所以到了第5天我决定把速度压得更低认认真真把循环这关过掉。1.2 为什么第5天要死磕循环它是算法的最小积木很多新手会低估循环的重要性觉得“不就是重复执行吗”。但往后面走你会发现几乎所有的笔试算法题骨子里都是循环的变种。穷举、遍历、迭代、递归转循环甚至后面学的数组、字符串、指针基本都要靠循环驱动。如果说分支结构是岔路口那循环就是传送带——程序批量处理数据的核心方式。我在Day5还没资格聊算法但我已经能感觉到笔试里那些“名校生轻松写出”的题本质上就是“循环套循环套条件”。如果循环不熟后面学什么都像在沙子上盖楼。所以我今天的核心任务只有一个把for、while、do-while三种写法彻底捋清楚并且在题目里用起来。1.3 用秋招笔试倒推今天的目标我给自己定学习计划时用了一个很笨但很管用的方法倒推。不去纠结“今天我该学教材的第几章”而是直接问自己“如果笔试遇到这类题我需要会什么”。Day5的答案很明确看到累加求和、穷举判断、遍历这种需求我能马上用循环把代码写出来。所以今天结束前的自我测试就是三道题求100以内素数、十进制转二进制、斐波那契数列。这三道题没有一道是偏题怪题全是笔试里最常见的基础题型。写完、跑通、复盘今天的目标就算达成。2. 循环语法拆解for、while、do-while 的差异与应用场景2.1 同一个求和任务用三种循环分别实现先看一个最经典的例子求1到100的累加和。我用三种方式各写一遍#include stdio.h int main(void) { int sum 0; for (int i 1; i 100; i) { sum i; } printf(for: %d\n, sum); sum 0; int j 1; while (j 100) { sum j; j; } printf(while: %d\n, sum); sum 0; int k 1; do { sum k; k; } while (k 100); printf(do-while: %d\n, sum); return 0; }三个输出都是5050但执行逻辑有明显差异。for循环把“初始化、条件判断、变量更新”都集中在一个括号里结构紧凑最喜欢用在“我知道要循环几次”的场景。while循环只关心条件变量更新要靠自己写在循环体里更适合“我不知道具体几次只知道什么时候该停”的情况比如读文件直到EOF、等待某个状态变成true。do-while则是先执行循环体再判断条件所以无论如何都会先跑一次。我给朋友讲的时候打了个比方for像“齐步走走满一百步就停”while像“边走边看路看到终点线就停”do-while像“先迈出一步再抬头看还有没有路”。2.2 循环边界的坑 还是 今天最值的经验之一就是学会用“边界代入法”检查循环。所谓边界代入法就是写完之后拿几个关键数字在脑子里过一遍初始值、中间值、刚好到达边界的值、刚好越过边界的值。举个例子打印1到10int i 1; while (i 10) { // 错误示范 printf(%d , i); i; }这段代码只会打印1到9因为i等于10的时候条件已经不成立了。如果我把条件改成 i 10就能正常打印到10。但紧接着又有个陷阱如果初始值写成 int i 0;while (i 10) 会打印0到10一共11个数字多了一个0。平时写代码多一个少一个数也许不致命但笔试里经常喜欢在这种地方设计陷阱让你判断循环到底执行几次。从Day5开始养成边界代入的习惯后面能省很多事。2.3 do-while 的隐藏价值至少执行一次的业务场景我觉得do-while是三种循环里最容易被忽略、但面试时又最爱被问到的一个。它最大的特点是“无条件先执行一次循环体”。这种特征天然适合做输入校验。举个例子写一个猜数字游戏要求用户必须输入1到100之间的整数如果输错了就一直重来int guess; do { printf(请输入1~100之间的整数); scanf(%d, guess); } while (guess 1 || guess 100); printf(你输入的是 %d\n, guess);这个场景下do-while是最顺手的写法因为用户无论如何都要先输入一次输错了再说。如果换成while就得在循环外面先写一遍相同的输入代码或者在循环里做额外处理代码会变丑。所以每次拿不准该用哪个循环时先判断“这段逻辑最短必须执行几次”至少一次就考虑do-while可能是零次就考虑while或for。2.4 break与continue中断和跳过千万别搞反Day5之前我经常把break和continue混为一谈今天专门用一段代码把它们钉死了。简单说break是“直接结束整个循环”continue是“跳过本次循环剩下的部分直接进入下一次迭代”。for (int i 1; i 10; i) { if (i % 3 0) { continue; // 3的倍数直接跳过不打印 } if (i 7) { break; // 到7直接停不再继续 } printf(%d , i); }这段代码输出结果是“1 2 4 5”i等于3和6时被continue跳过i等于7时触发break直接退出循环所以8、9、10都没机会执行。我还踩过一个嵌套循环的坑在一个双重循环里写了break本以为能直接跳出外层结果发现它只跳出了内层循环外层还在跑导致输出了一大堆多余内容。这一点笔试面试特别爱问一定要记牢break只能跳出一层循环也就是离它最近的那层想跳出外层需要额外用标志变量或者把逻辑拆到函数里。3. 循环嵌套实战九九乘法表、打印菱形与第一次死循环3.1 九九乘法表外层管行内层管列循环嵌套是Day5的另一个大重点。最经典的入门题就是九九乘法表for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%-2d , j, i, i * j); } printf(\n); }这里有个口诀外层循环管行内层循环管列。i表示当前在第几行j表示这一行要打印多少列。因为乘法表是梯形结构所以内层循环的结束条件是 j i而不是 j 9。如果写成j 9输出就会变成矩形。我一开始写的时候经常忘记在每行结束后加 printf(\n)结果所有式子全挤在一行里屏幕上一长串数字根本没法看。这算是一个特别新手的问题但也提醒我写代码时要注意“节奏”每一行输出结束后要想清楚是否该换行。3.2 打印菱形大部分新手的第一道逻辑题如果说九九乘法表是“照猫画虎”那打印菱形就是第一次真正需要自己推导公式的题。我今天的任务是用循环打印出下面这个图案* *** ***** *** *总共5行前3行星号数量是1、3、5后2行是3、1。问题在于每一行前面还要打印空格空格数量分别是2、1、0、1、2。我第一次写的时候完全不知道从哪里下手后来发现一个管用的笨办法先用纸把每一行的空格数和星号数列出来找规律。假设总行数的一半是n这里n等于3那么上半部分第i行i从1开始的空格数是 n-i星号数是 2i-1。下半部分第i行反过来从 n-1 往下递减空格数仍然是 n-i星号数还是 2i-1。代码如下#include stdio.h int main(void) { int n 3; // 上半部分第1行到第3行 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); } // 下半部分第2行到第1行对称回去 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); } return 0; }这道题我卡了大概一个小时卡点不是语法而是“怎么把空格数量和循环变量挂上钩”。最后我强迫自己把推导过程写进注释里先写中文思路、再写公式、最后才写代码。这个顺序让我顿悟了编程很多时候是“先用自然语言想清楚再翻译成代码”。3.3 死循环排错printf大法入门今天最刺激的一刻是我亲手写出了一个死循环。当时我在写一个while累加的需求代码长这样int i 1; int sum 0; while (i 100) { sum i; } printf(%d\n, sum);运行之后终端直接卡住什么都不输出敲键盘也没反应程序像掉进了一个无底洞。我当时还不太敢确定是死循环第一反应是“我是不是把电脑搞坏了”。后来我冷静下来按了CtrlC强制终止程序然后开始排错。因为代码很简单我一眼就找到了问题循环体里没有写 ii的值永远停在1条件 i 100 永远为真所以程序永远跑不完。这个错误看起来很低级但它给了我一个特别重要的经验调试循环问题最直接的办法就是在循环体里加printf把关键变量的变化过程打印出来。这就是传说中的“printf大法”。我在循环里临时加了一行代码while (i 100) { printf(i %d\n, i); sum i; i i 1; }运行之后你能清楚看到变量i是怎么一步步变化的。如果看到i卡在一个值不动那大概率就是更新语句丢了或者写错了。printf大法虽然显得原始但它在任何环境都能用而且排查逻辑问题的效率非常高。对于新手来说与其花时间去学复杂的调试器不如先把printf用熟练。4. Day5练手题记录从“看得懂”到“写得出来”4.1 求100以内的素数第一道题是求100以内的素数。思路很朴素遍历2到100的每个数对每个数再遍历2到它本身之前的数判断它能不能被某个数整除。如果能整除就说明它不是素数。#include stdio.h int main(void) { for (int n 2; n 100; n) { int is_prime 1; for (int j 2; j n; j) { if (n % j 0) { is_prime 0; break; } } if (is_prime) { printf(%d , n); } } printf(\n); return 0; }这道题有几个值得记录的点。第一内层循环的j要从2开始不能从1开始因为任何数都能被1整除从1开始的话所有数都会被判成非素数。第二我一开始没用break导致每个数都傻傻地跑到j等于n-1才停效率很低。加上break之后只要发现能被整除就立刻结束当前判断。第三这里用了一个标志变量is_prime这是很多新手不太习惯的写法。它的作用就是“先假设它是素数一旦找到反例就标记为假”这个模式在后面很多题目里都会反复出现。后来我朋友提醒我判断素数其实不用循环到n只需要循环到sqrt(n)就行因为如果n有一个大于sqrt(n)的因子那它必然还有一个小于等于sqrt(n)的因子只要检查小的那一半就够了。这个优化让我第一次直观感受到“算法效率”的存在。4.2 十进制转二进制循环和数组的首次配合第二道题是十进制整数转二进制。这道题我第一次写的时候完全懵因为我压根不知道转换原理。后来查了资料才明白“除2取余逆序排列”这个方法比如把25不断除以2余数分别是1、0、0、1、1倒过来读就是11001也就是25的二进制。int n 25; int bin[32]; int idx 0; while (n 0) { bin[idx] n % 2; n / 2; idx; } for (int i idx - 1; i 0; i--) { printf(%d, bin[i]); } printf(\n);我第一次写的输出结果是错的原因是我直接用正序打印了bin数组得到的是“10011”正好和正确答案11001反过来。后来我想清楚一个关键点数组下标是从0开始递增的所以先算出来的余数会存在0号位置但它却是二进制数里最靠后的那一位真正输出时必须倒着来。这道题让我第一次体会到“数组和循环是绑定在一起的”。数组负责存数据循环负责控制存和取缺了谁都不行。这也让我对接下来的学习更有底了因为后面字符串、排序、查找本质上都是这套组合拳。4.3 斐波那契数列为后续递归学习埋个种子第三道题是输出斐波那契数列的前若干项。斐波那契数列的定义是前两项是0和1从第三项开始每一项等于前两项之和0、1、1、2、3、5、8、13……我用循环写出了最直接的迭代版本int a 0, b 1; printf(%d %d , a, b); for (int i 0; i 8; i) { int next a b; printf(%d , next); a b; b next; }核心逻辑就是三个变量的滚动更新把a移到b的位置把b移到next的位置。我第一次写的时候少写了 a b;结果数列变成了0、1、0、1、0逻辑完全崩掉。后来我把整个更新过程在纸上画了一遍才彻底明白“三个变量轮换”是怎么回事。至于为什么只输出前10项左右是因为我不想把整篇内容写太长更重要的是掌握迭代思路。后面学到递归时斐波那契数列还会再出现一次那时候就要对比递归和循环各自的性能和风险了。4.4 复盘方法代码仓库加错题复写三道题全部跑通之后我并没有马上开始学新东西而是做了一次复盘。我把今天的代码整理到了自己的代码仓库里每个练习单独建一个文件夹除了.c文件之外还加了一个README记录三件事这道题的思路是什么、我在哪里卡住了、最终是怎么解决的。这一步看似费时间但之后复习会非常省力。除此之外我还给自己立了一条规矩错题需要复写。所谓复写就是过两天之后我合上所有答案像考试一样把那道题重新写一遍。能写出来才算真正掌握写不出来就说明当时只是“看懂了”不是“会写了”。Day5的累计工程不算大但这条习惯我必须从第一天开始就固定下来因为在秋招面前光靠看是撑不住笔试的。5. 秋招笔试与面试中的循环考点Day5如何转化为战斗力5.1 笔试常考的循环变式与复杂度很多人觉得基础语法和笔试离得很远实际上笔试第一轮就有大量直接考察循环的题。最典型的一类就是“循环执行次数计算”。比如面试官给你一段嵌套循环int count 0; for (int i 0; i n; i) { for (int j 0; j i; j) { count; } }问你count最后等于多少。答案是 n*(n-1)/2。这种题表面考循环实际考两个东西你有没有真的理解循环的“范围”以及你会不会算时间复杂度。另一类常见题是给你一段循环代码让你写出输出内容。这类题通常会在边界条件上设陷阱比如while条件是i n还是i n初始值从0开始还是从1开始。Day5我练的边界代入法在这个场景下特别管用。还有一类题涉及循环变量每次翻倍的情况比如 for (int i 1; i n; i * 2)那它的复杂度就是O(logn)而不是看起来的O(n)。我现在还不会什么高级算法但已经能看懂一个道理任何花哨的算法题最后落到执行层面都是循环在驱动复杂度也是在数循环的层数和次数。所以Day5花一天时间把循环吃透我认为非常值得。5.2 面试问答break、continue和do-while的送分陷阱面试环节里循环相关的题往往是送分题但也是“送命题”——因为太基础答错会特别尴尬。我给自己整理了一份面试卡片专门记这些容易翻车的点for(;;)是什么含义它等价于while(1)表示无限循环通常配合break使用。break和continue的区别break结束整个循环continue跳过本次迭代进入下一次。do-while至少执行几次至少一次因为先执行后判断。break在嵌套循环中能跳出外层吗不能只能跳出离它最近的那一层。i和i在循环里有什么区别单独作为更新语句时没有区别但如果写在表达式里结果就会不同。这些考点在Day5的知识范围内我现在就可以先背下来。很多同学喜欢追难题、追新框架但我个人觉得秋招基础题如果丢分才是真的可惜。把循环这些基础点答到滴水不漏至少能让面试官觉得“这个人是靠谱的”。5.3 基础语法如何变成简历上的亮点还有一个我很想分享的观点Day5学到的东西虽然基础但照样能变成“看得见的成果”。我准备在后续几天把这些语法点组合成几个命令行小工具比如猜数字游戏、乘法表生成器、简易计算器菜单。它们都不复杂但能说明你真的动手写过代码比起空泛地写“熟悉C语言”要具体得多。我也已经把Day1到Day5的所有代码放进了代码仓库并且保持了每次提交都有清晰的说明。这件事对秋招的价值不在于这些代码本身多牛而在于它证明了你有一个持续学习和整理的轨迹。等到真正开始投简历的时候这些积累可以直接变成项目描述的一部分甚至面试时可以直接拿出来演示。对菜鸟来说这种“看得见的进度”本身就能缓解焦虑。6. 菜鸟自学的三个心态救生圈写给同样焦虑的伙伴6.1 拒绝“收藏夹学习”输出才是最有效的学习Day5我最大的收获其实不是循环语法而是明白了“看懂了”和“会写了”之间隔着十万八千里。我以前特别喜欢看视频教程看得时候觉得“好简单、我全会”但一合上电脑就什么都写不出来。今天我改成了一种笨办法学一个知识点就必须写一个能运行的程序而且写完还要讲给别人听。我跟你讲如果你能把一道题的思路用大白话讲明白那你才是真的理解了。这也是为什么我坚持把Day5的内容写成一篇这么长的记录——写作就是输出输出会逼着我整理混乱的思路。收藏无数攻略和模板不会让你变强只有自己亲手敲出来的代码才会长在脑子里。6.2 别怕报错把编译错误当成导航今天是报错最密集的一天我粗略数了一下光是编译错误就处理了二十几个更别提那些运行起来逻辑不对的bug。一开始我看到屏幕上红色的error就心慌总觉得自己天赋不行。后来我发现C语言的编译器其实是一个非常耐心的助手它会在错误信息里告诉你是哪一行有问题、缺了什么符号、甚至直接给出“expected ;”这类提示。我调整心态之后把编译报错当成程序的“导航语音”来读它说第几行有问题我就去那一行附近找看不明白就先往前一行看经常是上一行的括号或者分号忘了写。这个方法看起来平平无奇但真的能大幅减少挫败感。你越怕报错报错越容易变成一堵墙你把它当成朋友它反而会帮你快速定位问题。6.3 进度焦虑怎么办定节奏不攀比学习群里总有人晒进度今天已经刷了第几章、明天已经写了多少个项目。我看了确实会焦虑觉得自己Day5还在捣鼓循环是不是太慢了。后来我想明白一件事秋招比的是最终谁能写出靠谱的代码不是比谁第5天学得快。我给自己的要求是每天至少写通一个练习状态好多写一点状态差也能保住下限。这样即使今天很忙也不会断掉编码手感。我今天学到的东西不多但每一个都是亲手敲出来、跑通的。这个节奏让我很踏实因为它保证了我每天都有真实产出而不是在“看教程”的自我感动里消磨时间。把焦虑转化成代码是我目前能想到的最好解药。Day5结束了我对C语言的掌控感比昨天高了不少。我最大的体会是C语言不是看会的也不是背会的而是在一次次编译报错、死循环、逻辑翻转里“错”出来的。每解决一个报错我对程序的边界就更清楚一分这种不断积累的掌控感才是冲击秋招真正的底气。最后分享一个小技巧初学者阶段的练习代码尽量写成“一个main函数就能跑”的小片段先别急着搞多文件工程。这样后期复习的时候只要双击打开、编译、运行就能立刻回想起当时的思路。明天我准备开始学数组继续把循环跟数组结合起来练。Day5的菜鸟已经比Day1的菜鸟强了不少这就够了。