强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

C/C++ Linux 开发教程(GCC + CMake) / 03 — 控制流(if/for/while/switch)

控制流(if/for/while/switch)

1. if / else if / else

#include <stdio.h>

int main(void)
{
    int score;
    printf("请输入成绩: ");
    scanf("%d", &score);

    if (score >= 90) {
        printf("优秀\n");
    } else if (score >= 80) {
        printf("良好\n");
    } else if (score >= 60) {
        printf("及格\n");
    } else {
        printf("不及格\n");
    }

    return 0;
}

⚠️ 注意: else 匹配最近的未匹配 if,不要被缩进迷惑。


2. 三元运算符

#include <stdio.h>

int main(void)
{
    int a = 10, b = 20;
    int max = (a > b) ? a : b;
    printf("max = %d\n", max);

    // 也可以嵌套(但不推荐过度嵌套)
    int x = 5;
    const char *label = (x > 0) ? "正数" : (x < 0) ? "负数" : "零";
    printf("%d 是%s\n", x, label);

    return 0;
}

💡 提示: 三元运算符适合简单的二选一赋值,复杂逻辑用 if/else 更清晰。


3. for 循环

3.1 基本语法

for (初始化; 条件; 步进) {
    循环体;
}
#include <stdio.h>

int main(void)
{
    // 打印 1 到 10
    for (int i = 1; i <= 10; i++) {
        printf("%d ", i);
    }
    printf("\n");

    // 倒序遍历
    for (int i = 10; i >= 1; i--) {
        printf("%d ", i);
    }
    printf("\n");

    // 步进为 2
    for (int i = 0; i <= 20; i += 2) {
        printf("%d ", i);
    }
    printf("\n");

    return 0;
}

3.2 for 循环变体

// 多变量控制
for (int i = 0, j = 10; i < j; i++, j--) {
    printf("i=%d, j=%d\n", i, j);
}

// 无限循环
for (;;) {
    // 用 break 退出
    break;
}

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

4. while 循环

#include <stdio.h>

int main(void)
{
    // while 循环
    int n = 1;
    while (n <= 100) {
        n *= 2;
    }
    printf("第一个超过 100 的 2 的幂: %d\n", n);

    // 读取输入直到 EOF
    int val;
    printf("输入整数(非数字退出): ");
    while (scanf("%d", &val) == 1) {
        printf("你输入了: %d\n", val);
    }

    return 0;
}

4.1 do-while 循环

do-while 至少执行一次循环体:

#include <stdio.h>

int main(void)
{
    int n;
    do {
        printf("输入 1-10 的数字: ");
        scanf("%d", &n);
    } while (n < 1 || n > 10);

    printf("你输入了: %d\n", n);
    return 0;
}

💡 提示: do-while 适合"先执行再判断"的场景,如用户输入验证。


5. break / continue / goto

5.1 break

立即跳出当前循环:

#include <stdio.h>

int main(void)
{
    // 查找第一个能被 7 整除的数
    for (int i = 1; i <= 100; i++) {
        if (i % 7 == 0) {
            printf("找到: %d\n", i);
            break;
        }
    }
    return 0;
}

5.2 continue

跳过本次迭代,进入下一次:

#include <stdio.h>

int main(void)
{
    // 打印 1-20 中所有奇数
    for (int i = 1; i <= 20; i++) {
        if (i % 2 == 0) continue;
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

5.3 goto

goto 可以跳转到函数内任意标签。过度使用会导致代码难以理解,但在某些场景下有其价值:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *a = malloc(10 * sizeof(int));
    if (!a) goto fail;

    int *b = malloc(20 * sizeof(int));
    if (!b) goto free_a;

    int *c = malloc(30 * sizeof(int));
    if (!c) goto free_b;

    // 使用 a, b, c ...
    printf("所有资源分配成功\n");

    free(c);
free_b:
    free(b);
free_a:
    free(a);
fail:
    return 0;
}

⚠️ 注意: goto 只能在同一函数内跳转,不能跨函数。仅在资源清理等必要场景使用。


6. switch / case / break / default

#include <stdio.h>

int main(void)
{
    int day;
    printf("输入星期几 (1-7): ");
    scanf("%d", &day);

    switch (day) {
    case 1:
        printf("星期一\n");
        break;
    case 2:
        printf("星期二\n");
        break;
    case 3:
        printf("星期三\n");
        break;
    case 4:
        printf("星期四\n");
        break;
    case 5:
        printf("星期五\n");
        break;
    case 6:
        printf("星期六\n");
        break;
    case 7:
        printf("星期日\n");
        break;
    default:
        printf("无效输入\n");
        break;
    }

    return 0;
}

6.1 Fall-through 特性

没有 break 时会"穿透"到下一个 case

#include <stdio.h>

int main(void)
{
    int month;
    printf("输入月份 (1-12): ");
    scanf("%d", &month);

    int days;
    switch (month) {
    case 2:
        days = 28;  // 不考虑闰年
        break;
    case 4: case 6: case 9: case 11:
        days = 30;
        break;
    case 1: case 3: case 5: case 7:
    case 8: case 10: case 12:
        days = 31;
        break;
    default:
        printf("无效月份\n");
        return 1;
    }

    printf("%d 月有 %d 天\n", month, days);
    return 0;
}

💡 提示: switchcase 后面只能跟整型常量表达式,不能用变量或浮点数。


7. 嵌套循环

#include <stdio.h>

int main(void)
{
    // 打印九九乘法表
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= i; j++) {
            printf("%d×%d=%-4d", j, i, i * j);
        }
        printf("\n");
    }

    return 0;
}

输出:

1×1=1
1×2=2   2×2=4
1×3=3   2×3=6   3×3=9
...

嵌套循环中的 break

break 只跳出最内层循环:

for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        if (j == 5) break;  // 只跳出内层 for
        printf("(%d,%d) ", i, j);
    }
    printf("\n");
}

8. 循环优化技巧

8.1 减少循环内计算

// 差:每次迭代都调用 strlen
for (int i = 0; i < strlen(str); i++) {
    // ...
}

// 好:提前计算
int len = strlen(str);
for (int i = 0; i < len; i++) {
    // ...
}

8.2 提前退出

// 搜索元素,找到即退出
int found = 0;
for (int i = 0; i < n; i++) {
    if (arr[i] == target) {
        found = 1;
        break;   // 不需要继续遍历
    }
}

8.3 循环展开

// 每次迭代处理 4 个元素
int sum = 0;
int i;
for (i = 0; i + 3 < n; i += 4) {
    sum += arr[i] + arr[i+1] + arr[i+2] + arr[i+3];
}
for (; i < n; i++) {
    sum += arr[i];
}

💡 提示: 现代编译器在 -O2 下通常会自动展开循环,手动展开主要用于热点代码。


9. 常见陷阱

9.1 无限循环

// 陷阱 1: 分号终止了 for 循环体
for (int i = 0; i < 10; i++);   // 注意这里的分号!
{
    printf("%d\n", i);           // 这行不在循环内
}

// 陷阱 2: 死循环
int i = 0;
while (i < 10) {
    printf("%d\n", i);
    // 忘记 i++,永远满足 i < 10
}

// 陷阱 3: 有符号整数导致的无限循环
for (unsigned int i = 10; i >= 0; i--) {  // 永远成立!
    // unsigned 的 0 - 1 会回绕到 UINT_MAX
}

9.2 数组越界

int arr[5] = {1, 2, 3, 4, 5};
// 陷阱:<= 导致越界访问
for (int i = 0; i <= 5; i++) {  // arr[5] 越界!
    printf("%d\n", arr[i]);
}

9.3 整数溢出

// 陷阱:sum 可能溢出
int sum = 0;
for (int i = 1; i <= 100000; i++) {
    sum += i * i;  // 可能溢出 int 范围
}
// 使用 long long 避免
long long safe_sum = 0;
for (int i = 1; i <= 100000; i++) {
    safe_sum += (long long)i * i;
}

10. 实际场景:猜数字游戏

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    srand((unsigned)time(NULL));
    int secret = rand() % 100 + 1;
    int guess, attempts = 0;

    printf("猜数字游戏:请猜一个 1-100 的数字\n");

    do {
        printf("你的猜测: ");
        if (scanf("%d", &guess) != 1) break;
        attempts++;

        if (guess > secret) {
            printf("太大了!\n");
        } else if (guess < secret) {
            printf("太小了!\n");
        } else {
            printf("恭喜!你猜中了!用了 %d 次\n", attempts);
        }
    } while (guess != secret && attempts < 10);

    if (attempts >= 10 && guess != secret) {
        printf("超过次数限制,答案是 %d\n", secret);
    }

    return 0;
}

编译运行:

gcc -Wall -std=c17 -o guess guess.c && ./guess

11. 实际场景:统计字符频率

#include <stdio.h>
#include <string.h>

int main(void)
{
    const char *text = "hello world, hello c programming";
    int freq[26] = {0};

    for (int i = 0; text[i] != '\0'; i++) {
        char ch = text[i];
        if (ch >= 'a' && ch <= 'z') {
            freq[ch - 'a']++;
        }
    }

    printf("字符频率统计:\n");
    for (int i = 0; i < 26; i++) {
        if (freq[i] > 0) {
            printf("'%c': %d\n", 'a' + i, freq[i]);
        }
    }

    return 0;
}

12. 扩展阅读