10、循环结构之for循环:for循环的语法、执行流程,循环嵌套,死循环

说实话,循环是C语言里最常用的结构之一。我做了十几年嵌入式开发,几乎每个项目都离不开它。不管是驱动开发、数据处理,还是状态机实现,for循环都是我的得力助手。

今天咱们就好好聊聊for循环。别小看它,用好了能写出优雅高效的代码,用不好嘛...嗯,我见过不少新手在循环上栽跟头。

10.1 for循环的语法结构

先看最基础的样子:

for (初始化表达式; 循环条件; 更新表达式) {
    // 循环体
}

三个部分,用分号隔开。我习惯这么理解:

  • 初始化表达式:循环开始前执行一次,通常用来定义循环变量
  • 循环条件:每次循环开始前判断,为真就继续,为假就退出
  • 更新表达式:每次循环体执行完后执行,通常用来改变循环变量

举个最简单的例子:

for (int i = 0; i < 5; i++) {
    printf("第%d次循环\n", i + 1);
}

这个代码会输出5行。i从0开始,每次加1,直到i等于5时条件不成立,循环结束。

我的习惯:在嵌入式开发中,我习惯把循环变量声明为int类型。虽然unsigned在某些场景下更安全,但int在边界判断时更直观,不容易出bug。

10.2 执行流程详解

for循环的执行顺序,我画了张图,你一看就明白:

for循环执行流程图 ① 初始化表达式 ② 循环条件 ③ 循环体 ④ 更新表达式 ⑤ 退出循环

流程其实很简单:

  1. 先执行初始化(只做一次)
  2. 判断条件是否成立
  3. 成立就执行循环体
  4. 执行更新表达式
  5. 回到第2步,继续判断

条件不成立时,直接跳到循环外面。

关键点:初始化只执行一次,条件判断每次循环前都做,更新表达式每次循环体结束后执行。

10.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");
}

输出结果:

1*1= 1 
1*2= 2 2*2= 4 
1*3= 3 2*3= 6 3*3= 9 
1*4= 4 2*4= 8 3*4=12 4*4=16 
...

外层循环控制行数,内层循环控制每行的列数。内层循环的结束条件依赖外层循环的变量i,这样每行的列数就不同了。

我曾经踩过的坑:有一次做二维数组遍历,我把内外层循环的变量名都写成了i。结果内层循环把外层i改了,程序跑飞了。从那以后,我坚持用i、j、k这种命名习惯,内外层绝不重复。

嵌套循环的时间复杂度是O(n²),数据量大时要小心。我在嵌入式项目里,如果循环层数超过3层,就会考虑优化算法。

10.4 死循环

死循环就是条件永远为真的循环。有时候是bug,有时候是故意为之。

先看几种常见的死循环写法:

// 写法1:条件永远为真
for (;;) {
    // 循环体
}

// 写法2:条件写死为1
for (int i = 0; 1; i++) {
    // 循环体
}

// 写法3:更新表达式没改变条件
for (int i = 0; i < 10; ) {
    // 忘了写 i++
    // 循环体
}

写法1在嵌入式开发中很常见。比如单片机的主循环:

for (;;) {
    // 读取传感器数据
    // 处理数据
    // 控制输出
    // 延时
}

这种死循环是故意的,因为嵌入式设备需要一直运行。

避坑指南:我曾经在调试一个电机控制程序时,不小心把循环条件写成了 i <= 10 而不是 i < 10。结果数组越界访问,电机突然反转,差点把设备搞坏。边界条件一定要仔细检查。

常见的死循环bug:

  • 忘记写更新表达式(i++)
  • 条件写反了(比如 i > 0 但 i 一直在增加)
  • 浮点数比较(浮点数精度问题导致条件永远不成立)
  • 循环体内修改了循环变量

10.5 for循环的变体用法

for循环的三个表达式都可以省略,但分号不能少。我分享几个实用技巧:

技巧1:多个变量同时控制

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

技巧2:在循环体内改变循环变量

for (int i = 0; i < 100; i++) {
    if (some_condition) {
        i += 5;  // 跳过一些元素
    }
}

这种做法要谨慎,容易造成逻辑混乱。我一般只在特殊场景下用。

技巧3:使用break提前退出

for (int i = 0; i < 100; i++) {
    if (data[i] == target) {
        printf("找到了,位置在%d\n", i);
        break;
    }
}
我的建议:在嵌入式开发中,for循环的循环变量尽量用局部变量,不要用全局变量。全局变量容易被中断修改,导致循环行为不可预测。我吃过这个亏,调试了整整两天才找到原因。

10.6 性能小贴士

嵌入式设备资源有限,循环性能很重要。分享几个经验:

  • 减少循环内的函数调用:把不变的表达式提到循环外面
  • 用局部变量缓存数组长度:避免每次循环都计算
  • 循环展开:对于固定次数的短循环,手动展开能提升速度

举个例子:

// 不推荐
for (int i = 0; i < strlen(str); i++) {
    // 每次循环都计算strlen
}

// 推荐
int len = strlen(str);
for (int i = 0; i < len; i++) {
    // 只计算一次
}

嗯,for循环的内容就这些。记住一点:循环是工具,用好了事半功倍。多写多练,自然就熟练了。


公众号:蓝海资料掘金营,微信deep3321