第三章 分支控制流程简介
为了能写一个完整一些的程序,需要先学一些分支控制流
文章头可下载笔记
3.1 前导程序
#include <stdio.h>
int main()
{
int a;
// 判断语句 判断 scanf() 的返回值 是否 等于 1
// 如果scanf() 的返回值等于1 表示成功读取到a的值了
if (scanf("%d", &a) == 1){
printf("读取到a的值为 %03d \n", a);
}else {
printf("读取出错,未能读取到 a 的值\n");
}
}
3.2 关系运算符
运算符 | 作用 |
---|---|
== | 比较左右两个数是否 相等 |
< | 比较左边的数是否 小于 右边的数 |
> | 比较左边的数是否 大于 右边的数 |
<= | 比较左边的数是否 小于等于 右边的数 |
>= | 比较左边的数是否 大于等于 右边的数 |
3.3 if 语句
3.3.1 if 语法
// expr 意思是 表达式语句 stat是一条语句(语句还包括 复合语句)
if ( expression )
statement
if意为如果,表判断,如上面的语句:
// 如果 scanf() 的返回值 等于 1
if (scanf("%d", &a) == 1)
如果 成功读入了一个整数赋给了 a ,那么 scanf() 返回值就是1 ,此时
1 == 1
是对的,用专业术语表达就是 此时 scanf(“%d”, &a) == 1 这个表达式的结果是 true(结果为真);
如果用户输入了 c 等非整数的字符,那么 scanf(“%d”, &a) 的返回值就是0,此时就说 scanf(“%d”, &a) ==1 是 false (结果为假)
不管是 true/false 还是 结果为 真/假 都是可以描述 关系表达式的
注:关于 true / false 这样的值在 C 语言中是由 1/0 表示的。1表示true(真),0表示false(假)
接下来看 if 后面的语句:
if 后面可以是一条表达式语句,也可以是一条复合语句(复合语句就是 由花括号{} ,在花括号里面可以有很多条 语句,复合语句不需要在右花括号后面加一个分号 ’ ; ’ )
只有当 if () 括号里的 关系表达式为真 时,才会执行紧跟在 if 后面的 语句(statement)
如:
if (scanf("%d", &a) == 1)
printf("%d\n", a);
// 或
if (scanf("%d", &a) == 1)
{
printf("%d\n", a);
}
以上两种写法是一样的
注:if 语句是一种选择语句,在 C 中有 6 种语句。分别是:标号语句,复合语句(上面有说到),表达式语句(比如:if里的条件表达式语句),选择语句(if语句),迭代语句,跳转语句(见过,比如:main函数中的 return 0; 语句)。
对于上面的 6 种语句我在后面会慢慢一一介绍清楚的,不必急,现在只需记住 if 语法需要使用两个语句
关于语句还有两个值得注意的点:
-
int a; 这样的句子不是一条语句,这在学后面的 选择语句 时将会证明,不过现在也可以做一个简单的证明
// 刚说到 在if 关系表达式 括号之后 需要使用一条语句 // 1 是 真 if (1) int a; // 声明定义一个变量 a
把这两行代码放到 main 函数中,进行编译,编译器会提示 在int之前预期一条表达式(语句)
这就已经能够初步验证了 声明定义变量 的句子不是一条语句
-
空语句
空语句 由 一个 ’ ; ’ 分号构成,如:
// C中 0 表示 假值false if (0) ;// 只有一个分号,表示一个空语句 printf("空语句\n"); // 或 if (0) ; printf("空语句\n");
把这两个形式的代码任意测试一条,都会发现即使 if 括号里面的 值为假; 也依然会执行printf() 函数的句子
这足以证明 一个分号 是一条语句
需要注意的是,空语句是C语言中一条特殊的语句,它不属于以上的任何一种语句
3.3.2 if - else
else 是搭配 if 语句使用的一种语句,意为 否则
它会自动匹配 相距 最近的一条 if 语句
下面是 else 的语法
// else 需要搭配 if 语句使用
if (expression)
statement
else
statement
看前导程序的例子:
else {
printf("读取出错,未能读取到 a 的值\n");
}
意思表示,如果 if 括号里的 关系表达式值为假时,执行 else 后面的语句
不知道读者有没有注意到 else 后面 需要使用一条语句 ,是不是说 可以在 else 后面跟上上面提到的 六种语句 与特殊的 空语句 呢?
事实上,除了标号语句外,其他的语句都可以跟在else后面(但是有一些语句的使用有一定的限制,所以不能无脑跟除标号语句外的任何语句,实际上也不会无脑跟),这意味着 可以在else 后面接一条 选择语句,以形成嵌套选择语句
嵌套是字面意思,至于如何进行嵌套 笔者已经说清楚了,只需在 else 后面在接一条 if 选择语句,这个嵌套的例子笔者就不给了,就由读者来完成吧。对于嵌套的内容,笔者将在后面的章节中,更加深入的解释它。因此读者现在还是少些使用嵌套吧
注意:虽然 else 是一条语句,但是它需要与 if 搭配,就是说 else 不能成为 if 后面的语句,因此,在 if 与 else 之间一定要有一条语句,哪怕是一条空语句。
3.4 switch 语句
switch 也是一种选择分支语句
3.4.1 switch 语法
看格式:
switch ( 整型表达式 )
{
case 整型常量1:
代码块 // 可选
case 整型常量2:
代码块
default:// 可选
代码块
}
整形表达式的意思大概可为,最后求值为整型的表达式,这里不要求是一个常量,我们可以如下:
int a = 0;
switch (a)
{
case 0:
代码块0
case 1:
代码块1
default:
代码块3
}
不过需要注意的是,case 后面 必须是整型常量,你不能使用 浮点数常量去代替,这时非法的行为(这里指的非法是指不符合甚至是违反 C语言语法规则的意思)
在 case 后面跟着 代码块,代码块的是 由多条代码组成的,最小的代码块只有一条语句,(有时 由花括号{} 括起来的 复合语句 我们也会称为代码块,事实上我们只会把 花括号括起来的 代码 称为代码块),不过在这里的代码块加不加花括号都可以
当a的值 与某个 case 后面的整型常量 相等 时,将会执行其后面的语句,比如上述例子,a为0,则 执行代码块0
default 意思是默认,也就是说 当 a 的值 不等于 任何case 后面的整型常量时,就会执行default 默认 的语句
像 case 和 default 我们都称其为 分支 也可称为 标签 。分支针对的是选择结构,标签针对的是 switch语句
注意:case 后面出现的整型常量只能出现一次!不能出现两个标签值一样的情况
接下来请做一个练习:
要求1:读取用户的一个整型输入,如果输入不是整型,输出错误信息并结束程序
要求2:自己为这个整型输入一个意义(比如是奥运会跳水裁判的评分),接着使用 switch 语句判断 a 的值,只需要写出 2个case ,一个 default 三个分支
要求3:为每个分支 后面接一条 printf 语句,每一条都要输出不同的内容 (只要能辨别执行了哪一个分支即可)
要求4:至少测试3次程序,并且从 default 分支往上测试,看看每次的测试有什么不一样 (比如上面的例子,我第一次给 a 一个2,它将执行default后面的语句,然后再给a 一个 1,然后它执行 case 1: 分支后面的语句)
注意:练习会影响到后面的知识点的展开,请务必敲代码做练习,代码写得慢也没关系!
3.4.2 switch - 运作原理
上面的练习做得如何?是否会感受到编码的乐趣?测试又做得怎么样了?是否发现有什么不妥得地方?
各位可能注意到了,当 输入的整数值是 两个case 分支时,得到的 输出好像不止一句?这是为何?
想要知道这些问题的答案我们需要了解 switch 的运作原理:
- 第一步是对 switch () 括号里的表达式进行求值
- 第二步是逐个对比 case 后面的 整型常量,找到相等的那个
- 第三步如果有对应的case 或 defalut 则跳转到对应的 case 或 default 位置,然后才能执行后面的代码,否则将会结束 switch 语句的执行
想必读者在看到 第三步时已经看出一点端倪了,没错,问题就出在这里,因为在switch 后面紧跟着的 由花括号括起来的代码块中,case 语句 与 default: 语句都属于 switch 的标签,而且这个标签只限制在switch空间中,并且只能由switch跳转,且只会跳转一次
也就是说 不管是 case 还是 default: 都是供给switch 来跳转使用的标签,它们只是告诉switch语句,如果 switch括号里 整型表达式的值 能在 case 找到的话,就 跳转到该 case 标签的位置去执行代码,而不是 由switch 去对比每个case 的值,并且在执行对应的代码块后遇到下一条 case 标签 或 default 标签时,又接着对比 与case 的值
小小总结一下:首先switch 由整形表达式 与 后面的代码块组成,开始时 switch 对 整型表达式 进行求值,然后对比 它与case标签后的值(实际上编译器会用比直接对比值还要高效的方式在 case 中查找 整型表达式的值),如果有对应的值则跳转到该标签位置 执行一切可到达的代码,如果没有则跳到 default 标签(如果有default标签),否则就执行 switch 之后的代码
读者在读到什么叫 执行一切可到达的代码 ,意思是 程序在逻辑上可以运行到的地方,比如:
if (1)
printf("逻辑上 程序可运行到");
else
printf("逻辑上 程序永远运行不到这里");
因为 if 中的 关系表达式的值 永远为 真(true),其后的printf() 就会 一直可以运行到,而在 与其搭配使用的 else后的 printf() 表达式语句在代码逻辑上永远也不会执行
因此可以有下代码:
int a;
scanf("%d", &a);
switch (a)
{
case 1:
printf("a=%d\n", a);
if (a > 2){
case 2:
printf("a可能大于 2 ,也可能等于 2\n");
}
}
我们把上面的代码放进 **主函数 main(也叫程序入口)**中,进行编译,发现编译器并未报错或有提示警告
接着运行它,发现,当我们 键入 1 (敲键盘输入 1, 就叫键入1)时,程序只有一行输出,且不会运行第二条 printf() 打印语句,因为此时,a==1,在执行第一条语句后,进行 if 语句中的判断 a>2 的 关系表达式 的值为 假 ,因此 case 2 标签后的 printf() 表达式语句在逻辑上是不可到达的,因此会跳过这一句
再次重新运行程序,这次我们键入 2 ,发现 会直接打印第二条 printf()表达式语句里面的字符,虽然 a>2 的关系表达式的值依然为假(因为 2等于2 ,而不大于 2),但是我们 的switch语句丝毫不在乎这件事,它只知道有一个 case标签 的值为2,于是它让程序直接 跳转 到case2 中继续执行代码,这与 if语句的关系表达式无关,因为根本就没有运行 if语句的关系表达式
初学者 与 学过部分C或其他语言的 读者可能会对 上面这样的代码结构感到新奇,因为 直接把 case标签 插入if语句的复合语句中可能是见所未见的
但是我们还是要清楚 switch的 本质概念,switch本质上就是 switch + 小括号与整型表达式 + 后面代码块, 即:
switch (/* 整型表达式 */)
{
/* 代码块 */
}
然后再根据自己的需要在里面 插入 一些 case标签 或 default标签 ,然后当 整形表达式 求到的值与之有对应时,就跳转到对应的地方执行代码。根据需要插入……? 也就是说我们还可以有下面这样颠倒思维的代码:
switch (/* 任意整型表达式 */)
{
/* 没有任何case标签 与 default标签 的代码块 */
}
对,即使我们写出上面这样令人费解的代码(不插入 case 为什么要使用switch 和 这段代码块),也能通过编译,只不过编译器会发出一个 提示警告:表示 代码块中的代码将永远都不会执行!。虽然编译器发出了警告,但是我们也可以选择忽略掉它们(然而在任何情况下都不要轻易胡罗编译器发出的警告,因为有时这些警告可能恰恰就是让我们程序崩溃的原因),继续运行程序,发现果然永远不会执行代码块里的代码
还可以有下面这样颠倒思维的代码:
switch (/* 任意整型表达式 */)
{
/* 没有任何case标签 与 default标签 的代码块 */
}
对,即使我们写出上面这样令人费解的代码(不插入 case 为什么要使用switch 和 这段代码块),也能通过编译,只不过编译器会发出一个 提示警告:表示 代码块中的代码将永远都不会执行!。虽然编译器发出了警告,但是我们也可以选择忽略掉它们(然而在任何情况下都不要轻易胡罗编译器发出的警告,因为有时这些警告可能恰恰就是让我们程序崩溃的原因),继续运行程序,发现果然永远不会执行代码块里的代码
练习
自己搜文心一言,使用ai “目前我正在学习C语言,但是仅仅学了 printf() 与 sanf()函数、与if和switch、还有变量的定义,没有学运算符,请帮我出几道我能做的题给我做”
向 ai 表达以上的大概意思即可,然后自己练
总结
自己在做完练习后做一些总结,没必要一定要使用手写做笔记,效率太低了
可以使用一些 如 《有道云笔记》 这样的电脑软件工具
也可以自己学习一些 md 文件的语法来做笔记