控制流语句决定了程序的执行路径。C 语言提供选择结构(if-else、switch)、循环结构(for、while、do-while)和跳转语句(break、continue、goto),组合起来可以表达任意复杂的逻辑。
if (condition1) {
/* ... */
} else if (condition2) {
/* ... */
} else {
/* ... */
}
else if 可以无限链式延伸else 捕获所有未匹配的情况switch (expression) {
case constant1:
/* ... */
break;
case constant2:
/* ... */
break;
default:
/* ... */
break;
}
expression 必须是整型(int、char、enum 等)case 后必须是编译时常量(不能是变量)break 会导致 fall-through(穿透到下一个 case)default 可选,建议总是写上case 可共享同一段代码for (init; condition; update) {
/* ... */
}
init 只在循环开始前执行一次condition 每次迭代前检查,为 0 时退出update 每次迭代后执行for (;;))while (condition) {
/* ... */
}
先判断条件,再执行循环体。条件为 0 时退出。
do {
/* ... */
} while (condition);
先执行循环体,再判断条件。至少执行一次。常用于菜单交互、输入校验等必须先执行一次的场景。
break:跳出当前循环或 switchcontinue:跳过本次循环的剩余部分,进入下一次迭代goto:无条件跳转到标签位置在内核和嵌入式代码中,goto 常用于统一错误处理路径(资源清理):
int func(void) {
int *buf1 = NULL, *buf2 = NULL;
buf1 = malloc(1024);
if (!buf1) goto err_buf1;
buf2 = malloc(1024);
if (!buf2) goto err_buf2;
/* 正常逻辑 */
free(buf2);
free(buf1);
return 0;
err_buf2:
free(buf1);
err_buf1:
return -1;
}
这种模式在 Linux 内核中大量使用(goto 错误处理是内核编码规范的一部分)。
switch (val) {
case 1:
case 2:
case 3:
printf("1, 2, or 3\n");
break;
case 4:
printf("4\n");
break;
}
for (int i = 0; i < 10; i++) { /* C99: 变量声明在 for 内 */
/* ... */
}
#include <stdio.h>
const char *grade_str(int score) {
switch (score / 10) {
case 10:
case 9:
return "A (优秀)";
case 8:
return "B (良好)";
case 7:
return "C (中等)";
case 6:
return "D (及格)";
default:
return "F (不及格)";
}
}
int main(void) {
/* if-else 链 */
int temp = 25;
printf("温度 %d°C: ", temp);
if (temp < 0) {
printf("结冰\n");
} else if (temp < 15) {
printf("寒冷\n");
} else if (temp < 25) {
printf("凉爽\n");
} else if (temp < 35) {
printf("温暖\n");
} else {
printf("炎热\n");
}
/* switch-case */
int score = 85;
printf("成绩 %d 分: %s\n", score, grade_str(score));
/* 多 case 共享代码 */
int day = 3;
switch (day) {
case 1: case 2: case 3:
case 4: case 5:
printf("星期%d: 工作日\n", day);
break;
case 6: case 7:
printf("星期%d: 周末\n", day);
break;
}
return 0;
}
编译命令:
gcc -std=c99 -Wall -Wextra -o control1 control1.c
./control1
#include <stdio.h>
int main(void) {
/* for 循环:已知迭代次数 */
printf("=== for 循环 ===\n");
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
printf("1+2+...+100 = %d\n", sum);
/* while 循环:条件驱动 */
printf("\n=== while 循环 ===\n");
int n = 1024;
int count = 0;
while (n > 0) {
n >>= 1;
count++;
}
printf("1024 需要右移 %d 次变为 0\n", count);
/* do-while:至少执行一次 */
printf("\n=== do-while 循环 ===\n");
int value;
do {
printf("请输入正数 (0 退出): ");
scanf("%d", &value);
if (value > 0) {
printf("你输入了 %d\n", value);
}
} while (value != 0);
/* break 与 continue */
printf("\n=== break 与 continue ===\n");
printf("1-20 中不能被 3 整除的数: ");
for (int i = 1; i <= 20; i++) {
if (i % 3 == 0) {
continue; /* 跳过 3 的倍数 */
}
printf("%d ", i);
}
printf("\n");
printf("寻找第一个大于 100 的完全平方数: ");
for (int i = 1; ; i++) {
int sq = i * i;
if (sq > 100) {
printf("%d (i=%d)\n", sq, i);
break;
}
}
return 0;
}
编译命令:
gcc -std=c99 -Wall -Wextra -o control2 control2.c
./control2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *name;
int *data;
size_t size;
} Resource;
int init_resource(Resource *r, const char *name, size_t size) {
r->name = NULL;
r->data = NULL;
r->size = 0;
r->name = malloc(strlen(name) + 1);
if (!r->name) {
fprintf(stderr, "Failed to allocate name\n");
goto err_name;
}
strcpy(r->name, name);
r->data = malloc(size * sizeof(int));
if (!r->data) {
fprintf(stderr, "Failed to allocate data\n");
goto err_data;
}
r->size = size;
memset(r->data, 0, size * sizeof(int));
printf("Resource '%s' initialized (size=%zu)\n", r->name, r->size);
return 0;
err_data:
free(r->name);
r->name = NULL;
err_name:
return -1;
}
void destroy_resource(Resource *r) {
free(r->data);
free(r->name);
r->data = NULL;
r->name = NULL;
r->size = 0;
printf("Resource destroyed\n");
}
int main(void) {
Resource res;
if (init_resource(&res, "sensor_buf", 256) != 0) {
fprintf(stderr, "Initialization failed\n");
return 1;
}
/* 使用资源 */
res.data[0] = 42;
printf("res.data[0] = %d\n", res.data[0]);
destroy_resource(&res);
return 0;
}
编译命令:
gcc -std=c99 -Wall -Wextra -o goto_err goto_err.c
./goto_err
if 后面多余分号:
if (x == 5); // 空语句,if 什么也没做
{ printf("ok"); } // 这个块总是执行
switch 漏写 break:忘记 break 导致 fall-through 是最常见的 switch bug。C17 引入 [[fallthrough]] 属性(C23 为标准关键字)可显式标注意图。
for 循环的悬挂分号:
for (i = 0; i < 10; i++); // 空循环体
{ sum += i; } // 这个块只执行一次
do-while 的分号:do { ... } while (condition); 末尾必须有分号,这是语法要求。
goto 标签作用域:标签的作用域是整个函数,不同函数可以有同名标签,但同一函数内不能重复。
循环变量溢出:unsigned char i 从 0 到 255,i++ 后回绕到 0,条件 i < 256 永远为真,造成死循环。
switch 不支持浮点数和字符串:switch 的表达式必须是整型或枚举类型。
Q1:while(1) 和 for(;;) 有区别吗?
A:在功能上没有区别,都是死循环。但 for(;;) 在某些编译器上可能生成略微不同的代码(while(1) 需要对常量 1 求值,for(;;) 完全省略条件检查)。实际中两者均可,风格偏好问题。更专业的写法是 for (;;) { /* ... */ }。
Q2:goto 为什么在嵌入式/内核代码中仍然被使用?
A:在内核和嵌入式开发中,goto 用于统一的错误清理路径。当函数需要按顺序分配多个资源时,使用 goto 可以避免多层嵌套的 if-else 或重复的清理代码。Linux 内核编码风格明确允许并推荐这种用法。但不应滥用 goto 做任意跳转。
Q3:for 循环的三个表达式可以是任意表达式吗?
A:是的。init、condition、update 都可以是任意表达式,包括函数调用、赋值、逗号表达式等。甚至可以修改循环变量本身。但为了可读性,通常只做简单操作。
Q4:switch-case 中 case 后面能用变量吗?
A:不能。case 后必须是编译时常量表达式(整型常量表达式),不能是 const 变量(C 语言的 const 不是真正意义上的常量)、变量或字符串。这是 C 语言 switch 的硬性限制。
Q5:如何避免循环嵌套中的性能问题?
A:(1) 减少内层循环的计算量,将不变的计算提到外层;(2) 注意数据局部性,按行优先(C 语言默认)遍历二维数组;(3) 循环展开减少分支预测失败;(4) 避免在循环内做内存分配/释放;(5) 利用缓存行大小优化步长。