news 2026/10/20 0:09:41

C语言循环语句详解:for、while、do-while的选型与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言循环语句详解:for、while、do-while的选型与避坑指南

聊C语言的循环语句。for、while、do-while这仨兄弟,几乎每个C程序里都有它们的身影,不管你是刚接触编程的学生,还是准备计算机等级考试的考生,又或者是回头复习基础的开发者,循环这块儿都绕不过去。很多人学循环的时候,语法背得滚瓜烂熟,一到写代码就翻车,死循环、漏循环、边界错位,各种问题层出不穷。这篇文章不打算讲教科书上那套,而是从实际写代码的角度,把循环语句的底层逻辑、选型思路、避坑经验一次说透。


1. 循环语句的整体设计与选择逻辑

1.1 为什么需要循环:重复做事是程序的本能

先想一个问题:让你打印1到100,你会怎么写?老老实实写100行printf?那如果打印1到10000呢?这时候你就明白循环的意义了——把重复性的操作交给计算机,是编程最基本的能力。

但循环不只是“重复”这么简单,它背后藏着两个核心要素。第一是重复执行某段代码,第二是每次执行时状态在变化。这两个要素缺一不可。看这个例子:

int i = 1; while (i <= 100) { printf("%d\n", i); i++; }

这段代码里,printf是重复的部分,i++是状态变化的部分。如果只有重复没有状态变化,那循环就成了永不停歇的复读机,这就是死循环的根源。理解了这一点,你在写任何循环之前,都应该先问自己两个问题:我要重复做什么?每做一次,什么东西在变?

很多初学者上来就写代码,想到哪儿写到哪儿,结果循环条件、变量更新、循环体三者各写各的,互相矛盾。正确做法是先想清楚这两个问题,再动手。我把这个过程叫做“循环三要素”:初始化、条件、更新。任何循环,不管用哪种语法写,本质上都是这三个要素的排列组合。

1.2 三种循环结构的一次性对照

C语言提供了三种循环结构,很多人纠结“到底用哪个”,其实你只要搞清楚它们的执行次序,选择就变成了一件很自然的事。

循环类型语法骨架执行次序典型场景
forfor (初始化; 条件; 更新)先初始化,再判断条件,执行循环体,执行更新,再判断条件循环次数已知或可通过变量确定
whilewhile (条件) { 循环体 }先判断条件,为真才执行循环体次数不确定,依靠条件终止
do-whiledo { 循环体 } while (条件);先执行循环体,再判断条件,条件为真则继续至少需要执行一次的场景

光看表可能还不够直观。for和while在本质上是等价的,for只是把初始化、条件、更新这三大件集中写在了同一行,结构更紧凑。do-while则是三个里面最特殊的一个——它先斩后奏,不管条件是否成立,循环体至少跑一次。

我用一个特别生活化的例子来帮大家记。你把循环想象成“检查手机有没有新消息”:while是先看一眼有没有消息,有就看,没有就睡觉;do-while是闭着眼睛先刷一下朋友圈,刷完了再告诉自己“哦今天没有新内容”或者“哎还真有”,然后继续刷。前者是“看了看再决定做不做”,后者是“先做了再说”。绝大部分日常循环属于前者,只有少数场景(比如菜单显示、输入校验)必须用后者。

1.3 选型原则:不纠结,按场景走

根据我写代码的习惯,选型其实可以压缩成三条非常简单的规则:

  • 知道要循环多少次,或者次数能用一个表达式算出来,用for。比如遍历数组的N个元素,for (i = 0; i < N; i++),一眼就能看出循环范围。
  • 不知道次数,但知道什么时候该停,用while。典型就是读文件或读用户输入,你没法预估对方会给多少数据,只能靠“读到末尾”或“收到退出指令”来判断。
  • 不管条件成不成立,至少得执行一次的业务逻辑,用do-while。比如用户菜单:先显示菜单,再根据用户选择判断是否继续,你总得先把菜单显示出来,才谈得上“用户可能选退出”。

这个选型规则帮我省掉了大量纠结的时间。很多人学循环的时候有个误区,总觉得一定要把三种全用上才算学会,其实不是。三种循环是工具,不是竞赛项目,哪种顺手用哪种。真正的高手写的代码,往往大量使用for,因为它的结构最规范、意图最清晰。而while和do-while则用在那些确实更贴合的特定场景里。


2. 三种循环结构的核心细节与边界

2.1 for循环:三大件的执行顺序是命门

for循环是所有循环里最容易“看懂”的,但也是出错后最难查的,原因就在于它的三大件写在同一行,执行顺序容易被忽略。我用一个例子把它彻底拆开:

for (int i = 0; i < 10; i++) { printf("i = %d\n", i); }

这段程序的执行顺序是:先执行int i = 0(初始化,只执行一次),然后判断i < 10,成立则进入循环体打印,打印完成后执行i++,再回到条件判断。注意,i++不是在循环体内执行的,而是在循环体的末尾执行的。这看起来是小事,但影响很大。比如你写:

for (int i = 0; i < 10; i++) { if (i == 5) { continue; } printf("i = %d\n", i); }

continue会跳过printf,但不会跳过i++,因为i++在语法结构上是for的更新部分,不受continue影响。这一点很多人会弄混——以为continue跳过了本轮所有代码,结果i没有更新,死循环了。实际上,在for循环中,continue只是跳到更新表达式那里,该更新的还是会更新。

再说一个高频问题:for里三个部分其实都可以省略,但分号不能省略。for (;;)是个死循环,等价于while (1)。我见过有人把for (;;)写成for (),编译直接报错。这种写法偶尔在嵌入式编程或驱动代码里见到,但在应用层写业务逻辑时,我建议还是老老实实把三大件写全,代码的可读性比那点“偷懒”重要得多。

还有一个细节是for循环的初始化表达式可以用逗号表达式同时做多件事,比如for (i = 0, j = n - 1; i < j; i++, j--),这个写法在双指针算法里特别常见。但刚入门的时候我不推荐你用这种花活,等熟悉了基础流程再去用,否则容易把自己绕晕。

2.2 while与do-while:判断时机决定一切

while和do-while的区别我在前面提到过,核心就是判断时机。但真实的坑往往藏在“什么时候该用do-while”上。

举个例子,你要写一个“密码输入校验”的程序:用户输入密码,不对就继续输,直到对为止。这里有一个隐含需求——用户至少得输入一次密码,因为程序不可能在一开始就知道用户密码对不对,必须先拿到输入才能判断。这种情况下用do-while是最自然的:

char pwd[20]; do { printf("请输入密码:"); scanf("%s", pwd); } while (strcmp(pwd, "123456") != 0);

如果用while写,你得先把密码变量初始化成一个“必然不等于正确密码”的初始值,才能进入循环,这样做既不优雅又容易出问题。do-while把“至少执行一次”这个语义直接写进了语法里,恰好对应了这类真实需求。

但要注意,do-while有个容易忽略的点,就是结尾的分号。很多初学者写完do { ... } while (条件)之后忘了加分号,编译直接报错。其实这个分号在语法上是必须的,它不是普通语句的分号,而是do-while语句的一部分。编译器报错的时候给的提示往往不太明显,这时候你就得有这个意识:去检查do-while尾部。

再说while。while有个特点,它没有初始化部分,所以循环变量的初始化必须在进入while之前单独写好。这意味着如果循环体内的某些条件会改变循环变量,你就得格外小心——如果初始化出了问题,循环体一次都不会执行。这个特性在某些情境下反而是优点,比如“当还有数据就继续读”的模型就很适合while:

int ch; while ((ch = getchar()) != EOF) { putchar(ch); }

这里的技巧是:把读取操作直接放在条件判断里,先让getchar()执行,把返回值赋给ch,再拿ch和EOF比较。这既完成了数据读取,又完成了条件判断,一举两得。这种写法很常见,但我建议初学者先拆开写:

int ch = getchar(); while (ch != EOF) { putchar(ch); ch = getchar(); }

逻辑更直白,虽然代码长一点,但不容易出错。

2.3 break和continue:控制流的两个转向开关

break和continue是循环体内部的两个“转向开关”。break是立即跳出整个循环,continue是跳过本轮剩余代码,直接进入下一轮。这个区别嘴上说说容易,写起来就有人犯糊涂。

特别要注意的是,break只能跳出一层循环。如果你在一个多层嵌套循环里写了个break,它只会跳出最内层的那一层,外层循环该怎么走还是怎么走。这个问题我在后面讲嵌套循环时会再展开,但在这里先给个预告——很多人写二维数组遍历时想用break提前结束全部搜索,结果只跳出了内层循环,外层还在跑,导致逻辑错误。

continue则要注意使用位置。在while循环里,如果你不小心把continue放在循环变量的更新语句前面,那continue一执行,更新语句就被跳过了,循环变量不再变化,死循环就来了。看这段代码:

int i = 0; while (i < 10) { if (i == 5) { continue; // 死循环! } printf("%d\n", i); i++; }

当i等于5时,continue直接跳到条件判断,i++永远没有机会执行,于是i永远定格在5,循环永远不退出。这种bug特别隐蔽,因为程序不会崩溃,也不会立刻暴露问题,但程序就像“卡住”了一样。排查方法我会在第4章详细讲,这里先提醒一句:但凡在while循环里用了continue,先检查更新语句有没有可能被跳过。

2.4 循环变量:类型、边界与符号的隐形陷阱

循环变量不是随便挑个int就完事了,它牵扯的问题相当多。一个最经典的大坑是unsigned和>= 0组合。看这段代码:

unsigned int i; for (i = 10; i >= 0; i--) { printf("%u\n", i); }

你以为它会打印10到0,实际上它是无限循环。因为i是无符号整数,当i从0减1时,它不会变成-1,而是绕回4294967295(32位无符号最大值),条件i >= 0永远成立。这个坑踩进去的时候,很多人对着屏幕怀疑人生。

解决办法很简单:要么循环变量用signed int,要么把条件写成i > 0然后内部打印时再处理。我在实际写代码时,循环变量一律优先用signed int,只有在明确需要非负语义时(比如数组下标)才考虑size_t,而且使用size_t时绝对不用反向递减的写法。

还有一个常见问题是边界究竟是< n还是<= n - 1。我记得刚学C语言时特别纠结这个问题,后来发现业界惯例是一律用半开区间[0, n),即for (i = 0; i < n; i++)。这个习惯的出发点很多:数组下标是从0开始的,最后一个合法下标是n - 1,循环条件写i < n正好覆盖了0到n - 1的全部合法下标,逻辑上无歧义。而如果你用i <= n - 1,不但要额外思考n = 0时的边界情况,还多了一次计算,没有任何好处。


3. 实操过程:从零开始搭一个完整循环

3.1 完整拆解一个经典案例:从1加到100

理论讲再多,不如写一个典型例子。我们来解决一道最经典的问题:计算从1加到100的和。

第一步,明确循环三要素。重复做的事是“把当前的数加到累加器里”,状态变化是“当前的数从1递增到100”。所以初始化是int sum = 0和int i = 1,条件是i <= 100,更新是i++。

第二步,选择循环结构。循环次数是明确的100次,直接用for:

#include <stdio.h> int main(void) { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } printf("sum = %d\n", sum); return 0; }

运行结果当然是5050。这个例子虽然简单,但它把循环的骨架完整地展示了一遍。我建议初学者拿到任何循环需求,都先在纸上或者注释里把三个要素写出来,再动手敲代码。这个习惯一旦养成,能少写一半的bug。

这里我要额外强调一个细节:循环体内的代码要尽量少做无关的事。上面的循环体只有一句sum += i,干净利落。有些人喜欢把printf放进循环体里打印每个中间值,方便调试,这可以理解,但正式写逻辑时不要这么干——循环体每执行一次,printf就跑一次,100次循环就是100次输出,既拖慢程序又把日志刷得乱七八糟。调试用临时打印没问题,记得最后删掉。

3.2 循环次数的口算公式

写循环最怕边界搞错,比如想循环10次却实际跑了9次或者11次。这里分享一个我常用的口算方法。

假设循环变量初始值是start,每次增加step,条件是变量< end或者<= end,循环次数可以这么算:

  • 条件为i < end时,循环次数 = ceil((end - start) / step),其中ceil是向上取整。
  • 条件为i <= end时,循环次数 = floor((end - start) / step) + 1。

举个例子,for (i = 0; i < 10; i++),次数 = ceil((10 - 0) / 1) = 10次。for (i = 0; i <= 10; i++),次数 = floor((10 - 0) / 1) + 1 = 11次。两者差了1次,而这点差别恰恰是很多bug的来源。

你可能会想,这么简单的循环还需要算吗?确实,步长为1且从0开始的情况不复杂,但一旦步长不是1,或者起始值不是0,口算公式就很有用了。比如for (i = 3; i <= 15; i += 2),次数 = floor((15 - 3) / 2) + 1 = 7次。你要是靠数的话,很容易数晕。

还有一个容易被忽略的点:循环变量的更新步长和边界条件的单位必须一致。如果步长是2但条件还是按步长1的心智来写,就会多算或少算。我写循环前会先用笔头把次数算一遍,花不了10秒钟,但能省下调试时的一小时。

3.3 循环体内的代码组织:把不变的东西挪出去

实操中你会发现,同样一个循环,不同的人写出来的效率天差地别。一个常见的优化点是循环不变量的提取。看下面的代码:

for (i = 0; i < n; i++) { double factor = computeFactor(); // 每次循环都重新计算 result[i] = data[i] * factor; }

如果computeFactor()的结果在循环过程中不会变,那把它放进循环里就是浪费。正确做法是提到循环外面:

double factor = computeFactor(); for (i = 0; i < n; i++) { result[i] = data[i] * factor; }

这一改动对可读性的帮助比性能还大——它明确告诉读代码的人:“factor在循环期间是常量”,减少了理解的成本。我第一次在实践中这样改代码时,发现运行时间从肉眼可见的卡顿变成了瞬间完成,那时候我才真正理解“循环体内的每一行代码都会被放大N倍”这句话的分量。

另一个组织原则是不要在循环体内改变循环变量的值(除非你有非常明确的原因)。循环变量是循环的控制中枢,在循环体内随意修改它,会让循环次数变得不可预测。有些人写查找代码时喜欢在找到目标后把i改成n来制造循环结束,这种写法虽然能跑,但可读性很差,也让循环的“契约”变得模糊。更好的做法是用break,语义清晰。


4. 常见问题与排查技巧实录

4.1 死循环:最常见的C语言翻车现场

死循环大概是C语言初学者遇到的第一个“看似睡觉实则崩溃”的问题。代码没有蹦出任何错误,控制台也不输出东西,程序像被定格了一样,其实它正在疯狂地做无用功。

排查死循环,我的套路是三步走:

  1. 先看条件是否可能永远为真。比如while (i >= 0)配合无符号整型就会永远为真。把条件两边列出来,逐个代入几个关键值,判断有没有“出口”。
  2. 再看循环体内是否更新了关键变量。continue跳过更新、更新语句写在条件分支里、或者干脆忘了写更新,都是常见原因。
  3. 最后看循环体的花括号是否匹配。有时候你本以为某行代码在循环内,实际上它在循环外,导致条件里的变量根本没被更新。

我调过一个印象很深的bug,同事写的代码长这样:

int i = 0; while (i < 100); { printf("%d\n", i); i++; }

发现了吗?while (i < 100);后面多了一个分号。这个分号让while的空循环体被执行了,而i++所在的代码块在while之外,根本不会执行。于是程序直接卡在while的空转里。这种一个分号引发的血案,在C语言里实在太常见了。排查时看到while后面紧跟分号,就要格外警惕。

4.2 循环变量溢出与类型转换

循环变量的溢出不像死循环那么明显,但它会造成非常诡异的运行结果。典型场景出现在循环次数特别多,或者循环变量被用于累加的时候。

看这个例子:

int i; int sum = 0; for (i = 0; i < 50000; i++) { sum += i; }

int在多数平台上是32位有符号整数,最大值为2147483647。如果循环次数和累加值的乘积接近或超过这个上限,sum就会溢出,出现“负的总和”这种违背直觉的结果。这个问题的根源是int的范围有限,而你们计算的目标超过了它。

解决方案很简单:预估你的数值范围,选择long、long long甚至unsigned long long。对于需要精确控制范围的场景,可以在循环里加一个保护判断:

long long sum = 0; for (int i = 0; i < 50000; i++) { sum += i; }

另一个和类型相关的坑是隐式类型转换。比如循环条件里混合了int和unsigned int,编译器会隐式地把int转换成unsigned int再比较,这可能导致比较结果和你以为的不一样。最简单的规避办法就是:循环里不要混用有符号和无符号类型,需要比大小就统一用同一种类型。

4.3 边界判断错误:少一次、多一次的经典失误

循环少跑一次或多跑一次,是最容易让程序“看起来对但实际错”的问题。典型例子是数组遍历漏掉了最后一个元素。看这个代码:

int arr[5] = {1, 2, 3, 4, 5}; for (int i = 0; i <= 5; i++) { // 越界!最后一个合法下标是4 printf("%d\n", arr[i]); }

数组长度是5,合法下标是0到4,但你用i <= 5,当i = 5时访问arr[5],这已经越界了。C语言不像某些高级语言会在数组越界时抛出异常,它只会去读取内存中紧挨着数组的未知数据,可能打印出一个莫名其妙的值,也可能直接导致程序崩溃。这类bug调试起来非常头疼,因为它不像死循环那样“一眼可见”。

我的经验是:写数组遍历时,条件一律用i < 数组长度,绝不用i <= 数组长度。这是C语言社区里非常基础但极其重要的习惯,具备这个意识的人写出的代码会少很多隐藏问题。

4.4 条件中的赋值与比较混淆

=和==的混淆,是C语言界的“传世经典”。一个等号是赋值,两个等号是比较。条件里如果是if (i = 5),这不是在判断i是否为5,而是把5赋值给i。在C语言中,赋值表达式的值是赋进去的值,所以if (i = 5)永远是“真”。这在循环里会导致死循环或者退出条件永不满足。

我还见过更隐蔽的写法:

while (c = getchar() != EOF) { // ... }

这里因为运算符优先级的关系,实际执行的是c = (getchar() != EOF),先把比较结果赋给c,c变成了0或1,而不是你想要的字符。要正确实现“先赋值再比较”,必须加括号:while ((c = getchar()) != EOF)。括号一丢,逻辑彻底变了。类似的问题在写条件表达式时一定要警惕。

为了防这类问题,有些编译器在开启警告(-Wall)时会提示“建议在赋值表达式外加括号”,建议大家编译时把警告选项打开,让编译器帮你排查这类肉眼难查的问题。


5. 嵌套循环与真实场景综合应用

5.1 嵌套循环:二维数据遍历的基本功

嵌套循环就是循环套循环,外层循环每执行一轮,内层循环就要完整地执行一遍。二维数组的遍历、乘法表的打印、矩阵运算,这些都离不开嵌套循环。

看一个打印乘法表的经典例子:

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取1时,内层j从1到1,打印1行;外层i取2时,内层j从1到2,打印2行……所以最终打印出来的是一个梯形结构的乘法表。内层的条件j <= i是关键,它利用外层循环变量的当前值来动态限制内层的循环范围,这种“外层控制行、内层控制列”的模式是嵌套循环最常见的设计思路。

写嵌套循环时,我强烈建议给循环变量起有意义的名字,比如用row、col或者i、j。最好别都用i,虽然C语言允许内层和外层使用同名变量(因为它们在不同的作用域,内层会遮蔽外层),但这会让代码读起来一脸懵。坚持“外层i、内层j、再内层k”的传统约定,在简单的二维场景里没问题,再深层的循环就要考虑用更语义化的命名了。

5.2 经典算法里的循环应用:素数判断与冒泡排序

循环在实际算法中的应用花样繁多,但万变不离其宗。拿判断一个数是否是素数来说,核心逻辑就是从2开始试着除到它的平方根,一旦发现能整除,就说明不是素数。这里有一个关键的优化点:只需要除到sqrt(n)而不是n,因为如果n有一个大于sqrt(n)的因子,那必然有一个小于sqrt(n)的因子与之对应。

int is_prime(int n) { if (n <= 1) { return 0; } for (int i = 2; i * i <= n; i++) { if (n % i == 0) { return 0; } } return 1; }

这个循环里有一个很值得学习的写法:用i * i <= n代替i <= sqrt(n)。一方面避免了调用数学库函数,另一方面也避免了浮点数比较的潜在精度问题。在循环条件里直接做乘法,每次都要算一次i * i,很多人会担心损耗性能。实际上现在的编译器会在开启优化时识别这类模式,而且即使不优化,i * i的整数乘法也远比sqrt函数调用快。所以在循环里能用整数运算解决的问题,就不要引入浮点运算。

再看冒泡排序。它的核心思想是反复比较相邻元素,如果顺序不对就交换,每一轮都能把当前未排序部分的最大值“冒泡”到末尾。代码里必然是嵌套循环:

for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } }

内层的j < n - 1 - i就是那个“每一轮少比较一次”的细节来源——因为经过i轮冒泡,数组末尾的i个元素已经排好序了,不需要再参与比较。这个写法是冒泡排序的核心优化点。很多教材直接写j < n - 1,功能上也对,但既然理解了原理,就应该把边界写准,既减少无效比较,也体现了对算法本身的理解深度。

5.3 循环的复杂度意识:从O(n²)到O(n)的思考

嵌套循环最容易带来性能问题。两个10000次的循环嵌套在一起,就是一亿次操作,在普通PC上也要跑一小会儿。所以写循环的时候要有基本的复杂度意识。

随着循环层数增加,复杂度会指数级增长。单层循环通常是O(n),双层循环通常是O(n²),三层循环是O(n³)。当你在一个函数里看到三层以上的嵌套循环,几乎总要想办法优化,比如用哈希表把内层查找从O(n)降为O(1),或者用双指针把O(n²)降为O(n)。

但注意,复杂度意识不等于过早优化。初学者写代码,正确性和可读性永远排在第一位。先把逻辑对了,如果后续发现性能确实有瓶颈,再回来优化对应的循环。我接手过很多“过度优化”的代码,为了省几次循环写出了一堆难懂的位运算和循环展开,结果维护成本极高。所以我建议:循环要写得清晰,同时知道它为什么够用或者不够用。


6. 循环的性能优化与长期编码习惯

6.1 循环内重复计算:把不变的东西提出去

第3章里提到了循环不变量的提取,这里再展开讲一些具体操作。循环体内的计算开销会被执行次数放大,所以循环内的每一行代码,都值得你用“这笔计算是不是每次都需要”的眼光重新审视。

看这个例子:

for (int i = 0; i < strlen(str); i++) { // 处理字符 }

这段代码有个隐蔽的性能问题:strlen(str)在每次循环条件判断时都会被重新调用。如果字符串长度是10000,那strlen就要跑10000次,而字符串长度在循环过程中是不变的。正确的写法是:

size_t len = strlen(str); for (size_t i = 0; i < len; i++) { // 处理字符 }

类似的还有math.h的pow、sqrt等函数,如果参数在循环里不变,就提到外面。我实践下来,这类优化的效果立竿见影,尤其当循环次数大时,可能把耗时的操作从10秒缩短到0.1秒。

不过我要提醒一点:提取的前提是确定这些值在循环过程中不会变化。如果你提取了一个可变的量,循环里逻辑就错了。所以提取之前先想清楚“这个值和循环变量有没有关系”,有关系就留在循环内,没关系就提出来。

6.2 编译器优化与代码可读性的平衡

现代编译器在开优化选项后,会帮你做很多循环层面的优化,比如循环展开、向量化、强度削减等。这就产生了一个有趣的问题:既然编译器会优化,是不是写循环时可以放飞自我?

恰恰相反。编译器优化的前提是它能分析出代码的语义。如果你的循环写得逻辑混乱、有隐藏的副作用,编译器很难做激进的优化。而且生产环境并不总是开着最高优化级别运行的,尤其是调试时经常需要关闭优化,这时候代码本身的循环效率就直接影响程序表现。

所以在写循环时,我的原则是:先写人看得懂的,再考虑机器跑得快的。以下几条算是我个人比较看重的长期习惯:

  • 循环变量尽量在for里定义,把生命周期限制在循环内,避免代码其他地方误用。
  • 条件判断避免复杂的函数调用,优先用简单比较。
  • 循环体内不要写无关的IO操作,打印输出放在循环外。
  • 边界条件用常量或具名变量,不要用魔法数字。

比如遍历数组时,不要默认写死100,而是用一个明确的常量ARRAY_SIZE:

#define ARRAY_SIZE 100 for (int i = 0; i < ARRAY_SIZE; i++) { // ... }

这样后续修改数组大小时,只需要改一处定义,循环边界会自动适配。

6.3 编码中的循环自检清单

最后分享一个我每次写完循环都会默默过一遍的自检清单,排查常见隐患:

检查项提问方式隐患示例
边界条件循环次数是否符合预期?最后一个值能不能访问?i <= n导致越界访问
类型选择循环变量有无符号?会不会溢出?unsigned配合>= 0死循环
循环变量更新每轮都能更新吗?continue会跳过更新吗?更新语句在continue之后被跳过
花括号匹配循环体的范围是否如你所想?while后多分号导致空循环体
运算符用了==还是=?条件里误用赋值
循环内重计算有没有可以提到循环外的常量?每次循环都调用strlen
break层级只想跳出内层还是跳出全部?嵌套循环里break只跳一层

这个清单花不了30秒,但能帮我拦截掉相当一部分循环类bug。特别是面试或者比赛写代码时,循环写完之后快速过一遍清单,能避免很多低级的提交失败。


写了这么多,我自己最深的感受是:循环语句的知识点并不难,难的是写完循环之后那份“边界感”。所谓边界感,就是清楚循环从哪里开始、到哪里结束、中间如何处理临界情况。我见过太多代码,语法完全正确,程序也能跑,但就是循环的边界判断得不清不楚,导致线上问题不断。写循环时多问自己一句“最后一次循环发生在什么时候”,很多问题都能在刚动手的那刻被你挡在门外。循环是程序最基础的节拍器,把节拍打准了,后面的路就稳了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 0:09:12

土壤热通量传感器:从经验到数据的棚室种植热量管理指南

种了十几年大棚的朋友&#xff0c;这两年见面聊得最多的不是产量&#xff0c;而是手机里那条实时跳动的曲线——土壤热通量传感器传回来的数据。以前判断地温够不够、要不要盖棚、浇水会不会伤根&#xff0c;全靠手摸、眼看、凭老经验。现在一台传感器装在地里&#xff0c;土壤…

作者头像 李华
网站建设 2026/10/20 0:09:12

LeetCode 55 跳跃游戏:从暴力递归到贪心算法的思维进阶

这道题是 LeetCode 55 跳跃游戏&#xff0c;我几乎每次和同行聊贪心算法都会把它拎出来当第一个案例&#xff1a;题面一句话就能读完&#xff0c;难点却全藏在“能不能到达”这五个字里。给定一个非负整数数组nums&#xff0c;每个nums[i]表示你在下标i处最多可以向前跳跃的长度…

作者头像 李华
网站建设 2026/10/16 12:24:22

AI助手长期记忆方案:claude-mem 原理、部署与踩坑记录

作为一个每天要开十几个AI对话窗口干活的人&#xff0c;我最近越来越受不了同一个问题&#xff1a;每开一个新会话&#xff0c;助手就完全不记得上一轮我们聊到哪了。claude-mem这个名字第一次出现在我视野里时&#xff0c;我正在为一个跨周项目第十几次给AI助手重新解释项目背…

作者头像 李华
网站建设 2026/10/14 11:11:50

Linux与Windows双系统安装避坑指南:UEFI、启动修复与硬件兼容

1. 为什么双系统不是“装两个系统”那么简单——从真实踩坑现场说起“Linux与Windows双系统安装完整指南”这个标题&#xff0c;听起来像一份说明书&#xff0c;但实际操作中&#xff0c;它更像一场精密的外科手术&#xff1a;你得在不伤及现有Windows心脏的前提下&#xff0c;…

作者头像 李华
网站建设 2026/10/15 5:37:26

AnyPS5不是模拟器:跨平台PS5能力抽象与混合架构实践

1. 项目概述&#xff1a;一个被误读的命名现象&#xff0c;背后是跨平台兼容性探索的真实需求“AnyPS5”这个词最近在多个技术社区和硬件讨论区频繁出现&#xff0c;但翻遍主流平台、官方渠道、开源仓库甚至专利数据库&#xff0c;都找不到一个叫“AnyPS5”的正式产品、软件项目…

作者头像 李华
网站建设 2026/10/16 11:00:33

DeepSeek昇腾迁移实战:AI应用分层拆解与工程适配指南

1. 先搞清楚"迁移"到底在迁什么DeepSeek的模型权重和昇腾&#xff08;Ascend&#xff09;相关组件陆续开源之后&#xff0c;我身边不少做AI应用的朋友第一反应是"是不是可以把整套系统搬过去"。这个想法很自然&#xff0c;但实际操作下来会发现&#xff0c…

作者头像 李华