[Debug 2025 Freshman] CP Chapter 02 IO and Flow Control


Debug 2025 Freshman - C Programing

Chapter02 - IO and Flow Control

本章将正式开始学习 C 语言相关的内容。内容有关 C 语言的基础语法,基本运算,以及流程控制。

Hello, World!

在正式入门语法之前,我们需要明白什么是输入输出

对于人类而言,眼睛、耳朵、鼻子能够帮我们从外界获取视觉、声音、气味信息,这些器官就是我们人体的输入设备

而嘴能说话、手能写字,这些器官能将我们脑中的想法传达给外界,就是我们人体的输出设备

同样的电脑的键盘、鼠标、麦克风就是电脑的输入设备,而显示器、扬声器就是电脑的输出设备

形式化的,对于一个系统而言,输入设备帮助系统从外界获取信息,输出设备帮助系统向外界发送信息。

就像我们的大脑能进行思考与运算、能记忆知识,小脑能控制我们身体的运动,除了输入输出设备之外电脑也有相应的运算器存储器控制器,也就是 CPU 和 硬盘。

而我们写程序,本质上就是在控制计算机按照人类的逻辑去执行任务,也就是从输入设备获取信息,控制电脑对信息进行处理运算,最后将信息给到输出设备。

就像人类从出生开始就要学习如何说话一样,写程序的第一步也是控制电脑进行一个最简单的输出。

对于人类而言,最直接的输出方式必然是说话,同样的,对于电脑而言最直接的输出方式是打字

打开你的 IDE(Dev-C++),新建一个文件并写入下面的代码,然后运行:

#include <stdio.h>

int main () {
    printf("Hello,World!");
    return 0;
}

然后你就会看到类似下面的窗口:

这句Hello, World!就是我们操作电脑在窗口中打印出的文字,而上面这个程序就是你的第一个 C 语言程序,同样也是你控制电脑进行输出的第一个成功尝试。

我们将现在这个黑色的窗口称为控制台,是我们目前控制电脑输出以及电脑获取输入的主要平台,在接下来的 C 语言课程中将会经常用到。

现在让我们回到这段代码,看看这段代码都干了什么:

// 引入输入输出库
#include <stdio.h>

// 主函数
int main () {
    // 在控制台打印 Hello, World!
    printf("Hello,World!");
    // 主函数默认返回 0
    return 0;
}

相信你除了打印 Hello, World!以外都看得一头雾水。但是没有关系,我们接下来的课程将会一一介绍。

在这里,你只需要知道两点:

首先对于这句代码

#include <stdio.h>

<stdio.h> 是 C 语言的标准输入输出库,其中stdiostandard input/output的缩写,.h表示这是一个 C 语言的头文件

你可以将stdio.h理解为一个工具箱。

就像我们用螺丝刀拧螺丝,用电烙铁焊接电路,不同的任务需要不同的工具控制。而我们用来控制输入输出的工具,例如我们用来打印文字的printf()工具,就在 stdio.h 里面放着。

同样的,头文件的类型还有很多,例如 <math.h> 就能用来执行各种数学运算,<time.h> 可以用来或许时间。

我们叫这样的一些 C 语言自带的工具箱的集合叫做 C 语言标准库,这其中包含着很多前辈们帮我们开发好的工具,我们只需要使用 #include 引入这些工具,就能很方便地使用他们。

你可以在 C 标准库 | 菜鸟教程 获取到 C 语言标准库的更多详细信息。

其次,对于剩下的这部分代码

int main() {
	// 代码从这里开始运行
	/**
	 *
	 *
	 *
	 */
    // 运行到这行之后程序退出
    return 0;
}

则定义了程序的入口。简单来说,代码会从 int main() { 的下一行开始执行,运行到 return 0;之后结束执行

其中 int main(); 被称为主函数,至于什么是函数,我们将会在后面的章节中介绍。

总观整份代码,你会发现诸如 int return 之类的用空格分隔开的不同单词,在 C 语言中我们称之为关键字,电脑就是通过解析这一个个的关键字,以及他们之间的关系,也就是词法分析语法分析,解读我们书写的代码的。

若干关键字组成一条语句,每条语句以 ; 结尾,通过若干条语句组成一个 C 语言程序。

在这之后你可以打印任意的文字了,让我们简单地做一个实验,分别在主函数中运行下面两份代码:

printf("Print end of line: next line");
printf("Print end of line: /n next line");

你会发现第一行代码输出的是:

Print end of line: next line

而第二行代码输出的是:

Print end of line: 
next line

这两行代码的区别就在于加入了一个 /n 字符,用来表示换行。

是的,实际上换行也是一个字符,只不过它的显示方式和普通的字符有些不同罢了。

形如 /n 这样的字符在 C 语言中被称为转义字符,你可以在 转义序列 | Microsoft Learn 学习更多有关转义字符的类型。

建议你在打印一串字符之后,都在字符的末尾加上 /n ,这样能让你更好地在控制台中区分不同的输出。

Data Type

现在我们知道了可以使用 printf() 在控制台中打印一串文字,同样的我们可以打印其他的除了文字之外的东西。

尽管文字是我们日常生活中最为常见的数据类型,但是计算机实际上更擅长处理的是数字而非文字。

根据要处理数据的不同类型,C 语言将这些数据分为了整数、小数、字符等等类型:

// 定义一个名称为 number 的整数变量,值为 1
int number = 1;
// 定义一个名称为 pi 的小数变量,值为 3.14
float pi = 3.14;
// 定义一个名称为 ch 的字符型变量,值为 'A'
// 注意这里需要打单引号 ' ', 引号中间只能放一个字符
char ch = 'a';

值得一提的是,在 C 语言中小数有一个更加常用的叫法:浮点数。顾名思义,就是小数点会浮动的数字,相比之下整数则是没有小数点的。

其中,intinteger 的简写,charcharacter 的缩写,而 float 其实是 float point 也就是浮点的简写。

结合上面我们学习的 printf 语句,就可以将这些数据的值打印出来:

char ch = 'a';
// 打印 number,其中 %d 表示数据类型为整型
int number = 1;
printf("number = %d/n", number);
// C 语言会自动按照逗号后面的量的出现顺寻在前面的 %d 处填入数值
int a = 1, b = 2, c = 3;
printf("a = %d, b = %d, c = %d/n", a, b, c);
// 打印 pi, %f 表示数据类型为浮点型
float pi = 3.1415926;
printf("pi = %f/n", number);
// 打印 ch, %.2f 表示数据类型为字符型,四舍五入到两位小数
printf("pi = %.2f/n", number);
// 打印 ch, %c 表示数据类型为字符型
printf("ch = %c/n", ch);

运行代码之后可以得到这样的结果:

number = 1
a = 1, b = 2, c = 3
pi = 0.000000
ch = a

这时候我们就可以解释 printf() 的行为了:printfprint format 的缩写,意思是按照格式打印文字。而按照什么格式以及打印什么样的数据,则由 printf("str", ...),的这个 "str" 中的内容确定。

同样我们可以对这些数据进行运算:

int a = 2;
int b = 3;
int add = a + b;
int mul = a * b;
int div = b / a; 
int ext = (a + b) * (a * b);
printf("a = %d, b = %d, add = %d, mul = %d, div = %d, ext = %d", a, b, add, mul, div, ext);

得到这样的输出:

a = 2, b = 3, add = 5, mul = 6, div = 1, ext = 30

可以发现,div 的值与我们预期的不一样,这是因为我们输出的是一个整数,而实际上期望的 1.5 是一个浮点值,浮点值在转换到整数的时候,会自动舍去小数点后的部分,也就是向下取整,这一行为我们称之为舍入误差

关于 C 语言整数与整数,小数与整数,小数与小数之间运算的规则十分复杂,我们只需要记住两件事:

第一,整数相除发生的是整除。第二,在一个表达式中尽量只使用一种数据类型。牢记这两点就能帮助你规避掉开发当中会遇到的大部分问题(但是不能保证你通过考试的刁难)。

这样看来只有这些整型、浮点型、字符型三种数据类型吗?当然不是。我们要知道的是,计算机对数据的表示能力是有限的,也就是说像 int 这样的整数类型是有长度上限的。

我们可以做一个简单的实验:

// 定义一个值为 10^9 (10 的 9 次方) 的变量 a
// 补充:1e9 是科学计数法的表示,类似的还有 1e-2 表示 0.02 之类的
int a = 1e9;
// 定义一个值为 10^9 (10 的 9 次方) 的变量 a 
int b = 1e9;
int mul = a * b;
printf("mul = %d", mul);

得到的结果居然是

mul = -1486618624

这根本不对!

答案就是,a * b 的值超过了 int 类型的表示范围,因此结果就变为一个预期之外的值,这一现象我们称之为溢出,在这里发生的就是整型溢出

虽然这个值可以通过某个规则计算出来,但是我们这里不做要求,因为实际开发中根本不会利用这种特性。

在 C 语言中,还提供了其他的一些数据类型,以支持更大的数字以及更高的运算精度。例如:

long long a = 1e9;
long long b = 1e9;
long long mul = a * b;
printf("mul = %lld", mul);

就能得到正确的结果

mul = 1000000000000000000

更多的数据类型可以查看:C 数据类型 | 菜鸟教程"

在这张表中可以看到,int 类型的长度是 4 个字节(一般情况下),一个字节有 8 个二进制位,那么 int 类型就能表示 $2^{4*8} = 2^{32}$ 个数,这 $2^{32}$ 个数字会被对半分到正负半轴。

Constants and Variables

刚才我们介绍的量其实都是变量。那么什么是变量呢?顾名思义,变量就是在代码执行过程中值可以改变的量:

int a = 0;
printf("a = %d\n", a);
a = 1;
printf("a = %d\n", a);
a = a + 1;
printf("a = %d\n", a);
a += 1;
printf("a = %d\n", a);

运行代码可以看到:

a = 0
a = 1
a = 2
a = 3

从这段代码可以看到,上面的操作中 a = a + 1a += 1等价的,同样的我们也有 a = a - 1 等价于 a -= 1

除此之外, C 语言还为了像变量自增或自减 1 的场景提供了自增运算符 ++自减运算符 --

int a = 1;
// 执行语句再自增 1
printf("a = %d\n", a++);
printf("a = %d\n", a);

int b = 1;
// 自增 1 再执行语句
printf("b = %d\n", ++b);
printf("b = %d\n", b);

运行代码之后可以看到以下结果:

a = 1
a = 2
b = 2
b = 2

自减运算符--也是同理。

除了加减乘除之外,C 语言还提供了一种特殊的运算:模运算

int a = 10 % 5;

这段代码的意思就是,将 10 除以 5 的余数赋值给变量 a

模运算还具有一些特殊的性质,可以用来避免整形溢出等等都问题,在我的一篇博客中有提到:ACM Note No.7: 数论

这个运算有什么用呢?我们之后的教程中会提到。

Input and Output

除了 printf() 之外,C 语言还提供了 putchar() 进行单个字符的输出:

char ch = 'a';
putchar(ch);

可以运行得到下面的输出:

a

前面我们已经学习了如何在控制台进行输出,但是我们还不知道如何在控制台进行输入。

方法与输出是类似的:

int n;
// 输入整型变量 n 的值
// 注意变量之前需要带上 & 
scanf("%d", &n);

同样的 scanf()scan format 的缩写,意思是从控制台按照给定格式扫描并拿到输入的字符。例如:

int a, b, c, d;
printf("input 4 number (separate by spaces): ");
// 输入四个数字,以空格分隔,(默认)以换行符结尾
scanf("%d %d %d %d", &a, &b, &c, &d);
printf("a = %d, b = %d, c = %d, d = %d", a, b, c, d);

运行程序之后,控制台会等待你的输入,直到输入的数据足够。

Input:

input 4 number (separate by spaces): 1 2 3 4

Output:

a = 1, b = 2, c = 3, d = 4

scanf()的行为实际上较为复杂,你可以简单理解为 scanf 在输入的时候会默认地丢弃所有空字符(包括空格、换行、制表符),只输入有实际意义的数据

如果我们想要输入被 scanf() 丢弃的空字符,例如输入空格或者换行符之类的,就需要用到 getchar()

char ch;
ch = getchar();
putchar(ch);

Input:

a

Output:

a

到这里你应该会发现,scanf() 的行为和 getchar() 不完全一样,因此为了避免这种分析与调试上的麻烦,一般不建议将 scanf()getchar() 混用,以避免发生一些意料之外的程序行为。

说完了 scanf(),让我们重新回到 printf()

首先是定义输出宽度与精度:

// 输出 "   12" (右对齐,宽度为5)
printf("%5d", 12);
// 输出 "12   " (左对齐)
printf("%-5d", 12);

// 输出 "3.14" (四舍五入)
printf("%.2f", 3.14159);
// 输出 "Hel",截取这个串的前三个字符,这个我们后面的字符串章节会细讲
printf("%.3s", "Hello");

我们都知道,printf() 中使用 %d 输出整数,这说明 %d 有别的意义,如果想要使用 printf() 输出 % 或者 \ 那就需要使用一些别的格式:

// 输出 99.9%
printf("99.9%%\n");
// 输出 一个反斜杠 \ 和一个换行
printf("\\\n");

此外 printf() 还有很多别的用法,可以参考:C 库函数 – printf() | 菜鸟教程

Math

前面提到,C 语言中的 math.h 是一个很常用的库,这里介绍一些常用的 math 库函数,不用全部记下来,主要以熟能生巧为主:

#include <stdio.h>
#include <math.h>

int main() {
    // 幂与指数
    // 计算 2 的 3 次方
    printf("pow(2, 3) = %.2f\n", pow(2, 3));
    // 计算平方根 √9
    printf("sqrt(9) = %.2f\n", sqrt(9));
    // 计算自然对数 ln(2.718)
    printf("log(2.718) = %.2f\n", log(2.718));
    // 计算常用对数 log10(1000)
    printf("log10(1000) = %.2f\n", log10(1000));
    // 计算 e^1
    printf("exp(1) = %.2f\n", exp(1));

    // 三角函数(参数是弧度制)
    // 计算正弦 sin(π/2),结果为 1
    printf("sin(pi/2) = %.2f\n", sin(M_PI/2));
    // 计算余弦 cos(0),结果为 1
    printf("cos(0) = %.2f\n", cos(0));
    // 计算正切 tan(π/4),结果为 1
    printf("tan(pi/4) = %.2f\n", tan(M_PI/4));
    // 计算反正切 atan(1),结果约为 π/4
    printf("atan(1) = %.2f\n", atan(1));
    
    // 取整与绝对值
    // 向上取整 ceil(2.3),结果为 3
    printf("ceil(2.3) = %.2f\n", ceil(2.3));
    // 向下取整 floor(2.7),结果为 2
    printf("floor(2.7) = %.2f\n", floor(2.7));
    // 四舍五入 round(2.5),结果为 3
    printf("round(2.5) = %.2f\n", round(2.5));

    // 取浮点数绝对值 fabs(-3.14),结果为 3.14
    printf("fabs(-3.14) = %.2f\n", fabs(-3.14));
    // 取整型绝对值 abs(-5),结果为 5
    printf("abs(-5) = %d\n", abs(-5));
    
    // 其他
    // 浮点数取余 fmod(5.3, 2.0),结果为 1.3
    printf("fmod(5.3, 2.0) = %.2f\n", fmod(5.3, 2.0));
    // 计算直角三角形斜边 hypot(3,4),结果为 5
    printf("hypot(3, 4) = %.2f\n", hypot(3, 4));
    // 取较大值 fmax(3.2, 5.6),结果为 5.6
    printf("fmax(3.2, 5.6) = %.2f\n", fmax(3.2, 5.6));
    // 取较小值 fmin(3.2, 5.6),结果为 3.2
    printf("fmin(3.2, 5.6) = %.2f\n", fmin(3.2, 5.6));
    return 0;
}

更多的数学函数,你可以参照:math.h | 菜鸟教程

Comments

注释是开发过程中极为重要的一部分,有句话说的好:写代码不写注释,连一个月后的自己都看不懂。在完成程序的同时,对一些关键点标记注释是极为重要的。

注释是代码的自然语言标注,不会被识别为真正的代码的一部分,但是却可以帮助你的同伴,也就是人类程序员更好地理解代码,并在你的代码上作出修改。

Inline Comments

第一种注释是行内注释,这种注释在前面的代码中也出现过:

// 这是行内注释
int n; // 这也是行内注释

行内注释会将 // 后面的文字全部标记为注释,一般而言行内注释遵循几点规范:

第一,行内注释单独提行,不要标注在代码的末尾

// (建议)这是一个好的注释
printf("QwQ"); // (不建议)这是一个不好的注释

第二,行内注释的 // 后面需要紧跟空格

// 这是一个好的注释(建议)
//这是一个不好的注释(不建议)

第三,行内注释一般用于注释代码的中间过程,也就是代码实现部分。

// (不建议)这是一个不好的行内注释,因为这个注释用来注释主函数,而不是逻辑
int main() {
    int n;
    // (建议) 这是一个好的行内注释,因为这个注释用来注释了代码的逻辑实现
    scanf("%d", &n);
}

这几点能让你的注释风格更为工整规范,便于阅读。

Block Comments

第二种注释是块注释,顾名思义就是成块的注释:

/**
 * 主函数
 * 程序的入口
 */
int main() {

其中,/* 标志着块注释的起始,*/ 标志着快注释的结尾。这两标志之间闭合的一个区域全部会被标记为注释。

一般而言,块注释也遵循几点规范:

第一,块注释单独提行

/**
 * (建议)这是一个好的块注释
 */
int main() {
    int n; /*
    (不建议)这是一个不好的注释
    */scanf("%d", &n);
}

第二,每一行块注释的起始位置需要打上*,块注释的第一行需要打两个 *,且一般不写内容,最长的那一列*一般对齐:

/**
 * (建议)这是一个好的注释
 */

/**

(不建议)这是一个不好的注释,因为每行的开头没有打上 * 号,导致块注释的起始和结尾不够明确

 */

第三,块注释的 * 号和文字之间需要打印空格

/**
 *这是一个不好的注释,因为第一个文字和符合之间没有空格(不建议)
 */

第四,块注释一般用于注释函数或者文档

/**
 * (建议)这是一个好的块注释,因为它被用来注释主函数
 */
int main() {
	int n;
    /**
     * (不建议)这不是一个好的块注释,因为它被用来注释中间过程
     */
    scanf("%d", &n);
}

块注释的特点是占用代码行数多,足够醒目,因此常常用来标记代码中较为重要的部分,而类似中间过程这样的次要部分使用块注释就显得有些打乱代码逻辑了。

遵循上面的这几点也能帮助你的代码变得更加工整规范。

Doxygen Style Comments

Doxygen 风格的文档注释能够帮助你在块注释中给注释信息添加额外的内容:

/**
 * @brief 计算两个整数的最大值
 * @param a 第一个整数
 * @param b 第二个整数
 * @return 返回较大的那个整数
 * @author Cao Xin
 */
int max(int a, int b);

在大部分的现代化 IDE (例如 VS Code)中,使用 Doxygen 风格的文档注注释能使得块注释更为清晰:

详细的 Doxygen 风格的文档注释介绍在这里:Doxygen 注释规范(C语言版)| Doxygen_stu

拓展阅读:Doxygen - 治好了我的代码注释强迫症 | 知乎

总而言之,注释风格的第一要义就是工整,写多了工程之后你就会明白,一份有着工整注释以及标准代码风格的的代码是多么的赏心悦目。

Flow Control

你已经学会了 C 语言中基本的语句与运算的使用了,但是你肯定不会满足:不是说写代码就是控制电脑干活吗?直接在主函数中按顺序执行代码似乎也不能实现所有的逻辑吧?

没错,但是在此之前让我们先回到一个经典问题:把大象放进冰箱需要几步?

你会说:打开冰箱,放进大象,关上冰箱。这就是顺序结构,也就是将任务按照顺序执行:

[打开冰箱] -> [放进大象] -> [关上冰箱]

但是显然的,大象并不能放进冰箱。

更形式化的,把一个东西放进冰箱要分几步?当然是:打开冰箱,判断是否可以放入并执行,关门。

这就是一个分支结构,即为经过判断之后选择执行的任务。

   [打开冰箱]
       |
  [能放下吗?]
   /       /
 [是]     [否]
   |        |
[放进去]  [无法放]
   /       /
   [关上冰箱]

同样的,如果我们有很多东西要放进冰箱,我们就需要不断的重复上面的过程:开门,判断是否放入并执行,关门。

这样不停的重复执行一系列任务的流程,我们称之为循环结构

[打开冰箱] 
      |
[还有东西?] -否-> [结束]
      |
     是
      |
[放入物品] -> [关上冰箱] -> 回到 [打开冰箱]

if and else

就像做判断题只有真假二值,C 语言中的判断语句也只有真假二值。

对于这样的只有真或者假的值,我们称之为布尔值(Boolean),因为这是一个叫 Bool 的科学家发明的。

在 C 语言中,我们可以通过引入头文件 <stdbool.h> 的方式在代码中使用布尔值:

#include <stdbool.h>
#include <stdio.h>

int main() {
    // 定义一个变量 ok, 值为假(false)
	bool ok = false;
	printf("false = %d/n", ok);
    // 修改变量 ok 值为真(true)
 	ok = true;
    printf("true = %d/n", ok);
}

运行得到的结果如下:

false = 0
true = 1

可以发现,实际上布尔值的 truefalse 实际上在 C 语言中就对应着 01

在 C 语言中,我们定义:不等于 0 的值为真

有了布尔值,那么相对应的我们就有操作布尔值的布尔运算符,运算结果为布尔值的布尔表达式

int a = 0, b = 2;
// 判断 a > b
printf("a > b = %d/n", a > b);
// 判断 a < b
printf("a < b = %d/n", a < b);
// 判断 a 是否与 0 相等
printf("a == a = %d/n", a == 0);
// 判断 a 是否不与 0 相等
printf("a == a = %d/n", a != 0);
// 判断 b 是否大于等于 0
printf("b >= 0 = %d/n", b >= 0);
// 判断 a 是否小于等于 0
printf("a <= 0 = %d/n", a <= 0);

得到的结果为:

a > b = 0
a < b = 1
a == a = 1
a == a = 0
b >= 0 = 1
a <= 0 = 1

在 C 语言中,为了区分赋值 a = 0 和判断 a == 0,判断 == 需要写两个等号。

在 C 语言中实现分支结构需要用到 if 以及 else 两个关键字,他们的用法也很直观:

int a, b;
scanf("%d %d", &a, &b);
    
// 如果 a 等于 b 就打印 "a == b"
if (a == b) {
	printf("a == b/n");
}

// 如果 a 大于等于 b 就打印 "a >= b",否则打印 "a < b"
if (a >= b) {
	printf("a >= b/n");
} else {
    printf("a < b/n");
}

if (a == b) {
    // 如果 a 等于 b 就打印 "a == b"
	printf("a == b/n");
} else if (a > b) {
    // 否则就判断 a 是否大于 b,如果是,打印 "a > b"
    printf("a > b/n");
} else {
    // 否则打印 "a < b"
    printf("a < b/n");
}

而对于我们需要同时判断多个逻辑表达式的情况,我们可以用逻辑运算符逻辑与&&逻辑或 || 连接:

int a = 0, b = 1;
// a = 0 且 b = 1 就打印
if (a == 0 && b == 1) {
    printf("a = 0 and b = 1\n");
}

// a = 0 或 b == 0 就打印
if (a == 0 || b == 0) {
    printf("a = 0 or b = 0\n")
}

简单来说,|| 运算符是判断两者之一满足就为 true,而 && 运算符就是判断两者都满足才为 true

我们再来看下面一段代码:

int a = 0, b = 1, c = 1;
if (a || b && c) {
    printf("Condition 1 is true\n");
} else {
    printf("Condition 1 is false\n");
}

// 等价于:
if (a || (b && c)) {
    printf("Condition 2 is true\n");
}

运行结果:

Condition 1 is true
Condition 2 is true

也就是说,就像加减乘除存在先乘除后加减的运算规则,C 语言内也存在一套运算规则,我们叫它运算符优先级

在上面的例子中, C 语言先执行了 && 运算再执行 ||

C 语言详细的运算符优先级可以看这一篇文章:C语言运算符优先级详解 - 知乎

不过在大部分情况下,如果你拿不准运算符优先级,直接使用括号改变优先级就好了(虽然考试可能会直接考你运算符优先级)。

这就是 C 语言中 ifif - elseif - eles if - else 语句的用法。

for, while and do-while

讲完了分支结构,下一步就该学习循环结构了。

仔细思考我们生活中出现的循环结构:

今天你有 5 节课,每节课都要签到签退,总共签到签退五次,这是一个循环次数确定的循环结构。

你每天都要起床、吃饭、睡觉,这种苦日子不知道什么才能时候结束,这是一个循环次数、循环终点都不确定的循环结构。

在 C 语言中,针对这两种情况,有不同的循环语法可以实现。

for

int a = 10;
// int i = 1,初始时 i = 1
// i < 5,每次判断 i 是否满足 [i < 5] 这一条件,若满足,则执行 printf("a[%d] = %d\n", i, a++);
// 大括号内的语句执行完之后,执行 i++
for (int i = 0; i < 5; i++) {
    // 打印 i 和 a,然后使得 a 增加 1 
	printf( "a[%d] = %d\n", i, a++);
}

运行代码之后可以得到:

a[0] = 10
a[1] = 11
a[2] = 12
a[3] = 13
a[4] = 14

简单来说,这个 for 循环会总共会执行 5 次,每次都会执行循环体内的语句,每次都会使得 i++

形式化的,我们给出 for 循环的语法定义:

for (init; condition; increment) {
   statement(s);
}
  1. init初始化,会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。
  2. 接下来,会判断条件condition。如果为真,则执行循环体,也就是 statement(s)。如果为假,则不执行循环体,且控制流会跳转到紧接着 for 循环的下一条语句,也就是 。
  3. 在执行完 for 循环体后,控制流会跳回上面的 increment 也就是增量语句。
  4. 条件 condition 再次被判断。如果为真,则执行循环,这个过程会不断重复。在条件变为 condition 假时,for 循环终止。

尽管根据语法 initconditionincrement,三条语句其实都可以为空(可以思考一下这三条为空的时候 for 循环的运行过程),但是在实际使用中,十分不建议采用这种写法,会导致一些代码可读性上的困难。大部分时候,对于 for 循环语句可以为空的情况,我们一般使用后面会提到的 while 循环解决,可以增加代码的可读性。

while and do while

int cnt = 0;
// 如果 cnt < 10,那么就执行语句 printf("cnt = %d\n", cnt++);
while (cnt < 5) {
    // 打印 "cnt = %d\n" 之后将 cnt 自增 1
    printf("cnt = %d\n", cnt++);
}

运行之后可以得到:

cnt = 0
cnt = 1
cnt = 2
cnt = 3
cnt = 4

如果你仔细观察,你会发现 while 语句的结构和我们之前的 if 语句非常类似。

形式化的,我们给出 while 循环语句的语法定义:

while (conditions) {
	 statement(s);
}

简单来说,在执行 while 语句的时候会首先判断 conditions,如果为 ture 就执行 statement(s) 部分的语句也就是执行循环体。

除此之外,do-while 语句的用法也是类似的:

int a = 5;
// 先打印 a ,再使 a 自减 1
do {
    printf("a = %d\n", a);
} while (a--);

int b = 5;
// 先使 b 自减 1 并判断假值,再打印 b
while (b--) {
    printf("b = %d\n", b);
}

运行代码你会发现:

a = 5
a = 4
a = 3
a = 2
a = 1
a = 0

b = 4
b = 3
b = 2
b = 1
b = 0

很显然,do-while 语句先执行循环体,再执行判断语句,而 while 语句则是先执行判断语句,再执行循环体

break and continue

就像刚才在 for 循环一章中提到的, 实际上你可以意识到一件事:for 循环语句和 while 循环语句是可以相互转化的。

那么这是怎么实现的呢?首先我们看一段代码:

int cnt = 0;
while (true) {
	printf("cnt = %d\n", cnt++);
}

很显然这个循环会不停地执行下去,除非你手动关闭这个程序。

此时如果我加上:

int cnt = 0;
while (true) {
    // 判断 cnt < 5, 为真就执行打印,为假就退出整个循环
	if (cnt < 5) {
		printf("cnt = %d\n", cnt);
	} else {
		break;
	}
}

那么这段代码就等价于:

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

那么前面那种写法的好处是什么呢?

这种写法使得 while 循环变为了一种更为通用的循环,也就是 while 语句只专注于循环本身。

如果某个循环的退出条件不那么明确,例如有很多的退出条件的情况,将退出条件写到循环的内部就可以省去很多逻辑梳理上的麻烦:

int a = 0;
int b = 10;
while (true) {
    // 判断 a < 10
    if (a < 10) {
        printf("a = %d\n", a);
        a++;
        // 如果 a 是 5 的倍数,就退出
        if (a % 5 == 0) {
            break;
        }
    }

    // 如果 b > 15 就退出
    if (b <= 15) {
        printf("b = %d\n", b);
        b++;
    } else {
        break;
    }
}

运行后得到:

a = 0
b = 10
a = 1
b = 11
a = 2
b = 12
a = 3
b = 13
a = 4

这段代码的逻辑就异常复杂了,不仔细分析你根本就不知道循环会在哪里退出。

不过尽管如此,相信你不用分析也能理解这段代码在干什么:分别处理 ab 的值并判断是否满足退出条件。

这就是 break 的作用:使得循环的终止条件更为灵活,增加代码可读性。breakfor 循环同样适用。

理解了 break 循环,理解 continue 就很简单了:

int cnt = 0;
while (true) {
    cnt++;
    if (cnt < 3) {
        printf("ready\n");
        // 直接跳回循环体头,不执行下面的判断
        continue;
    } else if (cnt <= 5) {
        printf("cnt = %d\n", cnt);
    } else {
        // 退出循环
        break;
    }
}

运行代码可以得到:

ready
ready
cnt = 3
cnt = 4
cnt = 5

也就是说 break退出整个循环,而 continue 则是退出当前这一次循环。

同样的,continue 语法也适用于 for 循环。

此时,我们就可以引出模运算的一些经典用法:

// 输入一个数并判断是否是偶数
int n = 0;
scanf("%d", &n);
if (n % 2 == 0) {
  	// 是偶数就输出 Even
    printf("Even\n");
} else {
  	// 是奇数就输出 Odd
    printf("Odd\n");
}
// 使得 cnt 以 5 个数为一个周期变化
int cnt = 0;
for (int i = 0; i < 100; i++) {
    printf("cnt = %d", cnt);
    cnt %= 5;
}

学到了这里,你就能够轻松地描述任何逻辑了。

How to begin?

作为一名 C 语言初学者,同时作为一名生活在 21 世纪的青年,请你一定要学会使用互联网资源学习,不要死读书。

在这里给大家推荐一些很好的线上资源:

  1. C 语言教程 | 菜鸟教程:菜鸟教程,非常适合初学者接触:
  2. CS自学指南:CS自学指南,非常著名的一部线上教程,涵盖了计算机与电子信息等专业相关的自学指路
  3. 洛谷:算法竞赛(主要是 NOI)的训练平台,平台上的入门题单很适合初学者学习,代码解也很全
  4. 《C Primer Plus(第6版)》中文版PDF下载 - C语言中文网:如果你喜欢看书,C Primer Plus 是一个很不错的选择
  5. AI:不必多说,神中神
  6. 搜索引擎:更是不必多说,Bing 或者 Google 能帮你找到互联网上几乎一切资源

声明:Blog|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - [Debug 2025 Freshman] CP Chapter 02 IO and Flow Control