C 执行输出控制

if else 是C语言中最常用的一种选择结构(分支结构)

C语言程序的 三种基本结构

1. 顺序结构

2. 分支结构

3. 循环结构

4. 跳转结构

if else语句 的用法

if else 是C语言中最常用的一种选择结构(分支结构)

基本语法:

if(条件1){
    //代码块1
    //如果条件为真,执行这里的代码
}else if(条件){
    //代码块2
    //如果条件1为假,条件2为真,执行这里的代码
}else{
	//代码块3
    //如果条件为假,执行这里的代码
}

if 语句中条件可以是任何有效的C语言表达式,常见的包括比较表达式、逻辑表达式,甚至是简单的变量或者函数调用。

关系运算符

关系运算符

NOTE 关系运算符都是双目运算符,其结合性均为左结合。 关系运算符的优先级低于算术运算符,高于赋值运算符

QUESTION: 单目运算符 & 双目运算符 & … 将 运算时有两个操作数的运算符称为双目运算符 将 运算时只有一个操作数的运算称为单目运算符 同理,三目运算符也是如此 EXAMPLE 常见的双目运算符有+、-、*、/等,单目运算符有++、--等, 三目运算符只有一个 → ?: ,在《条件运算符》中有详细介绍

关系运算符 含义 数学中的表示 优先级 结合性
< 小于 < 从左到右
<= 小于或等于 从左到右
> 大于 > 从左到右
>= 大于或等于 从左到右
<mark> 等于 = 从左到右
!= 不等于 从左到右
  • 关系运算符的运算结果只有 0(假)或 1(真)。 当条件成立时结果为 1,条件不成立结果为 0。

在C语言中,我们将值称为“”,表示条件不成立; 相反,我们将非零值称为“”,表示条件成立。

逻辑运算符

逻辑运算符

逻辑运算符和关系运算符的结果都是布尔类型; 布尔类型只有两个取值,分别是 真(true)假(false) ; 在C语言中,用 1 表示真,用 0 表示假。

同时,在逻辑运算或者条件判断的过程中,任何非零值(比如 10、926、-5)都会被视为真,只有零值(比如 0、‘\0’)才会被视为假。

支持 C99 以及更高标准的编译器,可以用 bool 表示布尔类型,用 true 表示真(非 0 值),用 false 表示假(0 值)

逻辑运算符的优先级

|| < && < !

逻辑运算符和其它运算符优先级从低到高依次为: 赋值运算符= < 逻辑运算符|| < 逻辑运算符&& < 关系运算符 < 算术运算符 < 逻辑运算符!

短路求值

C语言中的逻辑运算符使用短路求值,这意味着:

  • 在 && 运算中,如果第一个表达式的结果为假,就不会再计算第二个表达式,因为不管第二个表达式的结果是什么,都不会影响整个 && 运算的结果。
  • 在 || 运算中,如果第一个表达式的结果为真,就不会再计算第二个表达式,原理同上。

switch case语句 的用法

switch case 用于实现多分支选择结构,它会根据一个表达式的不同值,执行不同的代码块。

C语言 switch case 语句的用法如下:

switch (表达式) {
    case 整形常量1:
        //代码块 1
        break
    case 整形常量2:
        //代码块 2
        break;
    ......
    case 整形常量n:
        //代码块n
        break;
    default:
        //默认代码块
}

switch case 语句的执行流程如下:

  1. 先计算表达式的结果,然后将该结果与各个 case 后面的整形常量进行比较,看它们的值是否相等。

  2. 从第一个 case 开始,依次挨着往后比较,一旦遇到某个相等的整形常量,就停止比较,并执行当前 case 后面的代码块

每个代码块的后面都有一个 break 语句,用来“跳出”整个 switch 结构,防止继续执行下一个 case 分支;换句话说,一旦遇到 break 语句,整个 switch 结构就执行结束了。\

  1. 如果比较到最后也没有找到相等的整形常量,那么就执行 default 后面的默认代码块。default 是“默认”的意思,也就是当没有匹配的 case 分支时,默认执行的一个分支。换句话说,default 分支用来处理所有 case 都不匹配的情况,是负责“兜底”的。

default 是可选择,你可以不写。如果没有 default 分支,也没有匹配的 case 分支,那么 switch 将什么都不执行。

ATTENTION switch case 只能用来匹配整型值(包括 char 类型),不能匹配其它类型的值。虽然表达式的写法很灵活,可以是变量、常量、加减乘除运算、函数调用等,但它必须能够得到一个整型值。

在实际开发中,switch case 常用来替代简单的、拥有多个分支的 if else 语句。C语言虽然没有限制 if else 能够处理的分支数量,但当分支过多时,用 if else 处理会不太方便,而且容易出现 if else 配对出错的情况,此时就可以尝试 switch case。

EXAMPLE 例如,输入一个整数,输出该整数对应的星期几的英文表示,我们可以用 if else 来实现:

#include <stdio.h>
int main() {
    int a;
    printf("Input integer number:");
    scanf("%d", &a);

    if (a </mark> 1) {
        printf("Monday\n");
    }else if (a <mark> 2) {
        printf("Tuesday\n");
    }else if (a </mark> 3) {
        printf("Wednesday\n");
    }else if (a <mark> 4) {
        printf("Thursday\n");
    }else if (a </mark> 5) {
        printf("Friday\n");
    }else if (a <mark> 6) {
        printf("Saturday\n");
    }else if (a </mark> 7) {
        printf("Sunday\n");
    }else {
        printf("error\n");
    }

    return 0;
}

这段代码看起来有点乱糟糟,如果用 switch case 语句来替换,就清晰多了:

switch (a) {
case 1:
	printf("Monday\n");
	break;
case 2:
	printf("Tuesday\n");
	break;
case 3:
	printf("Wednesday\n");
	break;
case 4:
	printf("Thursday\n");
	break;
case 5:
	printf("Friday\n");
	break;
case 6:
	printf("Saturday\n");
	break;
case 7:
	printf("Sunday\n");
	break;
default:
	printf("error\n");
}

关于break

用于跳出当前的整个循环

Attention: 为什么要使用 break 因为 switch 有一个坏毛病,就是一旦发现表达式的值和某个整形常量匹配了,就会执行后面的所有代码,包括当前 case 分支的代码,以及它后面的其它 case 分支的代码。这种现象称为贯穿(fall through)

也就是说,switch 只会匹配一次,一旦匹配成功了,就会不管不顾地执行后面的代码,而不管其它 case 是否匹配。只有到达 switch 末尾,或者遇到 break 关键字,才会结束整个 switch 的执行。

贯穿往往不是我们所期望的,所以才在每个 case 的后面都加一个 break,这样在执行完当前分支以后就能强制跳出 switch 了。

至于 default 分支最后,加不加 break 都无所谓,因为它往往是 switch 的最后一个分支,有没有 break 都会结束整个 switch 的执行。

关于case

case 后面除了可以跟整形常量,还可以跟结果是整型的常量表达式,这意味着表达式的值在编译期间就能确定,而不用等到运行时计算得出

另外,虽然普通变量 不能用在 case 后面,但是由 const 修饰的常量是可以的,这是因为 const 常量的值不能修改,在编译期间就能确定。

while循环 和 do while循环 的用法

while 循环是C语言中常用的一种循环结构,而 do while 循环则是它的一种变体。

while 循环

while 循环的基本用法如下:

while (条件)
{
    // 循环体
    // 也即要重复执行的代码
}

当程序执行到 while 语句时,首先检测括号中的条件。 如果条件为真(非零),则执行循环体中的代码。 执行完循环体后,程序会再次回到 while 语句,重新检测括号中的条件。 如果条件为真,将继续执行循环体中的代码……这个过程会一直重复,直到条件变为假(零)为止。 一旦条件变为假,程序就会跳出循环(结束循环),继续执行循环后面的代码。

while 循环的执行过程可用下图表示: While 循环流程示意图

  • 如果循环条件中不包含变量,会发生什么情况呢? 如果循环条件成立时的话,while 循环会一直执行下去,永不结束,称为“死循环”
  • 如果循环条件一开始就不成立,那么循环体就一次也不执行。

do while 循环

do while 循环是 while 循环的一种变体,它的基本用法如下:

do {
    // 循环体
    // 要重复执行的代码
} while (条件);

do while 循环和 while 循环的不同在于: 它会先执行一次循环体,然后再检测条件是否为真,如果为真则继续循环;如果为假,则终止循环。因此,do while 循环至少要执行一次循环体。

ATTENTION 最后的分号;必须要写,不能省略。

while 循环执行 0n 次循环体,do while 循环执行 1n 次循环体,这就是它们之间的主要区别。

do while 循环的执行过程可用下图表示: Do While 循环流程示意图

for循环 的用法

所谓循环(Loop),就是重复地执行同一段代码,直到满足特定的条件才会终止。每执行一次代码,或者每执行一次循环,就叫做一次迭代(Iteration)。

for 循环的一般用法如下:

for (初始化表达式; 条件表达式; 更新表达式) {
    // 循环体
    //也就是要重复执行的代码
}

关于三个表达式:

  • 初始化表达式仅在开头(第一次循环之前)执行一次,以后都不会再执行,主要进行一些初始化设置(比如设置条件表达式或者更新表达式中某些变量的值)。
  • 条件表达式通常会包含关系运算符或者逻辑运算符,用以决定是否还要继续下次循环,有人称其为“循环条件”。
  • 更新表达式很多情况下是一个带有自增(++)或自减(–)运算符的表达式,以使条件表达式逐渐变得不成立,从而结束循环;这里的“更新”,说白了就是改变条件表达式中某个变量的值。

for 循环的执行过程可用下图表示: For循环流程示意图 对于每次迭代,先执行条件表达式,再执行循环体,最后执行更新表达式

C语言 goto语句 的用法

🏷️ Reference: C语言中文网 - goto语句的用法

Abstract: 核心概念 goto 是 C 语言中的一个流程控制关键字。它允许程序无条件地从当前位置跳转到代码中的另一个指定位置(标签),并从那里继续执行。

尽管在现代编程中不推荐滥用,但了解其原理对构建底层的编程思维依然很有帮助。

1. 基本语法

goto 语句主要由两部分组成:关键字+标签名目标标签。它可以实现向前跳转或向后跳转。

// 语法 1:向后跳转
goto label_name;
...
label_name: 
    statement;

// 语法 2:向前跳转
label_name: 
    statement;
...
goto label_name;
  • 标签名:自定义的标识符,配合冒号 : 使用,用于标记程序中的位置。

  • 执行逻辑:程序遇到 goto 时,会直接打破常规的顺序执行,瞬间跳转到标签处。


2. 典型代码示例

示例 A:替代循环(基础演示)

在下面这个简单的例子中,goto 被用来模拟一个循环结构,打印 0 到 4。

#include <stdio.h>

int main() {
    int i = 0;

start:  // 定义标签
    printf("%d ", i);
    i++;
    if (i < 5) {
        goto start;  // 满足条件时,跳转回 start 标签
    }
    
    printf("\nLoop finished.\n");
    return 0;
}

输出结果0 1 2 3 4 Loop finished.

Warning: 注意

这个例子仅仅是为了演示 goto 的跳转原理。在实际的 C 语言开发中,强烈建议使用规范的 whilefor 循环来实现此类功能。

示例 B:集中错误处理(最佳实践)

这是 goto 在现代 C 语言开发中最常见且被认可的用法。当有多层嵌套或多个资源需要分配时,如果中途发生错误,使用 goto 可以快速跳出多层逻辑,统一跳转到末尾的资源清理代码,避免代码冗余。

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

int main() {
    FILE *file1 = NULL, *file2 = NULL;
    char *buffer = NULL;

    file1 = fopen("input.txt", "r");
    if (file1 <mark> NULL) {
        printf("Error opening input file.\n");
        goto cleanup; // 发生错误,跳转到清理区
    }

    file2 = fopen("output.txt", "w");
    if (file2 </mark> NULL) {
        printf("Error opening output file.\n");
        goto cleanup; // 发生错误,跳转到清理区
    }

    buffer = (char*)malloc(1000);
    if (buffer == NULL) {
        printf("Memory allocation failed.\n");
        goto cleanup; // 发生错误,跳转到清理区
    }

    // 正常的文件处理代码...

cleanup:  // 统一的清理标签
    if (file1) fclose(file1);
    if (file2) fclose(file2);
    if (buffer) free(buffer);
    
    return 0;
}

3. 优缺点与使用建议

Check: 适用场景

  • 深层嵌套的跳出:当需要在极深的循环或条件判断中直接中断并跳出时,比使用多个状态标志位或多层 break 更高效。

  • 集中的资源释放/错误处理:如示例 B 所示,可以大大保持代码的整洁度(这也是 Linux 内核源码中大量使用 goto 的原因)。

Fail: 为什么不推荐滥用?

  • goto 会让程序跳来跳去,打乱结构化编程的顺序。

  • 滥用会导致产生难以阅读、难以维护的**“面条代码” (Spaghetti Code)**。

  • 大多数情况下,if-elsewhilefor 结构足以实现相同的功能,且逻辑更加清晰。

相关笔记