Linux C编程一站式学习笔记4

Posted 临风而眠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux C编程一站式学习笔记4相关的知识,希望对你有一定的参考价值。

Linux C编程一站式学习笔记 chap4 分支语句

文章目录

这本书在和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语句中包含两条子语句,子语句可以是任何语句或语句块,当然也可以是另外一条ifif/else语句。

    • 根据组合规则,ifif/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语言的规定,应该理解成elseif (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+\\overlineAC+BC=AB+\\overlineAC AB+AC+BC=AB+AC ( A + B ) ( A ‾ + C ) ( B + C ) = ( A + B ) ( A ‾ + C ) (A+B)(\\overlineA+C)(B+C)=(A+B)(\\overlineA+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语句就会一直往下执行,后面其它casedefault分支的语句也会被执行到,直到遇到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编程一站式学习笔记4的主要内容,如果未能解决你的问题,请参考以下文章

Linux C编程一站式学习笔记3

Linux C编程一站式学习笔记1

Linux C编程一站式学习笔记5

Linux C编程一站式学习笔记2

《Linux C编程一站式学习》——第一个程序HelloWorld.c

有关于《Linux C编程一站式学习》(备份)