Linux C编程一站式学习笔记 chap4 分支语句
文章目录
- Linux C编程一站式学习笔记 chap4 分支语句
- 一.if语句
- 语句块
- 习题
- 二.if/else语句
- 引例
- if/else语句 语法规则
- if else 的配对原则
- 习题
- 1、写两个表达式,分别取整型变量`x`的个位和十位
- 2、写一个函数,参数是整型变量`x`,功能是打印`x`的个位和十位
- 三.布尔代数
- 习题
- 四.switch语句
- 语法格式
- 例程
- switch语句的注意点
- 意外收获一堆宝藏
- debug可视化
- json可视化
- 自动生成流程图
- csv可视化
- 相关资源、参考资料
这本书在和CS61A一起学,感觉两者教学上有些地方手法类似,都做的很棒
一.if语句
之前的程序中语句是从前到后顺序执行的。除了顺序执行之外,有时我们需要检查一个条件,然后根据检查的结果执行不同的后续代码,在C语言中可以用实现,比如:
if (x != 0) {
printf("x is nonzero.\n");
}
其中x != 0
表示“x不等于0”的条件,这个表达式称为控制表达式(Controlling Expression)如果条件成立,则{}中的语句被执行,否则{}中的语句不执行,直接跳到}后面。if
和控制表达式改变了程序的控制流程(Control Flow),不再是从前到后顺序执行,而是根据不同的条件执行不同的语句,这种控制流程称为分支**(Branch)**
- if后面的括号中是逻辑表达式,如果表达式所表示的比较关系成立则值为真(True),否则为假(False),在C语言中分别用
int
型的1和0表示,常用的符号有:==和!=称为相等性运算符(Equality Operator),其余四个>,<,<=,>=
称为关系运算符(Relational Operator),相等性运算符的优先级低于关系运算符 - 如果变量
x
的值是-1,那么x>0
这个表达式的值为0,x>-2
这个表达式的值为1,这里面有一些易错点==
表示数学中的相等关系,相当于数学中的=号,初学者常犯的错误是在控制表达式中把==
写成=,在C语言中=号是赋值运算符- 在数学中
a<b<c
表示b
既大于a
又小于c
,但C语言会先计算a<b的结果得到0或者1,再把0或1继续参与运算 - 整型或者都是浮点型可以直接做比较,但两个字符串不能用大于小于这些符号直接作比较
语句块
-
在C语言中,任何允许出现语句的地方既可以是由;号结尾的一条语句,也可以是由{}括起来的若干条语句或声明组成的语句块(Statement Block),语句块和上一章介绍的函数体的语法相同。
-
注意语句块的}后面不需要加;号。如果}后面加了;号,则这个;号本身又是一条新的语句了,在C语言中一个单独的;号表示一条空语句(Null Statement)。上例的语句块中只有一条语句,其实没必要写成语句块,可以简单地写成:
if (x != 0) printf("x is nonzero.\n");
-
语句块中也可以定义局部变量,例如:
void foo(void) { int i = 0; { int i = 1; int j = 2; printf("i=%d, j=%d\n", i, j); } printf("i=%d\n", i); /* cannot access j here */ }
和函数的局部变量同样道理,每次进入语句块时为变量
j
分配存储空间,每次退出语句块时释放变量j
的存储空间。语句块也构成一个作用域,如果整个源文件是一张大纸,foo
函数是盖在上面的一张小纸,则函数中的语句块是盖在小纸上面的一张更小的纸。语句块中的变量i
和函数的局部变量i
是两个不同的变量,因此两次打印的i
值是不同的;语句块中的变量j
在退出语句块之后就没有了,因此最后一行的printf
不能打印变量j
,否则编译器会报错。语句块可以用在任何允许出现语句的地方,不一定非得用在if
语句中,单独使用语句块通常是为了定义一些比函数的局部变量更“局部”的变量。
习题
There is a semicolon (;
) after the if
statement, which causes the following printf
statement to be executed unconditionally, regardless of the value of x
.
Regarding why the code compiled without error, this is because the semicolon is a valid statement terminator in C. It is commonly used to end simple statements such as if
and while
statements, and is also used to separate multiple statements on the same line. However, in this case, the semicolon is being used incorrectly and is causing a semantic error.
The C compiler does not check for semantic errors, only for syntax errors. It is the responsibility of the programmer to ensure that their code is semantically correct.
二.if/else语句
引例
-
if
语句可以带一个else
子句(Clause),例如:if (x % 2 == 0) printf("x is even.\n"); else printf("x is odd.\n");
这里的%是取模(Modulo)运算符,
x%2
表示x
除以2所得的余数(Remainder),C语言规定%运算符的两个操作数必须是整型的。%运算符的结果总是与被除数同号来尝试证明一下,注意C语言中整数的除法 的 Truncate Toward Zero 规则
设 a 为被除数,b 为除数,q 为商,r 为余数。
首先,我们知道有这样一个等式: a = b * q + r
其中,q 是向下取整的商,即 q = floor(a/b)。
我们知道a = b * q + r, 且0 <= r < |b|,因此
- 当 a > 0, b > 0 时,r > 0; # q是向下取整的商,所以b * q肯定够不到 a 那么大
- 当 a > 0, b < 0 时,r > 0; # 显然
- 当 a < 0, b > 0 时,r < 0; # 显然
- 当 a < 0, b < 0 时,r < 0; # q是向下取整的商,所以b * q肯定够不着 a 那么小
所以,无论 a 和 b 的符号如何,余数 r 的符号总是和被除数 a 的符号相同。
所以在C语言中,%运算符的结果总是与被除数同号
取模运算在程序中是非常有用的,例如上面的例子判断
x
的奇偶性(Parity),看x
除以2的余数是不是0,如果是0则打印x is even.
,如果不是0则打印x is odd.
如果在上面的例子中去掉
else
,则不管x
是奇是偶,printf("x is odd.\n");
总是执行。为了让这条语句更有用,可以把它封装(Encapsulate)成一个函数:void print_parity(int x) { if (x % 2 == 0) printf("x is even.\n"); else printf("x is odd.\n"); }
-
把语句封装成函数的基本步骤是:把语句放到函数体中,把变量改成函数的参数。这样,以后要检查一个数的奇偶性只需调用这个函数而不必重复写这条语句了,例如:
print_parity(17); print_parity(18);
if/else语句 语法规则
-
语句 → if (控制表达式) 语句 else 语句
-
右边的“语句”既可以是一条语句,也可以是由{}括起来的语句块。
-
一条
if
语句中包含一条子语句,一条if/else
语句中包含两条子语句,子语句可以是任何语句或语句块,当然也可以是另外一条if
或if/else
语句。 -
根据组合规则,
if
或if/else
可以嵌套使用。例如可以这样:if (x > 0) printf("x is positive.\n"); else if (x < 0) printf("x is negative.\n"); else printf("x is zero.\n");
也可以这样:
if (x > 0) { printf("x is positive.\n"); } else { if (x < 0) printf("x is negative.\n"); else printf("x is zero.\n"); }
-
if else 的配对原则
-
很多编程语言的语法都有这个问题,称为Dangling-else问题
-
Dangling-else problem is a problem that arises in languages such as C and C++ when the syntax of the language does not specify the association of an “else” statement with the closest preceding “if” statement. This can lead to confusion about which “if” statement the “else” statement is associated with, and can result in unexpected behavior in the program. To avoid this problem, programmers often use braces(花括号) to explicitly specify the association between an “if” statement and its corresponding “else” statement.
-
C语言规定,
else
总是和它上面最近的一个if
配对 -
现在有一个问题,类似
if (A) if (B) C; else D;
形式的语句怎么理解呢?可以理解成if (A) if (B) C; else D;
也可以理解成
if (A) if (B) C; else D;
C代码的缩进只是为了程序员看起来方便,实际上对编译器不起任何作用,你的代码不管写成上面哪一种缩进格式,在编译器看起来都是一样的
-
那么编译器到底按哪种方式理解呢?也就是说,
else
到底是和if (A)
配对还是和if (B)
配对?按照C语言的规定,应该理解成else
和if (B)
配对,也就是按第二种方式理解。如果你写成上面第一种缩进的格式就很危险了:你看到的是这样,而编译器理解的却是那样。 -
如果希望编译器按第一种方式理解,应该明确加上braces:
if (A) { if (B) C; } else D;
习题
1、写两个表达式,分别取整型变量x
的个位和十位
int y;
y = x % 10; //个位
int z;
z = (x / 10) % 10 //十位
2、写一个函数,参数是整型变量x
,功能是打印x
的个位和十位
void printX(int x)
{
printf("%d",x%10);
printf("%d",(x/10)%10);
}
10位也可以是(x%100)/10
三.布尔代数
现在在并行看的书很多,在补大学前两年半欠下的债…
看着看着就会发现很多知识之间的关联性,以及一本好书,一份好的资料对于计算机思维的启迪, 而且环环相扣,很多概念的引入都非常自然
-
我在想当时大一的时候,学校的C语言这一块可以多讲讲吧,我觉得这个和计算机的01很接近,可以作为计算机启蒙的一块很好的切入点…但是当时emm
-
引入:
a<b<c 这个用C语言怎么写
if (a < b) { if (b < c) { printf("b is between a and c.\n"); } }
或者使用逻辑与(Logical AND) 运算符表示这两个条件同时成立
if (a < b && b < c) { printf("b is between a and c.\n"); }
-
逻辑代数的内容
- 可以看我以前写的这篇博客
习题
1、把代码段
if (x > 0 && x < 10);
else
printf("x is out of range.\n");
改写成下面这种形式:
if (____ || ____)
printf("x is out of range.\n");
____应该怎么填?
- 这个应该可以用德摩根律, 啊不看错了可以用也可以不用吧,好家伙,if 那后面就是分号😂,刚发现
- 那就简单了
if( x <= 0 || x >= 10)
- emm,理理思路,这些逻辑代数的知识确实可以用来检验if else语句是否达到自己想要的逻辑
- 令
a = x>0, b = x<10
, if条件是a*b
,那么else 就是¬(a*b)
也就是¬a+¬b
- 令
- 那就简单了
2、把代码段:
if (x > 0)
printf("Test OK!\n");
else if (x <= 0 && y > 0)
printf("Test OK!\n");
else
printf("Test failed!\n");
改写成下面这种形式:
if (____ && ____)
printf("Test failed!\n");
else
printf("Test OK!\n");
____应该怎么填?
-
if ( x <= 0 && y <= 0)
吧, 但是好像脑子里没有很清晰的处理思路…-
两个变量,也许可以画个坐标轴线性规划?这个挺好的感觉
-
if和else if的两个条件可以看为
a+¬a*b=a+b
, 那么剩下的就应该是这个条件的补集,也就是¬(a+b)=¬a*¬b
-
3、有这样一段代码:
if (x > 1 && y != 1) {
...
} else if (x < 1 && y != 1) {
...
} else {
...
}
要进入最后一个else
,x和y需要满足条件____ || ____。这里应该怎么填?
-
x==1||y==1
-
可以画坐标系
-
也可以用德摩根律
令
a为x>1,b为y!=1,c为x<1
那前两个条件合起来就是
a*b+c*b
然后德摩根律取反得到
(¬a+¬b)*(¬c+¬b)
-
4、以下哪一个if判断条件是多余的可以去掉?这里所谓的“多余”是指,某种情况下如果本来应该打印Test OK!
,去掉这个多余条件后仍然打印Test OK!
,如果本来应该打印Test failed!
,去掉这个多余条件后仍然打印Test failed!
。
if (x<3 && y>3)
printf("Test OK!\n");
else if (x>=3 && y>=3)
printf("Test OK!\n");
else if (z>3 && x>=3)
printf("Test OK!\n");
else if (z<=3 && y>=3)
printf("Test OK!\n");
else
printf("Test failed!\n");
-
一开始直觉上的判断👇
-
我感觉第三个和第四个else if 合并起来 , 就等价于 第二个 else if,所以可以把第二个 else if
else if ( x>=3 && y>=3 )
去掉
-
-
后来推理了一下
-
三维坐标系不好画,还是用公式吧
-
其实这题的意思就是让我们化简逻辑表达式
-
在这之前要先推导这两个公式 ,多余项定律
名称 公式1 公式2 化简目的 多余项定律 A B + A ‾ C + B C = A B + A ‾ C AB+\overline{A}C+BC=AB+\overline{A}C AB+AC+BC=AB+AC ( A + B ) ( A ‾ + C ) ( B + C ) = ( A + B ) ( A ‾ + C ) (A+B)(\overline{A}+C)(B+C)=(A+B)(\overline{A}+C) (A+B)(A+C)(B+C)=(A+B)(A+C) 消多余项 在一个与或表达式中有三项,其中两项中有一因子以原变量和反变量形式分别出现,这两项中剩余因子共同构成第三项,则该项多余,可消去
-
-
推导出来也是第二个语句是多余的
-
不过网上有个大佬的笔记里面说第四个是多余的🤔,链接如下
https://www.zybuluo.com/ChristopherWu/note/72463,感觉他说的也有道理,目前我也不确定了
不过感觉大佬这里笔误了所以导致做错了?
-
-
-
这几个练习非常有启发性,我感觉在数电的教学里面就可以把C语言的
if else
作为引例
四.switch语句
语法格式
switch (控制表达式) {
case 常量表达式: 语句列表
case 常量表达式: 语句列表
...
default: 语句列表
}
例程
#include <stdio.h>
void print_day(int day)
{
switch(day) {
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("illegal day number!\n");
break;
}
}
int main(void)
{
print_day(2);
return 0;
}
-
流程是这样的
如果传入的参数是2,则从
case 2
分支开始执行,先是打印相应的信息,然后遇到break
语句,它的作用是跳出整个switch
语句块。C语言规定各case
分支的常量表达式必须互不相同,如果控制表达式不等于任何一个常量表达式,则从default
分支开始执行,通常把default
分支写在最后,但不是必须的 -
pythontutor NB 啊!
switch语句的注意点
case
后面跟表达式的必须是常量表达式,这个值和全局变量的初始值一样必须在编译时计算出来。- 浮点型不适合做精确比较,所以C语言规定
case
后面跟的必须是整型常量表达式。 - 进入
case
后如果没有遇到break
语句就会一直往下执行,后面其它case
或default
分支的语句也会被执行到,直到遇到break
,或者执行到整个switch
语句块的末尾。通常每个case
后面都要加上break
语句,但有时会故意不加break
来利用这个特性,例如
#include <stdio.h>
void print_day(int day)
{
switch(day) {
case 1:
case 2:
case 3:
case 4:
case 5:
printf("Weekday\n");
break;
case 6:
case 7:
printf("Weekend\n");
break;
default:
printf("illegal day number!\n");
break;
}
}
int main(void)
{
print_day(2);
return 0;
}
-
流程如何
用pythontutor来看看
-
这一步在switch
-
下一步跳到print
-
-
switch
语句不是必不可缺的,虽然可以用一组if ... else if ... else if ... else ...
代替,但是一方面用switch
语句会使代码更清晰,另一方面,有时候编译器会对switch
语句进行整体优化,使它比等价的if/else
语句所生成的指令效率更高
意外收获一堆宝藏
-
受到之前pythontutor的启发,在谷歌上面直接搜了一下
visualize c code
又发现宝藏啦
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjL6kgaI-1674368512156)(null)]
呃但是有bug
除了py之外都报下面这个error
-
这个live模式是真的NB!
https://pt.jjk.is/live.html#mode=edit
实现了我之前想过的实时可视化Orz!!!
Soga,所以这个相比于pythontutor就是,这个只能可视化python,但是可以实时可视化!
我猜Vscode 插件肯定有可视化
于是去搜了一下,果真
debug可视化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BmtjIRcT-1674368504405)(https://cdn.jsdelivr.net/gh/xin007-kong/picture_new/img/20230122132556.png)]
json可视化
自动生成流程图
csv可视化
相关资源、参考资料
-
豆瓣评价
-
开源电子书
-
《Linux C编程一站式学习》这书写得很不错,为什么都买不到了呢? - echo1937的回答 - 知乎 https://www.zhihu.com/question/34069391/answer/544825938
-
[大佬们的学习笔记]
- 习题答案整理
-
https://blog.csdn.net/weixin_44576779/article/details/87443584