首页 > 版块 > C语言 > 帖子正文

if和for等基本语句结构

张角 发布于 2021-10-27 14:00
收藏 0 回复 0 浏览 208 原创

大家好,我是张飞实战电子张角老师!

同学们好,我们从今天开始探讨单片机C语言,我们首先从iffor等基本语句结构开始。

iffor,这两个从本质上来说,是不是C语言的两个关键字呀。那么我们为什么又把它称之为C语言的基本结构的一部分呢。要回答这个问题,我们是不是首先要搞清楚C语言的基本结构是什么?

实际上,任何结构化编程语言的基本结构都是相同的,也就是三种基本的程序结构:顺序,分支和循环。由这三者最基本的结构,可以搭建出任何我们想实现的程序结构。在狄杰斯特拉(Edsger W. Dijkstra)反复研究面条式代码(spaghetti code),并在1968年给某位编辑写了一篇著名的简信,题为《Go to语句是有害的》之后,计算机科学家Corrado BohmGiuseppe Jacopini证明,使用顺序(sequencing),分支/选择(alternation)和循环(iteration)这三种流程结构就足以表达所有程序的本质。C语言作为结构化编程语言的一种,其程序结构,自然也是由这三种最基本的程序结构组成。

顺序执行程序,这个很好理解,一条语句接着一条语句执行就可以了。那么C语言的分支和循环是如何实现的呢?

对于分支,我们常见的关键词是不是有if/elseswitch/case两种组合呀。if/else翻译过来,是不是就是“如果,否则”,是一个条件判断。如果用伪代码的方式来进行表达的话,一般有这两种形式。

第一种形式如下:

if(条件为真)

{

代码段1

}

else

{

代码段2

}

这里else的含义其实就是条件不为真,那么也就是条件为假。

image.png

第二种形式是这样的,

if(条件1为真)

{

代码段1

}

else if(条件2为真)

{

代码段2

}

else

{

代码段3

}

image.png

第一种形式和第二种形式本质上的区别,其实就是第一个是双分支,第二个是多分支。两种不同的分支,我们要根据具体情况去使用。某种意义上多分支模式可以由双分支演变而来,比如我们可以在双分支模式里面的else里嵌套一个双分支结构就可以了。

if(条件1为真)

{

代码段1

}

else

{

if(条件2为真)

{

代码段2

}

else

{

代码段3

}

}

从这个意义上看,双分支和多分支其实是一回事情,本质上都是分支。分支这个概念,大家应该都是相对容易接受的,任何一件事情总有它的对立面,高对低,胖对瘦,大对小,物质对暗物质等等。分支这个概念应该是反映了事物的一种本源的状态,是描述程序是不可再进行切分的维度,也就是说分支成为了任何程序的三种基本结构之一。

其实对于多分支的情况,C语言有另外一套关键字组合switch/case,写成伪代码的形式,大概是这样的。


switch(变量){

case 常数1

代码段1

break

case 常数2

代码段2

break

case 常数3

代码段3

break

 

。。。

 

default

代码段n

break

}

大家注意没,对于switch/case组合来说,它的条件一定要是常量,而且要是整数。这个是不是对判断的条件作出了限制呀。这里的default关键字,是和if/else里面的else对应的,表示意外情况。从表面看,switch/case适用于逻辑条件简单,但是分类较多的情况;if/else适用于判断条件复杂,但是分支较少的情况。但是从另外一个层面看来,switch/case所具备的功能,if/else完成起来,完全没有问题呀。那为什么还要搞出来这一个关键字组合呢?

我个人的理解是switch/case关键字的执行效率,在某些情况下,要比if/else要高。

int a = 0;

 switch(a)
 {
 case -1:
    break;
 case -2:
    break;
 case -3:
    break;
 case -4:
    break;
 case 0:
    printf("I am in switch case!n");
    break;
 default:
  break;
 }

 printf("I am between the switch case and if/else if/else!n"); 
 


 if (-1 == a)
 {
 }
 else if (-2 == a)
 {
 }
 else if (-3 == a)
 {
 }
 else if (-4 == a)
 {
 }
 else if (-5 == a)
 {
 }
 else
 {
     printf("I am in if/else if/else!n"); 
 }

比如对于上面的代码段,对于switch/case关键词对来说,程序是直接跳到case0的情况下的;但是对于if/else而言,程序则是一句一句比较之后才达到了else”这一句,程序执行效率的高低是显而易见的。

但是我们说,switch/case的程序执行效率可以比较高,并不是没有条件的。从汇编语言的层面来看,switch/case是建立了一张跳转表,因此需要一定的空间才行。这里某种程度上有以空间换时间的意思。

因此,如果程序可以使用switch/case尽量使用这个,以便提高它的执行效率。其实,我们这样比对一番之后,自己也就轻而易举地牢牢记住了它们,这个可能也就是知其然知其所以然的效果,符合人的记忆规律。

讲完了分支,我们来看一下循环。循环这个基本结构,在C语言里面,一共有两种实现方式,for循环和while循环,其中while循环还可以分为两种,一种是while循环,一种是do/while循环。我们下面分别看一下,这三种结构的程序表达大概是什么模样。

for(循环控制变量初始化;循环终止条件;循环控制变量增量){

循环体;

}

for循环的执行步骤是:首先执行循环变量的初始化,然后执行循环终止条件;如果判断条件为假(不符合终止条件),那么就开始执行循环体;然后执行循环控制变量的增量程序,执行完以后,再去判断是不是符合循环终止条件;如何符合条件,那么就退出循环;如果不符合条件,那么就继续执行循环体,并重复执行上述步骤。

感觉用第二种方式来描述这个循环体的执行过程,更为清晰。第一,先进行循环控制变量初始化;第二,执行循环终止条件,如果判断结果为假,则进入第三步;如果为假则循环终止并退出;第三,执行循环体;第四,执行循环控制变量增量,转入第二步;

注意,其实for循环括号中的三部分其实都可以省略,如果全部省略了,就变成了一个无限循环的死循环,跳不出来了。无限循环在操作系统中使用的是非常多的,每一个任务都是一个无限循环体,包括main函数也是一个无限循环体。

下面,我们来看一下while循环的代码格式。

循环控制变量初始化;

while(循环终止条件){

循环体;

循环控制变量增量;

}

while循环的执行步骤可以描述为如下的样子。

第一在while之前,先执行循环控制变量的初始化;第二,判断循环终止条件是否成立,如果成立那么就跳出循环,如果不成立就进入第三步;第三步,执行循环体;第四步,执行循环控制变量增量,然后进入第二步。

do/while的代码格式是什么样子呢?

循环控制变量初始化;

do{

循环体;

循环控制变量增量;

}while(循环终止条件)

while循环的执行步骤可以描述为如下的样子。第一,在do(也就是执行)之前,先执行循环控制变量的初始化;第二,先执行一次循环体和循环控制变量的增量;第三步,判断循环终止条件是否成立,如果成立那么就跳出循环,如果不成立就进入第二步;

我们来看一下呀,whiledo/while主要的区别,一个是先判断再执行,一个是先执行再判断,那么也就是说do/while这个关键字组合中,函数体至少执行一次。但是这并不影响whiledo/while之间的转化呀,同样的功能绝大多数情况下,可以用while也可以使用do/while来实现。那么有没有必须要用do/while的时候呢?答案是有的,在linux编程中,do/while常用的一个方式是do while0)。

比如,我需要定义一个宏:

 #define SAFE_FREE(p)  do{free(p); p=NULL}  while(0)

假设这里去掉do{....} while(0),及定义为:

#define SAFE_FREE(p)  free(p); p=NULL

那么以下代码:

If(NULL!=p)

   SAFE_FREE(p)

else

   .......

会被展开成如下的有仇

If(NULL!=p)

   free(p); p=NULL 

else

   .......

展开之后,存在两个问题。因为if分支后面有两个语句,会导致else分支没有对应的if,编译失败。即使假设没有else分支,SAFE_FREE中的第二句,无论if测试是否通过,这个都会被执行。那么如何解决上述问题呢?有人说给SAFE_FREE的定义加上{}就可以了,比如如下的样子:

 

 

#define SAFE_FREE(p)  { free(p); p=NULL; }

 

代码展开如下:

 

If(NULL!=p)

    { free(p); p=NULL; }

 else

    .......

 

这样问题又来了,else 没有对应的if了,编译还是会失败。但是使用了do while0)则可以完美解决上述问题。代码展开如下,

If(NULL!=p)

   do { free(p); p=NULL; } while(0)

 else

    .......

所以,do while(0)的使用是为了保证宏定义的使用者能无编译错误的使用宏。

 

下面我们讨论一下,为什么C语言有了for,还需要while呢?除了上面的do while0)的必要性之外,好像还真得没有确切的其他原因。比如在golang编译其中,就只有for,没有while了;再比如,java的源代码里面,就是有一堆的for(;;),据说可以提高性能。所以更大程度上讲,C语言只是为了,兼容程序员的编码习惯,保留了while这个关键字而已。

一些更现代的语言还加入了foreachfor in这种专门遍历集合的语法糖,也有pythonruby这种直接抛弃了C语言三段式for,只保留了for in,而把非遍历型的循环统统放到while循环里面的做法。

我们这次关于程序基本结构的探讨就先到这里。

 

参考资料: 

 基础学习C语言第四章:三种基本结构 https://zhuanlan.zhihu.com/p/97629275

 Linux - switch/caseif/else if/else的效率比较  

http://blog.sina.com.cn/s/blog_5acb430f0100ael9.html

  if/elseswitch/case的区别

https://www.cnblogs.com/anlia/p/11685639.html

  深度理解do{} while(0)  

https://blog.csdn.net/weibo1230123/article/details/81904498

 


0 0
发表评论 侵权投诉
评论 (0)

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表乌云踏雪网立场。

文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。