jianhong_wu 发表于 2016-9-25 11:56:28

本帖最后由 jianhong_wu 于 2016-9-25 12:31 编辑

第三十七节:单字节变量赋值给多字节变量的疑惑。

【37.1   不同类型变量的赋值疑惑。】

      之前讲过,多字节变量赋值给单字节变量时,多字节变量的低8位直接覆盖单字节变量,这个很容易理解,比如:
unsigned long a=0x12345678;//多字节变量
unsigned char t=0xab;   //单字节变量
t=a;//多字节赋值给单字节变量,t的结果由原来的0xab变成了0x78

      那么,问题来了,如果调换过来,单字节赋值给多字节变量,多字节变量除了低8位被单字节变量所直接覆盖之外,其它剩余的位会是什么状态?会被0覆盖吗?还是会保持原来的数值不变?这个就是本节将要解开的疑惑。比如:
unsigned long a=0x12345678;//多字节变量
unsigned char t=0xab;   //单字节变量
a=t;//单字节赋值给多字节变量,此时,a到底是0x123456ab?还是0x000000ab?疑惑中......
      想解开此疑惑,只要亲自上机测试一下就知道结果。经过在keil平台下的C51编译器测试后,发现结果是这样子的:a是0x000000ab!也就是说,多字节变量其余高位是默认被0覆盖的。但是,我还有一个疑惑,是不是所有的C编译器都是这样默认处理,会不会在不同的C编译器平台下,会有不同的结论?所以,下面我再介绍两种比较可靠的办法给大家。

【37.2   我以前用的办法。】

       我以前做项目的时候,每逢遇到这个疑惑,在不同变量赋值之前,我都多插入一行清零的代码,这行代码就是先把多字节变量通过直接赋值0来清零,因为我确信常量赋值都是直接覆盖的(其余高位都直接用0填充)。比如:
unsigned long a=0x12345678;//多字节变量
unsigned char t=0xab;   //单字节变量
a=0;//赋值之前先清零,这是我以前用的办法。
a=t;//单字节赋值给多字节变量
       现在反省了一下,这种办法虽然可靠实用,但是显得过于保守。

【37.3   我现在用的办法:C语言类型的强制转换。】

       前面章节提到,括号在C语言中有强制的意思,可以强制改变优先级,也可以强制促进不同变量类型的匹配。比如:
unsigned long a=0x12345678;//多字节变量
unsigned char t=0xab;   //单字节变量
a=(unsigned long)t;//此处的括号就是强制把t先转变成unsigned long类型,然后再赋值。
       这是我现在所使用的办法,推荐大家用这种。

【37.4   例程练习和分析。】

       现在编写一个程序来验证刚才讲到的主要内容:
       程序代码如下:
/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{

   unsigned long a=0x12345678;//多字节变量
   unsigned long b=0x12345678;
   unsigned long c=0x12345678;

   unsigned chart=0xab;   //单字节变量

   a=t;//a是0x000000ab,其余高位默认被0覆盖。

   b=0;//这是我以前用的办法,显得过于保守
   b=t;

   c=(unsigned long)t;//C语言的类型强制转换。现在推荐大家用这种。   

   View(a);                  //把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);                  //把第2个数b发送到电脑端的串口助手软件上观察。
   View(c);                  //把第3个数c发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:171
十六进制:AB
二进制:10101011

第2个数
十进制:171
十六进制:AB
二进制:10101011

第3个数
十进制:171
十六进制:AB
二进制:10101011

分析:      
      通过实验结果,发现在单片机上的实验结果和我们的分析是一致的。

【37.5   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-10-2 08:31:53

本帖最后由 jianhong_wu 于 2016-10-2 08:45 编辑

第三十八节:第二种解决“运算过程中意外溢出”的便捷方法。

【38.1   意外溢出。】

      运算过程中的意外溢出,稍不注意,就中招,不信,请看下面的例子:
    /*---C语言学习区域的开始。-----------------------------------------------*/
    unsigned longa=0;
    unsigned int x=1000;
    unsigned int y=3000;
    void main() //主函数
    {
       a=x*y;    //猜猜a是多大?
       View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
       while(1)
       {
       }
    }
    /*---C语言学习区域的结束。-----------------------------------------------*/
       猜猜a是多大?很多人以为理所当然3000000,但是实际上是50880!中招了吧。莫名其妙的50880,就是因为意外溢出所致。怎么办呢?请看下面介绍的两种解决办法。

【38.2   第一种办法:引入中间变量。】

       我在前面章节中曾多次说过“为了避免运算过程中的意外溢出,建议大家把所有参与运算的变量都用unsigned long类型的变量,如果不是unsigned long类型的变量,就引入unsigned long类型的中间变量。”这种老方法如下:
    /*---C语言学习区域的开始。-----------------------------------------------*/
    unsigned longa=0;
    unsigned int x=1000;
    unsigned int y=3000;
    unsigned longs; //引入的unsigned long中间变量。
    unsigned longt; //引入的unsigned long中间变量。
    void main() //主函数
    {
       s=x;//先把变量的数值搬到unsigned long中间变量。
       t=y;   //先把变量的数值搬到unsigned long中间变量。
       a=s*t;    //中间变量代表原始变量进行运算。
       View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
       while(1)
       {
       }
    }
    /*---C语言学习区域的结束。-----------------------------------------------*/
       这一次,运算结果是正确的3000000。
       现在反省了一下,这种办法虽然可靠实用,但是显得有点罗嗦,而且引入的中间变量也无形中增加了一点内存。还有没有更好的办法?请看下面介绍的第二种办法。

【38.3   第二种办法:C语言的类型强制转换。】

       前面章节提到,括号在C语言中有强制的意思,可以强制改变优先级,在本节也可以临时强制改变运算过程中的变量类型。在运算过程中临时强制改变类型变量,就可以省去额外引入的中间变量,这种方法相比上面第一种老办法确实更便捷灵活。
/*---C语言学习区域的开始。-----------------------------------------------*/
unsigned longa=0;
unsigned int x=1000;
unsigned int y=3000;
void main() //主函数
{
   a=(unsigned long)x*(unsigned long)y;//添加的两个括号就是类型的强制转换。
   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   while(1)
   {
   }
}
/*---C语言学习区域的结束。-----------------------------------------------*/
       这一次,运算结果也是正确的3000000。

       多说一句,除了上述的乘法运算之外,其它的加、减、除法运算适不适用呢?虽然我还没有逐个测试,但是我感觉应该是都适用的。因此,在“加、减、除”等运算中,在必要的时候,也要在相关的变量的前缀加上类型的强制转换。

【38.4   全局变量和局部变量。】
   
      先插入一个知识点,细心的朋友会发现,我上面的例子中,定义的变量都放在了main函数之外的上面,这种把变量定义在函数外面的变量叫全局变量,以前例子中定义在函数内的变量叫局部变量。
unsigned char a;//这个在函数之外,叫全局变量
void main() //主函数
{
   unsigned char b;    //这个在函数之内,叫局部变量
   while(1)
   {
   }
}
       上面例子中,a定义在函数之外是全局变量,b定义在函数之内是局部变量。全局变量与局部变量有什么不一样呢?以后的章节会仔细讲解这方面的知识,现在暂时不讲。之所以在这里提出这个知识点,是因为我今后的例子很多变量可能都会定义成全局变量,因此先在这里给大家打个招呼,知道C语言有这样一种语法就可以。

【38.5   例程练习和分析。】
   
      现在编写一个程序来验证刚才讲到的主要内容:
      程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

unsigned longa=0;
unsigned longb=0;
unsigned longc=0;
unsigned int x=1000;
unsigned int y=3000;

unsigned longs;//中间变量
unsigned longt;

void main() //主函数
{
   a=x*y;//意外溢出

   s=x;    //引入中间变量
   t=y;
   b=s*t;   

   c=(unsigned long)x*(unsigned long)y;    //类型的强制转换
   
   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);//把第2个数a发送到电脑端的串口助手软件上观察。
   View(c);//把第3个数a发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:50880
十六进制:C6C0
二进制:1100011011000000

第2个数
十进制:3000000
十六进制:2DC6C0
二进制:1011011100011011000000

第3个数
十进制:3000000
十六进制:2DC6C0
二进制:1011011100011011000000

分析:      
      通过实验结果,发现在单片机上的实验结果和我们的分析是一致的。

【38.6   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

jianhong_wu 发表于 2016-10-9 09:37:32

本帖最后由 jianhong_wu 于 2016-10-9 09:51 编辑

第三十九节:if判断语句以及常量变量的真假判断。

【39.1   if语句常规的书写格式。】

       “if”在英文里的含义是“如果”的意思,在C语言里也是这个意思,是判断语句的专用关键词,也是平时做项目时应用的频率最高的语句之一。
       如果if小括号里面的条件满足,就执行条件后面大括号里的语句;如果条件不满足,则直接跳过条件后面大括号里的语句。“if”语句的常见格式如下:
if(条件)
{
    语句1;
    语句2;
}
    语句3;
    语句4;

上述分析:
       如果(条件)满足,就从“语句 1”开始往下执行,直到把大括号里面所有的语句执行完之后,才跳出大括号,接着从大括号之外的“语句 3”开始往下执行。
       如果(条件)不满足,就直接跳过大括号里所有的语句,直接从大括号之外的“语句 3”处开始往后执行。

【39.2   if语句省略大括号的用法。】

       除了上述之外,还有一种省略大括号的书写格式,但是要注意,当if条件语句后面省略了大括号时,如果if小括号里面的条件满足,仅仅执行条件后面第一条语句,如果条件不满足,则跳过条件后面第一条语句。比如:
if(条件)
    语句1;
    语句2;
    语句3;
    语句4;

上述分析:
       如果(条件)满足,就从语句1开始一直往下执行。
       如果(条件)不满足,就直接跳过(条件)后的第一条语句“语句1”,直接从(条件)后的第二条语句“语句2”开始往后执行。

      上述格式省略了大括号,实际上它等效于以下这种书写:
if(条件)
{
    语句1;
}
    语句2;
    语句3;
    语句4;

       在实际项目中,为了阅读清晰,建议大家不要省略大括号。

【39.3   什么是真什么是假?】

       刚才讲到,if语句后面必备(条件)。那么,这个(条件)如何裁定“满足”和“不满足”?专业术语,我们用“真”表示“满足”,用“假”表示“不满足”。(条件)的真假判断,有两种:第一种是数值判断,第二种是关系判断。本节先讲第一种,数值判断。格式如下:
if(常量或者变量)
{
    语句1;
    语句2;
}
    语句3;
    语句4;

       当小括号里面的(常量或者变量)不等于0时,就代表小括号里面的条件“满足”,是“真”;当小括号里面的(常量或者变量)等于0时,就代表小括号里面的条件“不满足”,是“假”。举个例子:
if(25)
{
    语句1;
    语句2;
}
    语句3;
    语句4;

上述分析:
       因为”if(条件)”的“条件”是常量“25”,25不等于0,所以是“真”。因此,条件满足,直接从第一条语句“语句1”处开始往下执行。

【39.4   例程练习和分析。】

       现在编写一个程序,有5条if判断语句,如果条件为真,“统计变量a”就会自动加1,最后看看条件为真的语句有几条。
       程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

    unsigned char x=2;
    unsigned char y=0;
    unsigned char a=0;//“统计变量a”,此变量统计有多少条语句是真的

void main() //主函数
{
    if(1)      //常量不等于0,因此为真
    {
      a=a+1;//a由0自加1后变成1。
   }

   if(0)   //常量等于0,因此为假
   {
      a=a+1;//由于条件为假,这条语句没有被执行,因此此时a仍然是1
   }

   if(15)   //常量不等于0,因此为真
   {
      a=a+1;//a由1自加1后变成2。
   }

   if(x)   //变量x为2,不等于0,因此为真
   {
      a=a+1;//a由,2自加1后变成3。
   }

   if(y)   //变量y为0,等于0,因此为假
   {
      a=a+1;//由于条件为假,这条语句没有被执行,因此此时a仍然是3
   }

   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:3
十六进制:3
二进制:11

分析:      
       通过实验结果,发现在单片机上的实验结果和我们的分析是一致的。

【39.5   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-10-16 08:17:55

本帖最后由 jianhong_wu 于 2016-10-16 08:49 编辑

第四十节:关系符的等于“==”和不等于“!=”。

【40.1   关系符的等于“==”和不等于“!=”。】

       C语言的“=”并不是等于号,而是赋值的意思,这点前面已讲过。为了跟赋值区分开来,C语言用“==”来表示等于号的关系符,用“!=”表示不等于的关系符,之所以用“!=”表示不等于的关系,是因为C语言中的“!”就是“取非”的运算符,有否定之意。

       等于关系符“==”语句的常见格式如下:
if(常量或变量==常量或变量)
{
    语句1;
    语句2;
}
语句3;
语句4;

       不等于关系符“!=”语句的常见格式如下:
if(常量或变量!=常量或变量)
{
    语句1;
    语句2;
}
语句3;
语句4;

       上一节讲到,常量或变量在if语句中的真假判断,不等于0就是真,等于0就是假。而本节关系运算符的真假判断也很简单清晰,满足条件就是真,不满足条件就是假。例如:
if(2==1)   //2肯定不等于1,所以不满足条件“等于的关系”,因此为假,不会执行大括号内的语句。
{
    语句1;
    语句2;
}

       相反,请继续看下面不等于号“!=”这个例子:

if(2!=1)   //2肯定不等于1,所以满足条件“不等于的关系”,因此为真,会执行大括号内的语句。
{
    语句1;
    语句2;
}

【40.2   建议把常量放在“==”或“!=”关系符的左边】

       “if(a==1)”和“if(1==a)”在实现的功能上是相同的。但是,在实际做项目的时候,还是建议大家采用后面这种写法“if(1==a)”,把常量放在左边,这样写有什么好处?好处是,如果我们不小心把等于号“==”或者“!=”误写成赋值符号“=”时,C编译器在编译时,它能及时发现错误并且报错告知我们,因为常量在左边是无法赋值的,编译器能及时发现错误。但是如果常量在右边而变量在左边,因为变量是允许赋值的,所以有一些C语言编译器未必会报错,就会留下不易察觉的程序隐患。比如:


if(a==5)
{
    语句1;
}

if(b!=2)
{
    语句2;
}

         建议改成:

if(5==a)
{
    语句1;
}

if(2!=b)
{
    语句2;
}

【40.3   例程练习和分析。】

      现在编写一个实验程序,一共有8个给定的数,要统计其中数值“等于85”的数有几个,统计其中数值“不等于75”的数有几个。
      程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

unsigned char x1=90; //给定的第1个数   
unsigned char x2=65; //给定的第2个数   
unsigned char x3=85; //给定的第3个数   
unsigned char x4=79; //给定的第4个数   
unsigned char x5=95; //给定的第5个数   
unsigned char x6=65; //给定的第6个数   
unsigned char x7=75; //给定的第7个数   
unsigned char x8=85; //给定的第8个数

unsigned char a=0; //统计等于85的变量总数
unsigned char b=0; //统计不等于75的变量总数

void main() //主函数
{
      //第一部分:统计“等于85”的总数有多少个。
if(85==x1)//把常量85放在等于号的左边
{
   a++;   //相当于a=a+1,用来统计等于85的总数
}

if(85==x2)//把常量85放在等于号的左边
{
   a++;   //相当于a=a+1,用来统计等于85的总数
}

if(85==x3)//把常量85放在等于号的左边
{
   a++;   //相当于a=a+1,用来统计等于85的总数
}

if(85==x4)//把常量85放在等于号的左边
{
   a++;   //相当于a=a+1,用来统计等于85的总数
}

if(85==x5)//把常量85放在等于号的左边
{
   a++;   //相当于a=a+1,用来统计等于85的总数
}

if(85==x6)//把常量85放在等于号的左边
{
   a++;   //相当于a=a+1,用来统计等于85的总数
}

if(85==x7)//把常量85放在等于号的左边
{
   a++;   //相当于a=a+1,用来统计等于85的总数
}

if(85==x8)//把常量85放在等于号的左边
{
   a++;   //相当于a=a+1,用来统计等于85的总数
}

      //第二部分:统计“不等于75”的总数有多少个。
if(75!=x1)//把常量75放在不等于号的左边
{
   b++;   //相当于b=b+1,用来统计不等于75的总数
}

if(75!=x2)//把常量75放在不等于号的左边
{
   b++;   //相当于b=b+1,用来统计不等于75的总数
}

if(75!=x3)//把常量75放在不等于号的左边
{
   b++;   //相当于b=b+1,用来统计不等于75的总数
}

if(75!=x4)//把常量75放在不等于号的左边
{
   b++;   //相当于b=b+1,用来统计不等于75的总数
}

if(75!=x5)//把常量75放在不等于号的左边
{
   b++;   //相当于b=b+1,用来统计不等于75的总数
}

if(75!=x6)//把常量75放在不等于号的左边
{
   b++;   //相当于b=b+1,用来统计不等于75的总数
}

if(75!=x7)//把常量75放在不等于号的左边
{
   b++;   //相当于b=b+1,用来统计不等于75的总数
}

if(75!=x8)//把常量75放在不等于号的左边
{
   b++;   //相当于b=b+1,用来统计不等于75的总数
}

   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);//把第2个数b发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

         在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:2
十六进制:2
二进制:10

第2个数
十进制:7
十六进制:7
二进制:111

分析:      
      变量a为2。(等于85的有x3,x8这2个)
      变量b为7。(不等于75的有x1,x2,x3,x4,x5,x6,x8这7个)
      通过实验结果,发现在单片机上的实验结果和我们的分析是一致的。

【40.4   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-10-23 08:53:12

本帖最后由 jianhong_wu 于 2016-10-23 09:05 编辑

第四十一节:关系符的大于“>”和大于等于“>=”。

【41.1   大于“>”。】

       大于关系符“>”语句的常见格式如下:
if(常量或变量>常量或变量)
{
    语句1;
    语句2;
}
语句3;
语句4;
       上述if条件的真假判断规则是:如果左边的数大于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。例如:
if(2>1)   //2肯定大于1,所以满足条件“大于的关系”,因此为真,会执行大括号内的语句。
{
    语句1;
    语句2;
}

【41.2   大于等于“>=”。】

       大于关系符“>=”语句的常见格式如下:
if(常量或变量>=常量或变量)
{
    语句1;
    语句2;
}
语句3;
语句4;
       上述if条件的真假判断规则是:如果左边的数大于或者等于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。例如:
if(2>=2)   //左边的2虽然不大于右边的2,但是左边的2等于右边的2,因此为真,满足条件。
{
    语句1;
    语句2;
}

【41.3   例程练习和分析。】

      现在编写一个实验程序,一共有8个给定的数,要统计其中数值大于79的数有几个,同时,也统计其中数值大于等于79的数又有几个。
      程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/
      unsigned char x1=90; //给定的第1个数   
      unsigned char x2=65; //给定的第2个数   
      unsigned char x3=85; //给定的第3个数   
      unsigned char x4=79; //给定的第4个数   
      unsigned char x5=95; //给定的第5个数   
      unsigned char x6=65; //给定的第6个数   
      unsigned char x7=75; //给定的第7个数   
      unsigned char x8=85; //给定的第8个数

      unsigned char a=0; //统计大于79的变量总数
      unsigned char b=0; //统计大于等于79的变量总数

void main() //主函数
{
      //第一部分:统计“大于79”的总数有多少个。

if(x1>79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计大于79的总数
}

if(x2>79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计大于79的总数
}

if(x3>79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计大于79的总数
}

if(x4>79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计大于79的总数
}

if(x5>79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计大于79的总数
}

if(x6>79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计大于79的总数
}

if(x7>79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计大于79的总数
}

if(x8>79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计大于79的总数
}

      //第二部分:统计“大于等于79”的总数有多少个。

if(x1>=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计大于等于79的总数
}

if(x2>=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计大于等于79的总数
}

if(x3>=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计大于等于79的总数
}

if(x4>=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计大于等于79的总数
}

if(x5>=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计大于等于79的总数
}

if(x6>=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计大于等于79的总数
}


if(x7>=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计大于等于79的总数
}

if(x8>=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计大于等于79的总数
}

   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);//把第2个数b发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:4
十六进制:4
二进制:100

第2个数
十进制:5
十六进制:5
二进制:101
分析:      
      变量a为4。(大于79的有x1,x3, x5,x8这4个)
      变量b为5。(大于等于79的有x1,x3, x4, x5,x8这5个)
      通过实验结果,发现在单片机上的实验结果和我们的分析是一致的。

【41.4   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-10-30 09:22:04

本帖最后由 jianhong_wu 于 2016-10-30 09:35 编辑

第四十二节:关系符的小于“<”和小于等于“<=”。

【42.1   小于“<”。】

      小于关系符“<”语句的常见格式如下:
if(常量或变量<常量或变量)
{
    语句1;
    语句2;
}
语句3;
语句4;
       上述if条件的真假判断规则是:如果左边的数小于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。例如:
if(2<1)   //2肯定不小于1,所以不满足条件“小于的关系”,因此为假,不会执行大括号内的语句。
{
    语句1;
    语句2;
}

【42.2   小于等于“<=”。】

       小于关系符“<=”语句的常见格式如下:
if(常量或变量<=常量或变量)
{
    语句1;
    语句2;
}
语句3;
语句4;
       上述if条件的真假判断规则是:如果左边的数小于或者等于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。例如:
if(2<=2)   //左边的2虽然不小于右边的2,但是左边的2等于右边的2,因此为真,满足条件。
{
    语句1;
    语句2;
}

【42.3   例程练习和分析。】

       现在编写一个实验程序,一共有8个给定的数,要统计其中数值小于79的数有几个,统计其中数值小于等于79的数有几个。
       程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/
      unsigned char x1=90; //给定的第1个数   
      unsigned char x2=65; //给定的第2个数   
      unsigned char x3=85; //给定的第3个数   
      unsigned char x4=79; //给定的第4个数   
      unsigned char x5=95; //给定的第5个数   
      unsigned char x6=65; //给定的第6个数   
      unsigned char x7=75; //给定的第7个数   
      unsigned char x8=85; //给定的第8个数

      unsigned char a=0; //统计小于79的变量总数
      unsigned char b=0; //统计小于等于79的变量总数

void main() //主函数
{
      //第一部分:统计“小于79”的总数有多少个。

if(x1<79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计小于79的总数
}

if(x2<79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计小于79的总数
}

if(x3<79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计小于79的总数
}

if(x4<79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计小于79的总数
}

if(x5<79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计小于79的总数
}

if(x6<79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计小于79的总数
}

if(x7<79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计小于79的总数
}

if(x8<79)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计小于79的总数
}

       //第二部分:统计“小于等于79”的总数有多少个。

if(x1<=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计小于等于79的总数
}


if(x2<=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计小于等于79的总数
}


if(x3<=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计小于等于79的总数
}


if(x4<=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计小于等于79的总数
}

if(x5<=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计小于等于79的总数
}


if(x6<=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计小于等于79的总数
}


if(x7<=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计小于等于79的总数
}


if(x8<=79)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计小于等于79的总数
}

   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);//把第2个数b发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:3
十六进制:3
二进制:11

第2个数
十进制:4
十六进制:4
二进制:100

分析:      
       变量a为3。(小于79的有x2,x6, x7这3个)
       变量b为4。(小于等于79的有x2, x4,x6, x7这4个)
       通过实验结果,发现在单片机上的实验结果和我们的分析是一致的。

【42.4   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-11-6 10:48:25

本帖最后由 jianhong_wu 于 2016-11-6 11:07 编辑

第四十三节:关系符中的关系符:与“&&”,或“||”。

【43.1   关系符中的与“&&”。】

       前面在讲关系符的时候,讲了只存在1个(判断条件)的情况下,根据这个判断为真还是为假再执行对应的操作,那么,当同时存在2个(判断条件)以上的情况下,该如何描述(判断条件)与(判断条件)之间的关系,这就涉及本节所讲的“关系符中的关系符”:与“&&”,或“||”。
       先讲“&&”语句,符号“&&”称为“与”,它的含义是:假如有两个以上的(条件判断),当所有的(条件判断)都满足的时候,才认为这个整体判断是真,否则,只要有1个(条件判断)不满足,那么整体判断就是假。这个规律,有点像很多开关在电路回路中的串联关系,只有所有串联在回路中的开关都是闭合的状态,这个回路才是畅通的,否则,只要有一个开关是断开的,整个回路就是断开的。
       与语句“&&”的常见格式如下:
if(第1个条件判断&&第2个条件判断…&&第N个条件判断)
{
    语句1;
    语句2;
}
语句3;
语句4;
       在上述格式中,只有if语句后面小括号内所有的(条件判断)都满足的时候,整体判断才为真,才会执行到大括号内的“语句1”和“语句2”,否则,只要有1个不满足,就直接跳到“语句3”处往下执行。

       再举一个具体的例子,比如要取从70到80之间的所有数据,也就是说,既要大于等于70,同时又要小于等于80,程序代码可以这样书写:
if(a>=70&&a<=80)//在70到80的区间范围(包括边界70和80)
{
   语句1;
   语句2;
   ……
   语句N;
}

【43.2   关系符中的或“||”。】

       符号“||”称为“或”,它的含义是:假如有两个以上的(条件判断),只要有1个条件判断为真,则此整体判断裁定为真,否则,必须所有的(条件判断)都不满足,此整体判断才会裁定为假。这个规律,有点像很多开关在电路回路中的并联关系,并联在回路的多个开关,只要有1个开关是闭合的状态,那么这个回路肯定是畅通的,否则,必须全部开关都是断开的,整个回路才会是断开的。
       或语句“||”的常见格式如下:
if(第1个条件判断||第2个条件判断…||第N个条件判断)
{
    语句1;
    语句2;
}
语句3;
语句4;
      在上述格式中,只要if语句后面小括号内有1个(条件判断)是满足的时候,整体判断马上裁定为真,这时就会执行到大括号内的“语句1”和“语句2”,否则,必须全部的(条件判断)都不满足,整体判断才会裁定为假,这时就会直接跳到“语句3”处往下执行。

      再举一个具体的例子,比如要取除了70到80之间以外的所有数据,也就是说,要么小于70,或者要么大于80,可以这样写:
if(a<70||a>80) //在70到80的区间范围以外的数据(不包括边界70和80)
{
   语句1;
   语句2;
   ……
   语句N;
}



【43.3   “&”和“&&”,“|”和“||”的区别。】

       前面章节讲过运算符的“&”和“|”,它们发音也是“与”和“或”,跟本节讲的关系符“&&”和“||”的发音是同音,因此很容易让初学者混淆。区别如下:
       运算符的“&”和“|”,是属于运算符,是强调数与数,变量与变量,个体与个体之间的运算,而不是关系。它们之间的运算,是把一个数或一个变量转化成二进制后,进行二进制的0和1之间的“与”“或”运算。
       关系符的“&&”和“||”,是属于关系符,是强调(条件判断)与(条件判断),关系与关系,整体与整体之间的关系判断,而不是运算。它们之间的关系,关键词是判断。

【43.4   “&&”和“||”的“短路”问题。】

       关系符“&&”和“||”居然也有“短路”问题?大家不要惊异,这里所说的“短路”只是强调关系符内部判断的顺序和取舍。“短路”这个词在这里只是业内已经习惯了的一种称谓,虽然我个人感觉有一点怪怪的不自然,但是我自己也想不出其它更好的词来描述这种关系,因此就跟业内已习惯的称谓保持一致。
       “&&”的“短路”,它内部判断的顺序和取舍是这个样子的:在两个以上的判断中,从左边到右边,依次逐个判断,先判断第1个(条件判断),再第2个(条件判断)...再第N个(条件判断),但是,在此期间,只要发现有1个条件是不满足,就马上退出判断,不再继续判断后面的(条件判断),因为,对于“与”的关系符,只要有1个条件判断是不满足(假),就可以马上裁定整体判断为假了,没必要继续判断后面的(条件判断)。
       “||”的“短路”,它内部判断的顺序和取舍是这个样子的:在两个以上的判断中,从左边到右边,依次逐个判断,先判断第1个(条件判断),再第2个(条件判断)...再第N个(条件判断),但是,在此期间,只要发现有1个条件是满足,就马上退出判断,不再继续判断后面的(条件判断),因为,对于“或”的关系符,只要有1个条件判断是满足(真),就可以马上裁定整体判断为真了,没必要继续判断后面的(条件判断)。
       上述文字中的“从左到右”就是“顺序”,“马上退出”就是“取舍”。这种关系之所以称谓为“短路”,我猜测可能是把“&&”和“||”比喻成在电路的回路中,只要有个1个地方短路了,就可以马上裁定这个回路是短路的,就不用再判断其它地方了。

【43.5   例程练习和分析。】

       现在编写一个实验程序,一共有8个给定的数,要统计其中数值从70到80之间的数有几个,统计其中取除了70到80之间以外的数有几个。
       程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/
      unsigned char x1=90; //给定的第1个数   
      unsigned char x2=65; //给定的第2个数   
      unsigned char x3=85; //给定的第3个数   
      unsigned char x4=79; //给定的第4个数   
      unsigned char x5=95; //给定的第5个数   
      unsigned char x6=65; //给定的第6个数   
      unsigned char x7=75; //给定的第7个数   
      unsigned char x8=85; //给定的第8个数

      unsigned char a=0; //统计从70到80的变量总数
      unsigned char b=0; //统计除了70到80以外的变量总数

void main() //主函数
{
      //第一部分:统计“从70到80之间的数有多少个。

if(x1>=70&&x1<=80)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计从70到80的总数
}

if(x2>=70&&x2<=80)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计从70到80的总数
}

if(x3>=70&&x3<=80)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计从70到80的总数
}

if(x4>=70&&x4<=80)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计从70到80的总数
}

if(x5>=70&&x5<=80)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计从70到80的总数
}

if(x6>=70&&x6<=80)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计从70到80的总数
}

if(x7>=70&&x7<=80)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计从70到80的总数
}

if(x8>=70&&x8<=80)//如果条件为真,则执行下面大括号里面的语句。
{
   a++;   //相当于a=a+1,用来统计从70到80的总数
}

       //第二部分:统计除了70到80之间以外的数有多少个。

if(x1<70||x1>80)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计除了70到80以外的总数
}

if(x2<70||x2>80)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计除了70到80以外的总数
}

if(x3<70||x3>80)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计除了70到80以外的总数
}

if(x4<70||x4>80)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计除了70到80以外的总数
}

if(x5<70||x5>80)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计除了70到80以外的总数
}

if(x6<70||x6>80)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计除了70到80以外的总数
}

if(x7<70||x7>80)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计除了70到80以外的总数
}

if(x8<70||x8>80)//如果条件为真,则执行下面大括号里面的语句。
{
   b++;   //相当于b=b+1,用来统计除了70到80以外的总数
}

   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);//把第2个数b发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

       在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:2
十六进制:2
二进制:10

第2个数
十进制:6
十六进制:6
二进制:110


分析:      
       变量a为2。(数值从70到80之间的有x4, x7这2个)
       变量b为6。(除了70到80之间以外的有x1, x2,x3,x5, x6, x8这6个)
       通过实验结果,发现在单片机上的实验结果和我们的分析是一致的。

【43.6   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-11-13 09:30:32

本帖最后由 jianhong_wu 于 2016-11-13 09:48 编辑

第四十四节:小括号改变判断优先级。

【44.1   小括号的概述。】

       小括号在C语言里一直有“强制、改变优先级、明确顺序”这层含义。C语言中,凡是在判断语句里插入了小括号,程序就会优先执行最里面小括号的判断语句,之后才会根据判断符的优先级执行其它相关语句。
       此功能很实用,因为C语言的判断符号众多,非常不利于程序员记忆各种不同符号的优先级顺序,而小括号却解决了这个问题。只要在合适的地方插入恰当的小括号,就可以强制改变判断的优先级,有了此项功能就不用程序员再刻意去记忆繁杂的优先级,同时,也为实际项目带来两个好处,一个是明确判断顺序,另一个是改变判断顺序。多说一句,哪怕添加的小括号是多余的重复的啰嗦的,也不会对程序带来副作用,反而,只会给程序员内心带来更大的确定和安全感。比如:
       两个if条件判断语句:
       if(a>=70&&a<=80)和if(a<70||a>80)

       有一些朋友喜欢插入两个小括号变成:
       if((a>=70)&&(a<=80))和if((a<70)||(a>80))

       在这里插入的小括号是多余的重复的啰嗦的,但是还好,不会对程序有副作用。上述的修改,在不知道 “>、>=、<、<=” 这类语句跟 “&&,||” 这类语句哪个优先级更高的前提下,插入了小括号,可以更加明确判断的顺序,这种做法也值得肯定。

【44.2   小括号的具体应用。】

       我个人平时在面对同时存在“>、>=、<、<=”和 “&&、||” 这些语句时,由于我很清楚“>,>=,<,<=”比“&&,||” 这类语句的优先级更高,所以我不需要在此插入小括号来明确判断的顺序。但是遇到下面这种情况,我是一定会通过插入小括号的方式来明确判断的顺序。什么情况呢?如下:

       if(“判断条件1”||“判断条件2 ”&&“判断条件3”)
       这种情况下,就会很容易让我出现一个疑问,到底是先“判断条件1” 跟“判断条件2”相“或”,最后再跟“判断条件3”相“与”?还是先“判断条件2” 跟“判断条件3”相“与”,最后再跟“判断条件1”相“或”?如果此时果断插入小括号,就可以很容易明确它们的先后顺序,减少内心不必要的纠结。

       插入小括号的第1种情况:
      if((“判断条件1”||“判断条件2 ”)&&“判断条件3”)

       插入小括号的第2种情况:
       if(“判断条件1”||(“判断条件2 ”&&“判断条件3”))

      上述两种情况,具体选择哪一种判断顺序要根据项目的需要来决定。同样的3个“判断条件”,如果插入的小括号的位置不一样,判断的顺序就不一样,那么结果也可能出现不一样,比如,上述判断条件:
       假设“判断条件1”为“真”,
       假设“判断条件2”为“真”,
       假设“判断条件3”为“假”,
       等效成如下:

       插入小括号的第1种情况:
if((真||真)&&假)
{
   语句1;
}
       这种情况下,先判断最里面小括号的真假,(真||真)的结果是“真”,然后再把结果“真”和外面的“假”进行“与”判断,(真&&假)的结果是“假”,所以上述的最终判断是“假”,不能执行“语句1”。

       插入小括号的第2种情况:
if(真||(真&&假))
{
   语句1;
}
       这种情况下,先判断最里面小括号的真假,(真&&假)的结果是“假”,然后再把结果“假”和外面的“真”进行“或”判断,(真||假)的结果是“真”,所以上述的最终判断是“真”,能执行“语句1”。

       综合上述两种情况,对比之后,得出这样的结论:在同样的条件和关系下,如果插入不同位置的小括号,就可以得出不同的结果。也就是说,小括号可以让关系判断变得丰富起来,可以实现各种复杂的逻辑判断功能。

【44.3   例程练习和分析。】

       现在编写一个实验程序验证上述两种判断顺序。
       程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

      //x,y这三个变量作为条件判断的变量
      unsigned char x=5;
      unsigned char y=6;

   //a,b这两个变量作为输出判断结果的真假,0代表假,1代表真。
      unsigned char a=0;//默认为0,也就是默认为假
      unsigned char b=0;//默认为0,也就是默认为假

void main() //主函数
{
if((x<y||y>x)&&x==y) //里面的条件是((真||真)&&假),最终结果判断是假
{
   a=1;
}

if(x<y||(y>x&&x==y)) //里面的条件是(真||(真&&假)),最终结果判断是真
{
   b=1;
}

   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);//把第2个数b发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

       在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:0
十六进制:0
二进制:0

第2个数
十进制:1
十六进制:1
二进制:1

分析:      
       变量a为0。(0代表此条件判断结果为假)
       变量b为1。(1代表此条件判断结果为真)
       通过实验结果,发现在单片机上的实验结果和我们的分析是一致的。

【44.4   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-11-20 11:01:42

本帖最后由 jianhong_wu 于 2016-11-20 11:32 编辑

第四十五节: 组合判断if...else if...else。

【45.1   三类组合判断语句的概述。】

       if的英文含义是“如果”,else是“否则”,else if是“否则如果”。在C语言里,if,else if ,else 所表达的含义,跟英文也是一样的。
       if,else if,else可以组成三种组合判断语句,第一种是“二选一”,第二种是“多选一”,第三种是“多选一或者什么都不选”。
       这类组合判断语句可以这样解读:在众多条件判断中,先从第一个if条件开始判断,如果第一个if条件是真,那么不管后面的条件是否为真,都不再判断,直接执行第一个if条件后面大括号的语句,组合语句中其它剩余的条件不再判断直接跳过,否则,就挨个条件往下判断,只要其中一个条件满足,就不再判断剩余的条件,也就是我们日常所说的多选一,甚至在某些组合语句如果所有条件都不满足,那么什么也不选。总之,在如此众多的条件中,最多只能执行一个条件后面大括号的语句。组合语句还有一个规律:if语句只能出现在第一个条件判断,而且只能出现一次;else只能出现在最后,而且也只能出现一次;而else if语句总是出现在中间,绝对不能出现在第一个条件判断,如果没有else,也可以出现在最后的条件判断。多说一句,在上述所提到的“只能出现一次”的概念仅仅局限于在一个组合判断语句的范围内,而组合判断语句在整个程序的出现次数是不受限制的。

【45.2   二选一的组合判断。】

      先讲第一种的“二选一”的书写格式,如下:
      书写格式如下:
if(条件1)   //if只能出现第一个条件,并且只能出现一次
{
    语句1;
}
else      //else只能出现最后,并且也只能出现一次。
{
       语句2;
}
语句3;
      这类语句的书写特点是:第一个是if判断语句,最后一个是else语句,中间没有else if判断语句。
      这类语句的执行顺序是:先判断第一个的if里面的(条件1),如果(条件1)满足而为真,就执行该(条件1)后面紧跟的大括号里面的“语句1”,执行完该大括号内的所有语句之后,就直接跳出整个组合判断的语句,不再判断也不再执行剩下来的else那部分的代码,直接跳到“语句3”处,从“语句3”处(包括“语句3”)继续往下执行。但是,如果第一个的if里面的(条件1)不满足而为假,那么就直接执行else后面大括号内的语句。也就是说,else是在if条件不满足时才执行的,所以叫“二选一”,在if和else之间二选一。

【45.3   多选一的组合判断。】

      接着讲第二种书写格式的“多选一”,这种书写格式,跟第一种对比,是在if与else的中间多插入了N个else if的判断语句。书写格式如下:
if(条件1)         //if只能出现第一个条件,并且只能出现一次
{
    语句1;
}
else if(条件2)   //else if只能出现中间或最后,可以出现多次
{
    语句2;
}
...
else if(条件N)   //else if只能出现中间或最后,可以出现多次
{
    语句N;
}
else             //else只能出现最后,并且也只能出现一次。
{
       语句N+1;
}
语句N+2;
      这类语句的书写特点是:第一行是if开始,最后一行以else结束,中间是N个else if判断语句。
      这类语句的执行顺序是:跟第一种“二选一”对比,判断顺序和规律大致也是一样的,也是从第一个if开始,往下逐个判断,然后到中间的else if,只要发现一个条件满足,就执行该条件后面的大括号内的代码,之后就马上结束整个组合判断语句,不再判断剩下的组合判断语句。但是,如果万一前面第一个if和中间所有的else if的条件都不满足而为假,就直接执行最后一个else大括号内的语句。所以叫“多选一”,在“第一个if、中间的else if、最后一个else”之间多选一。

【45.4   多选一或者什么都不选的组合判断。】

      最后讲第三种书写格式的“多选一或者什么都不选”,这种书写格式,跟第二种对比,只有第一个if和其它的else if语句,没有最后那个else语句。书写格式如下:
if(条件1)          //if只能出现第一个条件,并且只能出现一次
{
    语句1;
}
else if(条件2)    //else if只能出现中间或最后,可以出现多次
{
    语句2;
}
...
else if(条件N)   //else if只能出现中间或最后,可以出现多次
{
    语句N;
}
语句N+1;
      这类语句的书写特点是:第一行是if开始,中间是N个else if判断语句,没有最后一个else语句。
      这类语句的执行顺序是:跟第二种“多选一”对比,判断顺序和规律大致也是一样的,也是从第一个if开始,往下逐个判断,然后到中间的else if,只要发现一个条件满足,就执行该条件后面的大括号内的代码,之后就马上结束整个组合判断语句,不再判断剩余的组合判断语句。但是,如果万一前面第一个if和中间所有的else if的条件都不满足而为假,因为此时没有else语句,就意味着整个组合判断语句都没有条件满足,因此就没有相关满足的代码被执行到。所以把这种情况称为“多选一或者什么都不选”。

【45.5   例程练习和分析。】

      现在编写一个实验程序。
      程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

      //x这个变量作为条件判断的变量
      unsigned char x=5;

      //a,b,c这3个变量作为输出判断结果,0代表什么语句都没执行,1代表执行了语句1,
      //2代表执行语句2,3代表执行语句3。
      unsigned char a=0;
      unsigned char b=0;
      unsigned char c=0;

void main() //主函数
{

//第一种“二选一”
if(x>6)
{
   a=1;   //1代表执行了“语句1”
}
else
{
   a=2;//2代表执行了“语句2”
}

//第二种“多选一”
if(x>6)
{
   b=1;   //1代表执行了“语句1”
}
else if(7==x)
{
   b=2;//2代表执行了“语句2”
}
else
{
   b=3;//3代表执行了“语句3”
}

//第三种“多选一或者什么都不选”
if(x>6)
{
   c=1;   //1代表执行了“语句1”
}
else if(7==x)
{
   c=2;//2代表执行了“语句2”
}
else if(8==x)
{
   c=3;//3代表执行了“语句3”
}

   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);//把第2个数b发送到电脑端的串口助手软件上观察。
   View(c);//把第3个数c发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:2
十六进制:2
二进制:10

第2个数
十进制:3
十六进制:3
二进制:11

第3个数
十进制:0
十六进制:0
二进制:0

分析:      
       变量a为2。(2代表执行了语句2)
       变量b为3。(3代表执行了语句3)
       变量c为0。(0代表什么语句都没执行)

【45.6   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-11-27 11:29:59

本帖最后由 jianhong_wu 于 2016-12-4 08:55 编辑

第四十六节: 一维数组。

【46.1   数组是什么?】

       数组就是一堆变量或常量的集合。把一个数组里面某一个变量或者常量称为数组的元素,反过来也可以这么说,元素的集合就是数组。数组的最大特点就是内部所有的元素的地址都是挨家挨户相连的,同花顺似的,以第一个元素(下标是0的元素)为首地址,后来元素的地址挨个依次增大。首地址在RAM中的绝对地址往往是编译器自动分配的,我们不用管,可以看成是随机的。多说一句,在某些单片机,也可以通过特定的C语言关键词,强制要求编译器按我们的意愿,来分配到RAM中指定的某个绝对地址,这部分的内容这里暂时不讲。继续刚才的话题,首地址就像是一个坐标原点,一旦被编译器确定下来它在RAM中的地址,那么后面其它元素的地址都是在此基础上依次增大的,有规律的。正因为这个特点,数组在项目中往往起到缓存的作用。比如,在通信的项目中,用来作为一串数据的接收缓存。在界面显示的项目中,某个16x16点阵汉字的字模,需要一个内含32个元素的数组来作为缓存。在读写文件的项目中,也需要一个大数组来作为文件内容的缓存。在某些涉及复杂算法的项目,以数组作为缓存,并且通过配合循环语句或者指针,就可以快速批量的处理数据(循环语句和指针的相关知识后面章节会讲到)。总之,在项目应用中,数组无处不在。
       数组分为一维数组,二维数组,三维数组。一维数组应用最广,二维数组其次,三维数组最少用。所以本教程只讲一维数组和二维数组,本节先讲一维数组。

【46.2   一维数组的书写格式和特点。】

      一维数组不带初始化时候的定义格式如下:
      数据类型 数组名[数组元素总数N];
      数据类型是指unsigned char,unsigned int,unsigned long这类关键词;数组名就是由字母和数字组合而成的字符串,遵循常用变量的命名规则;N是数字,代表此数组内部有多少个元素。比如:
      unsigned char x;//这里的3是数组内部元素的总数,但不是下标。
      上述这一行代码,就相当于一条语句定义了3个变量,这3个变量分别是x,x,x,但是不存在x这个变量。这里,具体元素中括号内的“0,1,2”称为数组的下标,代表某个具体的元素。由此可见,数组有“批量定义”的特点。同时也要发现,此数组定义的N是3,代表内含3个元素变量,但是具体到某个元素的时候,下标不是从1开始,而是从0开始,最后一个也不是3而是2。可以这样描述,某个数组有N个元素,它具体元素的下标是从0开始,到N-1结束。那么问题来,如果一个数组明明最大只有N个元素,但是我在操作某个具体的元素时,非要用下标N或者N+1,也就是说,如果超过数组的范围的操作,会出现什么问题?后果严重吗?答案是:会导致数组越界出现异常或者编译不通过,可能会破坏其它数据,后果是严重的。因此大家使用数组的时候,要注意数组不能越界的问题。

      刚刚讲了一维数组不带初始化的定义格式,现在接着讲带初始化的定义格式,如下:
      数据类型 数组名[数组元素总数N]={元素0, 元素1,…元素N-1};
      比如:
      unsigned char y={10,11,12};
      此数组一行代码定义了3个变量,分别是y, y, y。而y初始化为10,y初始化为11,y初始化为12。
      在程序中,操作数组某个变量元素时,下标可以是常量,比如y,此时的0就是常量;下标也可以是变量,比如y的中括号内的i,此时的i就是变量。再强调一次,作为下标的常量或者变量i的数值必须小于数组定义时的元素个数,否则就会导致数组越界出现异常或者编译不通过。

      中括号内的N什么时候是“数组的元素总数”,什么时候是“数组的元素下标”,这个问题对初学者很容易混淆。其实很简单,定义的时候是“数组的元素总数”,操作调用具体某个元素的时候是“数组的元素下标”。

【46.3   什么情况下可以省略定义的元素总数?】

       一维数组在定义时,如果预先给它填写若干个初始化的数据,在语法上,也可以省略中括号里面的元素总数N,这样编译器在编译时会根据初始化的总数来自动识别和定义此一维数组实际元素总数,分配对应数量的内存RAM。比如:

       unsigned chary={10,11,12};//没有省略元素总数的写法

       跟

       unsigned chary[]={10,11,12};   //在初始化的情况下,省略了元素总数的写法。

       的意义是一样的,都是合法的,都是C语言所允许的。注意,省略元素个数时必须要有初始化的数据,否则,编译器不知道此数组的长度,可能导致编译出错。

       这个功能在实际应用中有什么作用呢?在实际应用中,此项功能一般会用在常量数组里,而不是变量的数组里。当在数组定义的前面加上“const”或者“code”(针对51单片机)的关键词时,原来“变量”的数组就会变成“常量”的数组,这时,如果把常量的数组用来作为某个转换表格,此功能就很实用。因为作为转换表格的常量数组,我们在编程程序的过程中,有可能随时往里面添加数组,这个时候,不用我们刻意去计算和调整数组的元素总数N,给我们写程序带来了便利。对于这个功能的应用,大家先有一个感性的认识即可,暂时不用深入去了解,因为后续的章节还会讲解这方面的内容。

【46.4   例程练习和分析。】

    现在编写一个程序来熟悉一下一维数组的使用。
    程序代码如下:
/*---C语言学习区域的开始。-----------------------------------------------*/

   unsigned charx;//此处的3不是下标,而是元素总数,里面的3个变量没有初始化
   unsigned chary={10,11,12}; //里面三个元素变量y,y,y分别初始化为10,11,12
   unsigned chari=0; //定义和初始化一个变量。用来做x数组的下标。

void main() //主函数
{

x=25;//此时下标i为0.相当于把25赋值给x
i=i+1;    //i由0变成1.
x=26;//此时下标i为1.相当于把26赋值给x
i=i+1;    //i由1变成2.
x=27;//此时下标i为2.相当于把27赋值给x
x=x+1; //此时x自加1变成了28

   View(x);//把第1个数x发送到电脑端的串口助手软件上观察。
   View(x);//把第2个数x发送到电脑端的串口助手软件上观察。
   View(x);//把第3个数x发送到电脑端的串口助手软件上观察。
   View(y);//把第4个数y发送到电脑端的串口助手软件上观察。
   View(y);//把第5个数y发送到电脑端的串口助手软件上观察。
   View(y);//把第6个数y发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

   在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:25
十六进制:19
二进制:11001

第2个数
十进制:26
十六进制:1A
二进制:11010

第3个数
十进制:28
十六进制:1C
二进制:11100

第4个数
十进制:10
十六进制:A
二进制:1010

第5个数
十进制:11
十六进制:B
二进制:1011

第6个数
十进制:12
十六进制:C
二进制:1100

分析:      
       变量元素x为25。
       变量元素x为26。
       变量元素x为28。
       变量元素y为10。
       变量元素y为11。
       变量元素y为12。

【46.5   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。



jianhong_wu 发表于 2016-12-4 09:46:55

本帖最后由 jianhong_wu 于 2016-12-4 10:04 编辑

第四十七节: 二维数组。

【47.1   二维数组的书写格式和特点。】

       拿一维数组和二维数组来对比一下,一维数组只有一个下标,像由很多点连成的一条直线,整体给人的是一种“线”的观感。而二维数组有两个下标,这两个下标类似平面中的行与列,也类似平面中的X轴和Y轴的坐标,通过y轴和x轴坐标就可以找到所需的点,也就是二维数组的某个元素,因此,二维数组整体给人的是一种“面”的观感。

      上述是对二维数组的感性描述,二维数组是由一维数组发展而来,所以继承了很多一维数组的特点。二维数组的所有“网点”元素的地址都是挨个相临的,先从第0行开始“扫描”当前行的列,第0行第0列,第0行第1列,第0行第2列......再第1行第0列,第1行第1列,第1行第2列......再第2行......再第N行,上一行“尾”元素跟下一行“头”元素的地址也是相临连续的。
       二维数组未带初始化时的通用定义格式如下:
       数据类型 数组名[行数Y][列数X];
       比如:
       unsigned chara; //此处的2代表有2行,3代表有3列。
       分析:此二维数组定义了6个变量,跟一维数组一样,下标都是从0开始,到(N-1)时结束,此处的N代表行数或者列数。所以a数组的元素挨个分别是a,a, a, a, a, a这6个变量。这6个变量的地址是顺序挨个相连的。

       二维数组有两种常用初始化格式,一种是逐行初始化,一种是整体初始化。

       第一种逐行初始化:
unsigned char a=
{
   {0,1,2},
   {3,4,5}
};
       在逐行初始化定义二维数组时,只要有初始化的数据,也可以省略行下标,但是列下标不能省略,比如:
unsigned char a[]=
{
   {0,1,2},
   {3,4,5}
};
       此时编译器会根据元素的个数来确定行数是多少。


       第二种整体初始化,跟一维数组一样,内部数据元素不需要额外增加大括号来分行。
unsigned char a=
{
   0,1,2,3,4,5
};
       或者
unsigned char a=
{
   0,1,2,
   3,4,5
};
都行。

       C语言是很丰富的语言,比如二维数组还允许不完全初始化的一些情况,这种情况我就不再深入讲解,我讲解的都是挑选一些针对以后单片机项目中可能会经常用到的语法。
       二维数组我在很多项目上还是经常用到的,比如用在一些需要把所得的信息进行查表判断的项目,在每一行里放一条关键词字符串信息,利用循环语句进行逐行查找匹配信息。至于二维数组如何存放字符串的知识点以后再讲。这节的重点是让大家对二维数组有个初步的认识。

【47.2   例程练习和分析。】

       现在编写一个程序来熟悉一下二维数组的书写和使用格式。
       程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

    unsigned chara=//定义和初始化一个二维数组
    {
       {0,1,2},
       {3,4,5}
    };
void main() //主函数
{
   a=8;//故意把第0行第0列的这个变量赋值8,让大家看看如何直接操作二维数组某个元素。

   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   View(a);//把第2个数a发送到电脑端的串口助手软件上观察。
   View(a);//把第3个数a发送到电脑端的串口助手软件上观察。
   View(a);//把第4个数a发送到电脑端的串口助手软件上观察。
   View(a);//把第5个数a发送到电脑端的串口助手软件上观察。
   View(a);//把第6个数a发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

       在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:8
十六进制:8
二进制:1000

第2个数
十进制:1
十六进制:1
二进制:1

第3个数
十进制:2
十六进制:2
二进制:10

第4个数
十进制:3
十六进制:3
二进制:11

第5个数
十进制:4
十六进制:4
二进制:100

第6个数
十进制:5
十六进制:5
二进制:101

分析:      
      变量元素a为8。从原来定义的0变成8,因为被main函数里的第1行代码赋值了8。
      变量元素a为1。
      变量元素a为2。
      变量元素a为3。
      变量元素a为4。
      变量元素a为5。

【47.3   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-12-11 10:48:23

本帖最后由 jianhong_wu 于 2016-12-11 11:11 编辑

第四十八节: while循环语句。

【48.1   程序的“跑道”。】

       经常听到这句话“程序跑起来了吗?”,程序在哪里跑?有跑道吗?有的。循环语句就像一条椭圆的跑道,程序在跑道上不停的跑,不知疲倦的跑,永无止境,一秒钟几百万圈的速度。单片机的main主函数内往往有一条while(1)语句,这就是单片机的“循环跑道”,称之为主循环,主循环内还可以继续嵌套多层while循环语句。

【48.2   while循环的常见格式。】

       常见格式如下:
while(条件)
{
    语句1;   
    语句2;
   ……   
    语句N;
}
语句N+1;


       上面的“花括号内”称为“循环体内”,“花括号外”称为“循环体外”,现在来分析一下上述代码的执行规律,如下:
       (1)像if语句一样,先判断while的(条件)是否为真。如果为“假”,就不执行循环体“内”的“语句1”至“语句N”,直接跳到循环体“外”的“语句N+1”处开始往下执行。如果为“真”,才执行循环体“内”的“语句1”至“语句N”,当执行完循环体“内”最后的“语句N“时,单片机会突然返回到第一行代码“while(条件)”处,继续判断循环的(条件)是否为真,如果为假就跳到循环体“外”的“语句N+1”,表示结束了当前循环。如果为真就继续从“语句1“执行到“语句N“,然后再返回while(条件)处,依次循环下去,直到条件为假时才罢休,否则一直循环下去。
      (2)while(条件)语句中,条件判断真假的规则跟if语句一模一样,有两种类型:一种是纯常量或者变量类型的,只要此数值不等于0就认为是真,所以while(1)也称死循环语句,因为里面的条件永远不为0。对于死循环这样的语句,如果不遇到break,return,goto这些语句,那么就永远也别想跳出这个循环;另外一种是关系判断,以及关系语句之间的像“与或”关系这类的判断。这些条件判断的真假,跟if语句的规则是一样的,这里不再多讲。break,return,goto这些语句后面章节会讲到。

【48.3   while省略花括号,没带分号。】

while(条件)
    语句1;   
    语句2;
    ……   
    语句N;
    语句N+1;
       上面的代码,居然没有了花括号,问题来了,此循环语句的“有效射程”究竟是多远,或者说,此循环语句的循环区域在哪里。现在跟大家解开这个谜团。第一行代码,while(条件)后面“没有分号”,接着第二行就是“语句1”,所以,这种情况跟if语句省略花括号的写法是一样的,此时循环体默认只包含离它最近的一条且仅仅一条的“语句1”,因此,上述的语句,等效于下面这种添加花括号的写法:
while(条件)
{
    语句1;   
}
语句2;
……   
语句N;
语句N+1;

【48.4   while省略花括号,带分号。】

while(条件);
    语句1;   
    语句2;
    ……   
    语句N;
    语句N+1;
      这次的代码跟刚才“48.3”的代码唯一的差别是,第一行代码,while(条件)后面“有分号”。所以它循环的有效范围就在第一行就结束了,不涉及“语句1”。此时,等效于下面这种添加花括号的写法:   
while(条件)
{
   ;   //这里的分号代表一条空语句
}
语句1;   
语句2;
……   
语句N;
语句N+1;
      如果while的(条件)一直为“真”,单片机就一直在循环体内执行一条“无意义”的空语句,相当于“耗着”的状态,执行不到后面“语句1”的语句,除非,条件为“假”才罢休才会跳出循环体。
      循环体内什么都没有,只写一条“空语句”,这种写法在实际项目中也是有用武之地的,比如,等待某件事是否满足条件,如果不满足,就一直死等死磕在这里,其它事情都干不了,这种“死等死磕”的做法,专业术语叫“阻塞”,与之反面相对应的是另外一个词叫“非阻塞”。对于循环的“阻塞”用法,老练的工程师通常会多加一个超时的判断,这些内容大家暂时不用深入了解,后续章节我会讲到。

【48.5   例程练习和分析。】

      现在编写一个程序来熟悉一下while语句的书写和使用格式。
      程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

   unsigned char a=0;//观察这个数最后的变化
   unsigned char b=0;//观察这个数最后的变化

   unsigned char i;    //控制循环体的条件判断变量

void main() //主函数
{
i=3;   
while(i)//i不断减小,直到变为0时才跳出此循环体
{
   a=a+1; //当i从3减少到0的时候,这条语句被循环执行了3次。
   i=i-1; //循环的条件不断发生变化,不断减小
}

i=0;
while(i<3)//i不断增大,当i大于或者等于3时才跳出此循环体
{
   b=b+2;   //当i从0增加到3的时候,这条语句被循环执行了3次。
   i=i+1;   //循环的条件不断发生变化,不断增加
}

   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);//把第2个数b发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:3
十六进制:3
二进制:11

第2个数
十进制:6
十六进制:6
二进制:110

分析:      
      变量a为3。a初始化为0,进入循环体内后,a每次加1,循环加3次,因此从0变成了3。
      变量b为6。b初始化为0,进入循环体内后,b每次加2,循环加3次,因此从0变成了6。

【48.6   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-12-18 18:41:34

本帖最后由 jianhong_wu 于 2016-12-18 19:15 编辑

第四十九节: 循环语句do while和for。

【49.1   do while语句的常见格式。】

       格式如下:
do
{
         语句1;   
         语句2;
         ……   
         语句N;
} while(条件);

      上述代码,单片机从上往下执行语句,先从do那里无条件进来,从“语句1”开始往下执行,一直执行到“语句N”,才开始判断while(条件)的条件是否为真,如果为真继续返回到do的入口处,继续从“语句1”开始往下执行,依次循环。大家留意到了吗,do while和while语句有什么差别?差别是,do while是先无条件进来执行一次循环体(花括号里所有的程序代码),执行到循环体最底部才判断while(条件)的条件是否为真来决定是否继续循环,先上车再买票。而while语句是先判断条件是否为真再决定是否需要进入循环体,先买票再上车。

【49.2   for语句的简介。】

       for语句也是循环语句,任何for语句能实现的功能都可以用while语句来实现同样的功能,for语句和while语句有什么差别呢?for语句把变量初始化,变量的条件判断,变量在执行循环体后的步进变化这三个常见要素集成在语句内部,以标准的格式书写出来。在很多场合下,for在书写和表达方面比while语句显得更加简洁和直观。

【49.3   for语句的自加格式。】

       格式如下:
for(变量的初始化语句; 变量的条件判断;变量在执行一次循环体后自加的步进变化)
{
      语句1;
      语句2;
      ……
      语句N;
}

      在把上述变成更具体的代码例程如下:
for(i=0; i<3;i++)
{
      语句1;
      语句2;
      ……
      语句N;
}

       上述代码,单片机从上往下,在进入循环体前,先把变量i初始化赋值0(这行初始化代码在整个循环期间只被执行1次),然后判断i是否小于3这个条件,如果此条件为真,就开始正式进入循环体,从“语句1”往下执行到“语句N”,执行完一次循环体后,i就自加1(因为“i++”语句),此时i从原来初始化的0变成了1,接着再返回来到for语句的条件判断”i<3”那里,判断i是否继续满足“小于3”这个条件,如果此条件为真就继续往下执行,否则就跳过循环体结束当前循环。上述for语句实现的功能如果用while语句来写,等效于以下代码:
i=0;//进入循环体之前先初始化给予初值
while(i<3)
{
   语句1;
   语句2;
   ……
   语句N;
   i++;   //执行一次循环体之后此变量自加发生变化
}

       上述的while循环语句只执行了3次循环体。

【49.4   for语句的自减格式。】

       刚才讲的for(i=0; i<3;i++)这种格式,它的变量i是不断自加的。还有一种比较常见的格式是i不断自减的,它的格式如下:
for(i=3; i>0;i--)
{
   语句1;
   语句2;
   ……
   语句N;
}

       上述自减的for语句功能如果用while语句来写,等效于以下代码:

i=3;//进入循环体之前先初始化给予初值
while(i>0)
{
   语句1;
   语句2;
   ……
   语句N;
   i--;   //执行一次循环体之后此变量自减发生变化
}

       上述的while循环语句只执行了3次循环体。

【49.5   for省略花括号,没带分号。】

       前面讲的if和while语句中,都提到了省略花括号的情况,for语句也有这种写法,而且省略之后默认的有效范围都是一样的。请看例子如下:
for(i=0; i<3;i++)   //注意,这里没带分号。
         语句1;
         语句2;
         ……   
         语句N;

      分析:上述代码,跟if语句一样,此时循环体默认只包含“语句1”,等效于:
for(i=0; i<3;i++)//注意,这里没带分号。
{
   语句1;
}
         语句2;
         ……   
         语句N;

【49.6   for省略花括号,带分号。】

for(i=0; i<3;i++);   //注意,这里带分号。
         语句1;
         语句2;
         ……   
         语句N;

       分析:注意,此时循环体默认不包含“语句1”,而是等效于:   
for(i=0; i<3;i++)
{
    ;//空语句。
}         
      语句1;
      语句2;
      ……   
      语句N;

      此时循环体内先循环执行三次空语句,然后才会结束for循环,接着才从“语句1”开始往下执行。


【49.7   for循环语句的条件判断。】

      上面举的例子中,仅仅列出了for语句条件判断的小于号关系符“<”,其实,for语句条件判断的关系符跟if语句是一样通用的,凡是if语句能用的关系符都可以用在for语句上,比如“>”,“!=”,“==”,“<=”,“>=”等等。如下:
for(i=0;i<=3;i++);//小于等于的情况。这种写法是合法的。
for(i=0;i!=3;i++);//不等于的情况。这种写法是合法的。
for(i=0;i==3;i++);//等于的情况。这种写法是合法的。

【49.8   例程练习和分析。】

       编写一个程序来熟悉一下do while和for语句的使用。
       程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

    unsigned char a=0;//观察这个数最后的变化
    unsigned char b=0;//观察这个数最后的变化
    unsigned char c=0;//观察这个数最后的变化

unsigned char i;//控制循环体的条件判断变量

void main() //主函数
{
i=3;   
do
{
   a=a+1;//每执行一次循环体a就增加1,此行代码被循环执行了3次
   i=i-1;//i不断变小
}while(i); //i不断变小,当i变为0时才跳出此循环体

for(i=0;i<3;i++)
{
   b=b+2;//此行代码被循环执行了3次
}

for(i=3;i>0;i--)
{
   c=c+3;//此行代码被循环执行了3次
}

      View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
      View(b);//把第2个数b发送到电脑端的串口助手软件上观察。
      View(c);//把第3个数c发送到电脑端的串口助手软件上观察。

      while(1)
      {
      }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:3
十六进制:3
二进制:11

第2个数
十进制:6
十六进制:6
二进制:110

第3个数
十进制:9
十六进制:9
二进制:1001

分析:      
      变量a为3。a从0开始,循环加1,一共3次,因此等于3。
      变量b为6。b从0开始,循环加2,一共3次,因此等于6。
      变量c为9。c从0开始,循环加3,一共3次,因此等于9。

【49.9   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-12-25 10:36:01

本帖最后由 jianhong_wu 于 2016-12-25 10:49 编辑

第五十节: 循环体内的continue和break语句。

【50.1   continue语句。】

       通常情况下,单片机在循环体里从第一行的“入口条件”开始往下执行,直至碰到循环体的边界“底部花括号”,才又折回到第一行的“入口条件”准备进行新一轮的循环。但是,若中途碰到continue语句,就会提前结束当前这一轮的循环,只要碰到continue语句,就立即折回到第一行的“入口条件”准备进行新一轮的循环。注意,continue语句“结束”的对象仅仅是“当前这一轮的循环”,并没有真正结束这个循环的生命周期。它好像拦路虎,遇到它,它说“你回去,第二天再来。”这台词里的“第二天再来”就强调这个循环体的生命周期还没有真正结束。举一个具体的例子,如下:

while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
    continue;
    语句3;
    ……   
    语句N;
}    //循环体结束

       分析:上述语句中,单片机从“循环体的条件判断入口处”开始往下执行,碰到continue就马上折回到“循环体的条件判断入口处”,继续开始新一轮的循环,因此,这段代码,continue后面的“语句3”至“语句N”是永远也不会被执行到的。因为continue的拦截,上述语句等效于:

while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
}    //循环体结束

       问题来了,既然可以如此简化,还要continue干什么,不是多此一举?在实际应用中,continue肯定不会像上面这样单独使用,continue只有跟if语句结合,才有它存在的意义。例如:
while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
    if(某条件)
    {
         continue;
    }
    语句3;
    ……   
    语句N;
}    //循环体结束

【50.2   break语句。】

      continue语句提前结束当前这一轮的循环,准备进入下一轮的新循环,强调“某次结束”,但不是真结束。break语句是直接跳出当前循环体,是真正的结束当前循环体,强调循环体的“生命结束”。举例如下:

while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
    break;
    语句3;

    ……   
    语句N;
}    //循环体结束
语句(N+1);//循环体之外语句

       分析:上述语句中,单片机从“循环体的条件判断入口处”开始往下执行,突然碰到break语句,此时,立即无条件跳出当前循环体(无需判断while或者for的条件),直接执行到循环体之外的“语句(N+1)”,break后面的“语句3”至“语句N”也没有被执行到。实际项目中,break也往往会配合if一起使用,例如:

while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
    if(某条件)
    {
         break;
    }
    语句3;

    ……   
    语句N;
}    //循环体结束
语句(N+1);//循环体之外语句

【50.3   break语句能跳多远?】

      break语句能跳多远?预知答案请先看以下例子:
while(…)
{
      语句1;   
      语句2;
      while(…)
      {   
         语句3;   
         break;
         语句4;
   }   
   语句5;
}   
语句6;

       分析:上述例子中,在while循环里面有藏着第二个while循环,像这种循环之中还有循环的情况,通常称为循环嵌套。单片机从上往下执行,当遇到break后,它会跳到“语句5”那里呢,还是会跳到“语句6”那里?正确答案是“语句5”那里,这说明了break语句的“有效射程”仅仅刚好能跳出当前的循环体。也就是说,在上述循环嵌套的例子中,最内层的break只能跳出最内层的循环体,不能跳到最外层的“语句6”那里,如果需要继续跳出最外层的“语句6”那里,可以继续在外层的循环体内再增加一个break语句。

【50.4   还有哪些语句可以无条件跳出循环体?】

      除了break以外,还有return和goto语句可以跳出循环体。这部分的内容大家只需大概了解一下即可。return语句比break语句还厉害,它不仅仅跳出当前循环体,还是跳出了当前函数,也就是提前结束了当前函数,这部分的内容后面章节会讲到,暂时不用管。而goto语句在C语言中大家都公认不建议用,因为它很容易扰乱大家常用的C语言编程结构,我本人也从来没有用过goto语句,因此不再深入讲解它。

【50.5   例程练习和分析。】

      编写一个程序来熟悉一下continue和break语句的使用。
      程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

unsigned char a=0;//观察这个数最后的变化
unsigned char b=0;//观察这个数最后的变化
unsigned char c=0;//观察这个数最后的变化
unsigned char d=0;//观察这个数最后的变化

unsigned char i;//控制循环体的条件判断变量
void main() //主函数
{
//i<6的条件判断是在进入循环体之前判断,而i的自加1是在执行完一次循环体之后才自加的。
for(i=0;i<6;i++)
{
   a=a+1;    //被执行了6次,分别是第0,1,2,3,4,5次
   if(i>=3)//当i等于3的时候,开始“拦截”continue后面的代码。
   {
      continue;//提前结束本次循环,准备进入下一次循环
   }
   b=b+1;//被执行了3次,分别是第0,1,2次
}

//i<6的条件判断是在进入循环体之前判断,而i的自加1是在执行完一次循环体之后才自加的。
for(i=0;i<6;i++)
{
   c=c+1;   //被执行了4次,分别是第0,1,2,3次
   if(i>=3)//当i等于3的时候,直接跳出当前循环体,结束此循环体的“生命周期”。
   {
      break; //马上跳出当前循环体
   }
   d=d+1;   //被执行了3次,分别是第0,1,2次
}

      View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
      View(b);//把第2个数b发送到电脑端的串口助手软件上观察。
      View(c);//把第3个数c发送到电脑端的串口助手软件上观察。
      View(d);//把第4个数d发送到电脑端的串口助手软件上观察。

      while(1)
      {
      }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

       在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:6
十六进制:6
二进制:110

第2个数
十进制:3
十六进制:3
二进制:11

第3个数
十进制:4
十六进制:4
二进制:100

第4个数
十进制:3
十六进制:3
二进制:11

分析:      
       变量a为6。
       变量b为3。
       变量c为4。   
       变量d为3。

【50.6   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2017-1-4 10:22:14

本帖最后由 jianhong_wu 于 2017-1-4 10:44 编辑

第五十一节: for和while的循环嵌套。

【51.1   循环的嵌套。】

       大循环的内部又包含了小循环,称为循环嵌套。生活中,循环嵌套的现象很常见,一年12个月,假设每个月都是30天(仅仅假设而已),1月份30天,2月份30天......11月份30天,12月份30,这里的年就是大循环,年内部的月就是小循环。一年12个月,大循环就是12次。一个月30天,小循环就是30次。用for语句来表达,大意如下:
for(m=1;m<=12;m++)//大循环。一年12个月。这里的m看作月,代表一年12个月的大循环。
{
    for(d=1;d<=30;d++) //内嵌小循环。一月30天。这里的d看作天,代表一个月30天的小循环。
      {

    }
    }

【51.2   循环嵌套的执行顺序。】

      例子如下:
for(i=0;i<2;i++)//大循环
{
      语句1;
      for(k=0;k<3;k++) //内嵌的小循环
      {
            语句2;
      }
      语句3;
}
       上述例子中,带i的for称为大循环,带k的for称为小循环,单片机从大循环入口进来,由上往下执行,执行第1次大循环,先执行1次“语句1”,接着进入小循环,小循环要连续循环执行3次“语句2”才跳出小循环,之后执行1次“语句3”,然后再返回到大循环入口判断i条件是否满足,此时条件满足,继续执行第2次大循环,1次“语句1”,3次“语句2”,1次“语句3”,第2次循环结束后又返回到大循环入口判断i条件,此时i已经等于2不再小于2了,因此条件不满足,结束整个循环嵌套。上述执行的语句顺序如下:
       语句1;   //第1次大循环开始
       语句2;   
       语句2;
       语句2;
       语句3;
       语句1;   //第2次大循环开始
       语句2;
       语句2;
       语句2;
       语句3;
       根据此顺序,再看一个具体的程序例子:
a=0;
b=0;
for(i=0;i<2;i++)//大循环
{
   a=a+1;//被执行了2次
   for(k=0;k<3;k++) //内嵌的小循环
   {
         b= b+1;//被执行了6次
   }
}
      上述例子中,执行完程序后,a的值变成了2,b的值变成了6。重点分析b的变化,“b=b+1”在内嵌循环体里被执行了6次,6次从何而来?就是i乘以k等于6。这个乘法次数是循环嵌套一个很重要的特性。上述程序如果用while语句来实现,等效如下:
a=0;
b=0;
i=0;//控制大循环的变量初始化
while(i<2)//大循环
{
   a=a+1;//被执行了2次
   k=0;    //控制小循环的变量初始化
   while(k<3) //内嵌的小循环
   {
         b= b+1;//被执行了6次
         k=k+1;
    }
   i=i+1;
}

【51.3   循环嵌套的常见用途---二维数组的应用。】

       二维数组a,它有6个变量,在没有学for语句之前,如果要依次把每个元素单独赋值清零真不容易,要写6次赋值语句如下:
a=0;
a=0;
a=0;
a=0;
a=0;
a=0;

       自从懂了for嵌套语句之后,可以让同样功能的代码简洁许多。上述代码等效于如下:

for(i=0;i<2;i++)//大循环
{
   for(k=0;k<3;k++) //内嵌的小循环
   {
          a=0;
      }
}

【51.4   循环嵌套的常见用途---大延时。】

    单片机项目会经常会用到delay这个延时函数,大部分都是利用for循环来实现,小延时的函数往往不用嵌套,直接如下编写:
    for(k=0;k<N;k++);

    上述的N是控制循环次数,每次循环都要消耗单片机一点时间,如果N越大需要消耗的时间就越多,起到延时的作用。但是N所能取的最大值受它所定义的类型所限制,比如unsigned char类型最大范围是255,unsigned int类型最大范围是65535,unsigned long类型最大范围是4294967295。如果要实现更大的延时怎么办?就可以用for的循环嵌套,利用循环嵌套可以使得循环总次数进行乘法翻倍的放大,很容易编写大延时的函数。比如:
for(i=0;i<M;i++)//大循环
{
      for(k=0;k<N;k++); //内嵌的小循环
}

    此时循环的次数是N乘以M的乘积。如果N和M都是unsigned long类型,就意味着最大循环次数是4294967295的平方,次数大到惊人。

【51.5   例程练习和分析。】

现在编写一个循环嵌套的练习程序。
    程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

      unsigned char a=0;//观察这个数最后的变化
      unsigned char b=0;//观察这个数最后的变化
      unsigned char c=0;//观察这个数最后的变化

      unsigned char i;//控制大循环体的条件判断变量
      unsigned char k;//控制内嵌小循环体的条件判断变量
void main() //主函数
{
for(i=0;i<2;i++)//大循环
{
   a=a+1;    //被执行了2次
   for(k=0;k<3;k++)//内嵌小循环
   {
          b=b+1;//被执行了6次,也就是i乘以k,2乘以3等于6.
   }
   c=c+1;    //被执行了2次
}

      View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
      View(b);//把第2个数b发送到电脑端的串口助手软件上观察。
      View(c);//把第3个数c发送到电脑端的串口助手软件上观察。

      while(1)
      {
      }
}

/*---C语言学习区域的结束。-----------------------------------------------*/


   在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:2
十六进制:2
二进制:10

第2个数
十进制:6
十六进制:6
二进制:110

第3个数
十进制:2
十六进制:2
二进制:10


分析:      
    变量a为2。
    变量b为6。
    变量c为2。

【51.6   如何在单片机上练习本章节C语言程序?】

    直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

jianhong_wu 发表于 2017-1-8 11:24:31

本帖最后由 jianhong_wu 于 2017-1-8 11:42 编辑

第五十二节: 支撑程序框架的switch语句。

【52.1   switch的重要性。】

       switch是非常重要的语句,我所有的单片机项目都是用switch搭建程序主框架。如果说while和for是一对孪生兄弟,那么“if-else if”和switch也是一对孪生兄弟,凡是用“if-else if”能实现的功能都可以用switch实现。switch有条件分支的功能,当条件的分支超过3个以上时,switch会比“if-else if”更加直观清晰。

【52.2   switch的语法。】

      switch常见的格式如下:
switch(变量)    //根据变量的数值大小从对应的case入口进来
{
   case 0:   //入口0
         语句0;
         break; //switch程序体的出口之一
   case 1://入口1
         语句1;
         break; //switch程序体的出口之一
   case 2: //入口2
         语句2;
         break; //switch程序体的出口之一
}    //最下面的花括号也是一个switch程序体的出口之一

       分析:单片机从第一行的switch(变量)进来,依次往下查询跟变量匹配的case入口,然后从匹配的case入口进来,往下执行语句,直到遇上break语句,或者return语句,或者“最下面的花括号”这三种情况之一,才跳出当前switch程序体。上述例子中,假如变量等于3,单片机从switch(变量)进来,往下查询跟3匹配的case入口,因为没有发现case 3,最后遇到“最下面的花括号”于是结束switch程序体,像这种变量等于3的情况,就意味着switch里面的有效语句没有被执行到。多补充一句,在case 2选项中,“语句2”后面紧跟的break可以省略,因为case 2是最后一个case,即使没有遇到break也会遇到“最下面的花括号”而结束switch程序体。上述程序功能如果用“if-else if”语句来实现,等效于如下:
if(0==变量)
{
    语句0;
}
else if(1==变量)
{
    语句1;
}
else if(2==变量)
{
    语句2;
}

【52.3   switch的break。】

       刚才的例子中,可以看到三个关键字:switch,case,break。其实并不是每个case都必须要跟break配套,break只是起到一个出口的功能。假如没有遇到break,程序会一直往下执行,直到遇到break或者switch“最下面的花括号”为止。比如:
switch(变量)    //根据变量的数值大小从对应的case入口进来
{
   case 0:   //入口0
         语句0;
         break;
   case 1://入口1
         语句1;
   case 2: //入口2
         语句2;
         break;
   case 3: //入口3
         语句3;
         break;
}   //最下面的花括号也是一个switch程序体的出口之一

       分析:假如此时switch(变量)的变量等于1,单片机经过查询后,就从匹配的case 1入口进来,执行“语句1”后,居然没有遇到break语句,于是紧接着碰到“case 2”入口的语句,现在问题来了,单片机此时是退出switch程序体还是忽略“case 2”入口语句而继续执行后面的“语句2”?答案是:忽略“case 2”入口语句而继续执行后面的“语句2”。这里有点像坐地铁,你只关注一个入口和一个出口,进入地铁内之后,你中途再遇到无数个入口都可以忽略而继续前进,直到你到达目的地的出口才结束整个乘车过程。继续刚才的分析,单片机执行“语句2”之后,紧接着遇到break语句,这时才跳出整个switch程序体。回顾一下整个流程,本例子中case 1没有break语句,就继续往下执行下面case2里面的语句,直到遇到break或者“最下面的花括号”为止。

【52.4   case的变量有顺序要求吗?】

       switch语句内部的case有规定顺序吗?必须连贯吗?switch程序体内部可以写很多case入口,这些case入口是不是必须按从小到大的顺序?是不是规定必须case数字连贯?答案是:没有规定顺序,也没有规定case数字连贯。case的数值只是代表入口,比如以下两种写法都是合法的:
       第一种:case不按从小到大的顺序(这种格式是合法的):
switch(变量)   
{
   case 2:
         语句2;
         break;
   case 0:
         语句0;
         break;
   case 1:
         语句1;
         break;
}   

       第二种:case的数字不连贯(这种格式也是合法的):
switch(变量)   
{
    case 0:
         语句0;
         break;
   case 3:
         语句3;
         break;
   case 9:
         语句9;
         break;
}   

【52.5   switch的default。】

       default是入口语句,它在switch语句中也不是必须的,应根据程序需要来选择。default相当于“if-else if-else ”组合语句中的else,也就是当switch的入口变量没有匹配的case入口时,就会默认进入default入口,就像“if-else if-else ”语句中当前面所有的条件不满足时,就进入else语句的程序体,比如:
switch(变量)    //根据变量的数值大小从对应的case入口进来
{
   case 0:   //入口0
         语句0;
         break; //switch程序体的出口之一
   case 1://入口1
         语句1;
         break; //switch程序体的出口之一
   case 2: //入口2
         语句2;
         break; //switch程序体的出口之一
   default://当所有的case不满足,就从default的入口进来
         语句3;
         break;
}    //最下面的花括号也是一个switch程序体的出口之一

       分析:假如switch的入口变量等于35,单片机从上往下查询,因为没有找到case 35,所以就会从默认的default入口进来执行” 语句3”,然后遇到break语句才跳出switch程序体。上述程序功能如果用“if-else if-else”组合语句来实现等效于如下:
if(0==变量)
{
    语句0;
}
else if(1==变量)
{
    语句1;
}
else if(2==变量)
{
    语句2;
}
else   //相当于switch中的default
{
    语句3;
}

【52.6   switch中内嵌switch。】

       if语句可以内嵌if语句,while语句也可以内嵌while语句,switch语句当然也可以内嵌switch。比如:
switch(a)
{
   case 1:
      switch(b)//内嵌的switch
      {
             case 1:
                  Break;
             case 2:
                  Break;
      }
      Break;
   case 2:
      Break;
}

      分析:上述这种switch内嵌switch语句也是合法的,而且在实际项目中也很常用,大家目前先有个大概的了解即可,暂时不深入讲解。

【52.7   例程练习和分析。】

      现在编写一个switch的练习程序。
      程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

   unsigned char k;   //switch的入口变量
   unsigned char a;   //观察此变量的变化来理解switch的执行顺序
void main() //主函数
{
      a=0;
      k=2;   //入口变量等于2
switch(k)
{
   case 0://入口0
       a++;
       break; //跳出switch
   case 1://入口1
       a++;
   case 2://入口2,上述k等于2所以从这里进来
       a++;
   case 3://入口3
       a++;
   case 4://入口4
       a++;
       break;//跳出switch
   case 5://入口5
       a++;
       break;//跳出switch
   default://当前面没有遇到匹配的case入口时,就从此default入口进来
       a++;
       break;//跳出switch
}            //最后一个switch的花括号也是跳出switch

      View(a);//把第1个数a发送到电脑端的串口助手软件上观察。

      while(1)
      {
      }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

       在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:3
十六进制:3
二进制:11

分析:      
      变量a为3。单片机从case 2入口进来,因为case 2和case 3都没有break语句,直到遇到case 4的break语句才结束switch程序体,因此整个过程遇到了3次“a++”语句,因此变量a的“自加一”执行了3次后从0变成了3。

【52.8   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2017-1-15 14:17:24

本帖最后由 jianhong_wu 于 2017-1-15 14:44 编辑

第五十三节: 使用函数的三要素和执行顺序。

【53.1   函数的十大关联部件。】

       函数是什么?我很难用一句话给它下定义,哪怕我真能用一句话定义了,初学者也很难从一句话的定义中“格”出函数之理。之所以函数有如此玄机,确实因为它包罗万象,涉及的内容非常多,就像要我去定义什么是中国,我也没法用一句话去定义,只有长大了慢慢了解它的地理文化历史,你才会对咱中国有深刻的认识。函数也是如此,虽然我不能用一句话定义函数,但是函数跟十大部件有关,只要今后不断学习和运用,对十大部件各个击破直到全部“通关”,总有一天你会感悟到函数的精髓。现在先把十大部件列出来,让大家有一个感性的认识,它们是:函数体,函数接口,return语句,堆栈,全局变量,普通局部变量,静态局部变量,单个变量的指针,数组的指针,结构体的指针。本节讲的“使用函数的三要素和执行顺序”就是属于“函数体”这个部件的内容。

【53.2   使用函数的三要素。】

       有的人习惯把函数称为程序,比如主程序,子程序,这时的主程序对应主函数,子程序对应子函数,是一回事,只是每个人的表达习惯不一样而已。使用函数的三要素是声明,定义,调用。每次新构造一个函数时,尽量遵守这个三个要素来做就可以减少一些差错。什么叫函数的声明,定义,调用?为了让大家有一个感性的认识,请先看下面这个例子:
/*---C语言学习区域的开始。-----------------------------------------------*/

void HanShu(void);   //子函数声明的第一区域

unsigned chara;   //全局变量定义的第二区域
unsigned charb;
unsigned charc;

void HanShu(void)    //子函数定义的第三区域
{
   a++;    //子函数的代码语句
   b=b+5;
   c=c+6;
}

void main() //主函数
{
   a=0;
   b=0;
   c=0;
   HanShu() ;      //子函数被调用的第四区域
   c=a+b;
   while(1)
   {

   }
}
/*---C语言学习区域的结束。-----------------------------------------------*/

      分析:上述例子中,从书写代码区域的角度来寻找函数的大概规律,从上往下:
      第一区域:写子函数HanShu声明。
      第二区域:全局变量的定义。
      第三区域:子函数HanShu的定义。
      第四区域:在main函数里对子函数HanShu的调用。

【53.3   子函数被其它函数调用时候的执行顺序。】

       子函数被其它函数调用时,子函数的名字就相当于一个跳转地址,而子函数的定义部分就是要跳转的实际地址,单片机在主函数里遇到子函数名字,就直接跳转到子函数定义那里执行子函数内部的代码,执行完子函数后再返回到主函数,此时返回到主函数哪里呢?答:因为子函数已经被执行了一次,所以返回到主函数中的子函数名字后面,然后继续往下执行main函数其它剩余的代码。请看下面这个代码的执行顺序,一目了然:
/*---C语言学习区域的开始。-----------------------------------------------*/

   void HanShu(void);   //子函数的声明
   void HanShu(void)    //子函数的定义
   {
      语句1;
      语句2;
   }
   void main() //主函数
   {
      语句3;
      HanShu() ;      //子函数的被调用
      语句4;

      while(1)
      {

      }
}
/*---C语言学习区域的结束。-----------------------------------------------*/

      执行顺序分析:单片机从主函数main那里进来往下执行,先执行“语句3”,接着遇到HanShu名字的跳转地址,然后马上跳转到HanShu的定义部分,执行“语句1”,“语句2”,执行完子函数HanShu的定义部分,就马上返回到主函数,继续执行HanShu名字后面的“语句4”。整个执行语句的先后顺序如下:
      语句3;
      语句1;
      语句2;
      语句4;

【53.4   例程练习和分析。】

      现在编写一个练习程序来体验一下函数的使用。
      程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/
void HanShu(void);   //子函数声明的第一区域

unsigned chara;   //全局变量定义的第二区域

void HanShu(void)    //子函数定义的第三区域
{
   a++;    //子函数的代码语句
}
void main() //主函数
{
   a=0;
   a++;
   HanShu() ;      //子函数被调用的第四区域
   a++;
   View(a);//把第1个数a发送到电脑端的串口助手软件上观察。
   while(1)
   {

   }
}
/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:
开始...

第1个数
十进制:3
十六进制:3
二进制:11

分析:      
      变量a为3。单片机从main主函数进来,主函数里有2条“a++”,再加上子函数里也有1条“a++”,因此累加了3次,从0变成了3.

【53.5   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2017-1-23 13:58:34

本帖最后由 jianhong_wu 于 2017-1-23 14:13 编辑

第五十四节: 从全局变量和局部变量中感悟“栈”为何物。

【54.1   本节阅读前的名词约定。】

       变量可以粗略的分成两类,一类是全局变量,一类是局部变量。如果更深一步精细划分,全局变量还可以分成“普通全局变量”和“静态全局变量”,局部变量也可以分成“普通局部变量”和“静态局部变量”,也就是说,若精细划分,可以分成四类。其中“静态全局变量”和“静态局部变量”多了一个前缀“静态”,这个前缀“静态”是因为在普通的变量前面多加了一个修饰关键词“static”,这部分的内容后续章节会讲到。本节重点为了让大家理解内存模型的“栈”,暂时不考虑“静态变量”的情况,人为约定,本节所涉及的“全局变量”仅仅默认为“普通全局变量”,“局部变量”仅仅默认为“普通局部变量”。

【54.2   如何判定全局变量和局部变量?】

       全局变量就是在函数外面定义的变量,局部变量就是在函数内部定义的变量,这是最直观的判定方法。下面的例子能很清晰地说明全局变量和局部变量的判定方法:

unsigned char a;   //在函数外面定义的,所以是全局变量。
void main()//主函数
{
    unsigned char b; //在函数内部定义的,所以是局部变量。
    b=a;
    while(1)
    {

    }
}

【54.3   全局变量和局部变量的内存模型。】

       单片机内存包括ROM和RAM两部分,ROM存储的是单片机程序中的指令和一些不可更改的常量数据,而RAM存放的是可以被更改的变量数据,也就是说,全局变量和局部变量都是存放在RAM,但是,虽然都是存放在RAM,全局变量和局部变量之间的内存模型还是有明显的区别的,因此,分了两个不同的RAM区,全局变量占用的RAM区称为“全局数据区”,局部变量占用的RAM区称为“栈”,因为我后面会用宾馆来比喻“栈”,为了方便记忆,大家可以把“栈”想象成 “客栈”来记忆。它们的内存模型到底有什么本质的区别呢?“全局数据区”就像你自己家的房间,是唯一的,一个房间的地址只能你一个人住(假设你还没结婚的时候),而且是永久的,所以说每个全局变量都有唯一对应的RAM地址,不可能重复的。而“栈”就像宾馆客栈,一年下来每天晚上住的人不一样,每个人在里面居住的时间是有期限的,不是长久的,一个房间的地址一年下来每天可能住进不同的人,不是唯一的。“全局数据区”的全局变量拥有永久产权,“栈”区的局部变量只能临时居住在宾馆客栈,地址不是唯一的,有期限的。全局变量像私人区,局部变量像公共区。“栈”的这片公共区,是给程序里所有函数内部的局部变量共用的,函数被调用的时候,该函数内部的每个局部变量就会被分配对应到“栈”的某个RAM地址,函数调用结束后,该局部变量就失效,因此它对应的“栈”的RAM空间就被收回以便给下一个被调用的函数的局部变量占用。请看下面这个例子,我借用“宾馆客栈”来比喻局部变量所在的“栈”。
void HanShu(void);   //子函数的声明
void HanShu(void)    //子函数的定义
{
    unsigned char a;   //局部变量
    a=1;
}
void main() //主函数
{
    HanShu() ;      //子函数的调用
}

      分析:上述例子,单片机从主函数main往下执行,首先遇到HanShu子函数的调用,所以就跳到HanShu函数的定义那里开始执行,此时的局部变量a开始被分配在RAM的“栈区”的某个地址,相当于你入住宾馆被分配到某个房间。单片机执行完子函数HanShu后,局部变量a在RAM的“栈区”所分配的地址被收回,局部变量a消失,被收回的RAM地址可能会被系统重新分配给其它被调用的函数的局部变量,此时相当于你离开宾馆,从此你跟那个宾馆的房间没有啥关系,你原来在宾馆入住的那个房间会被宾馆老板重新分配给其他的客人入住。全局变量的作用域是永久性不受范围限制的,而局部变量的作用域就是它所在函数的内部范围。全局变量的“全局数据区”是永久的私人房子(这里的“永久”仅仅是举一个例子,别拿“70年产权”来抬杠),局部变量的“栈”是临时居住的“客栈”。重要的事情说两遍,再次总结如下:
    (1)每定义一个新的全局变量,就意味着多开销一个新的RAM内存。而每定义一个局部变量,只要在函数内部所定义的局部变量总数不超过单片机的“栈”区,此时的局部变量不开销新的RAM内存,因为局部变量是临时借用“栈”区的,使用后就还给“栈”,“栈”是公共区,可以重复利用,可以服务若干个不同的函数内部的局部变量。
    (2)单片机每次进入执行函数时,局部变量都会被初始化改变,而全局变量则不会被初始化,全局变量是一直保存之前最后一次更改的值。

【54.4   三个常见疑问。】

       第一个疑问:
       问:“全局数据区”和“栈区“是谁在幕后分配的,怎么分配的?
       答:是C编译器自动分配的,至于怎么分配,谁分配多一点,谁分配少一点,C编译器会有一个默认的比例分配,我们一般都不用管。

       第二个疑问:
       问:“栈”区是临时借用的,子函数被调用的时候,它内部的局部变量才会“临时”被分配到“栈”区的某个地址,那么问题来了,谁在幕后主持“栈区”这些分配的工作,难道也是C编译器?C编译器不是在编译程序的时候一次性就做完了编译工作然后就退出历史舞台了吗?难道我们程序已经在单片机内部运转的时候,编译器此时还在幕后指手画脚的起作用?
       答:单片机已经上电开始运行程序的时候,编译器是不可能起作用的。所以,真相只有一个,“栈区”分配给函数内部局部变量的工作,确实是C编译器做的,唯一需要注意的地方是,它不是“现炒现卖”,而是在单片机上电前,C编译器就把所有函数内部的局部变量的分配工作就规划好了,都指定了如果某个函数一旦被调用,该函数内部的哪个局部变量应该分到“栈区”的哪个地址,C编译器都是事先把这些“后事”都交代完毕了才“结束自己的生命”,后面,等单片机上电开始工作的时候,虽然C编译器此时“不在”了,但是单片机都是严格按照C编译器交代的“遗嘱”开始工作和分配“栈区”的。因此,“栈区”的“临时分配”非真正严格意义上的“临时分配”。

      第三个疑问:
      问:函数内部所定义的局部变量总数不超过单片机的“栈”区的RAM数量,那,万一超过了“栈”区的RAM数量,后果严重吗?
      答:后果特别严重。这种情况,专业术语叫“爆栈”。程序会出现异常,而且是莫名其妙的异常。为了避免这种情况,一般在编写程序的时候,函数内部都不能定义大数组的局部变量,局部变量的数量不能定义太多太大,尤其要避免刚才所说的定义开辟大数组局部变量这种情况。大数组的定义应该定义成全局变量,或者定义成“静态的局部变量”(“静态”这部分相关的内容后面章节会讲到)。有一些C编译器,遇到“爆栈”的情况,会好心跟你提醒让你编译不过去,但是也有一些C编译器可能就不会给你提醒,所以大家以后做项目写函数的时候,要对“爆栈”心存敬畏。

【54.5   全局变量和局部变量的优先级。】

      刚才说到,全局变量的作用域是永久性并且不受范围限制的,而局部变量的作用域就是它所在函数的内部范围,那么问题来,假如局部变量和全局变量的名字重名了,此时函数内部执行的变量到底是局部变量还是全局变量?这个问题就涉及到优先级。注意,当面对同名的局部变量和全局变量时,函数内部执行的变量是局部变量,也就是局部变量在函数内部要比全局变量的优先级高。为了深刻理解“全局变量和局部变量的优先级”,强烈建议大家必须仔细看完下面列举的三个练习例子。

【54.6   例程练习和分析。】

      请看下面第一个例子:

/*---C语言学习区域的开始。-----------------------------------------------*/

unsigned char a=5;      //此处第1个a是全局变量。

    void main() //主函数
{
    unsigned char a=2;//此处第2个a是局部变量。跟上面全局变量的第1个a重名了!

    View(a);//把a发送到电脑端的串口助手软件上观察。
    while(1)
    {

    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/

分析:
      上述例子,有2个变量重名了!其中一个是全局变量,另外一个是局部变量。此时输出显示的结果是5还是2?正确的答案是2。因为在函数内部,函数内部的局部变量比全局变量的优先级更加高。此时View(a)是第2个局部变量的a,而不是第1个全局变量的a。虽然这里的两个a重名了,但是它们的内存模型不一样,第1个全局变量的a是分配在“全局数据区”是具有唯一的地址的,而第2个局部变量的a是被分配在临时的“栈”区的,寄生在main函数内部。


       再看下面第二个例子:

/*---C语言学习区域的开始。-----------------------------------------------*/
void HanShu(void); //函数声明
unsigned char a=5;      //此处第1个a是全局变量。
void HanShu(void)   //函数定义
{
    unsigned char a=3;//此处第2个a是局部变量。
}
    void main() //主函数
{
    unsigned char a=2;//此处第3个a也是局部变量。
    HanShu();//子函数被调用
    View(a);//把a发送到电脑端的串口助手软件上观察。
    while(1)
    {

    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/

分析:
      上述例子,有3个变量重名了!其中一个是全局变量,另外两个是局部变量。此时输出显示的结果是5还是3还是2?正确的答案是2。因为,HanShu这个子函数是被调用结束之后,才执行View(a)的,就意味HanShu函数内部的局部变量(第2个局部变量a)是在执行View(a)语句的时候就消亡不存在了,所以此时View(a)的a是第3个局部变量的a(在main函数内部定义的局部变量的a)。


       再看下面第三个例子:

/*---C语言学习区域的开始。-----------------------------------------------*/
void HanShu(void); //函数声明
unsigned char a=5;      //此处第1个a是全局变量。
void HanShu(void)   //函数定义
{
    unsigned char a=3;//此处第2个a是局部变量。
}
    void main() //主函数
{
    HanShu();//子函数被调用
    View(a);//把a发送到电脑端的串口助手软件上观察。
    while(1)
    {

    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/

分析:
       上述例子,有2个变量重名了!其中一个是全局变量,另外一个是局部变量。此时输出显示的结果是5还是3?正确的答案是5。因为,HanShu这个子函数是被调用结束之后,才执行View(a)的,就意味HanShu函数内部的局部变量(第2个局部变量)是在执行View(a)语句的时候就消亡不存在了,同时,因为此时main函数内部也没有定义a的局部变量,所以此时View(a)的a是必然只能是第1个全局变量的a(在main函数外面定义的全局变量的a)。

【54.7   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2017-1-24 16:38:01

本帖最后由 jianhong_wu 于 2017-1-24 16:57 编辑

第五十五节: 函数的作用和四种常见书写类型。

【55.1   函数和变量的命名规则。】

       函数的名字和变量的名字一样,一般是由“字母,数字,下划线”三者组成。第1个字符不能是数字,必须是字母或者下划线“_”,后面紧跟的第2个字符开始可以是数字。在C语言中名字所用的字母是区分大小写的。可以用下划线“_”,但是不可以用横杠“-”。名字不能跟C编译系统已经征用的关键字重名,比如不能用“unsigned ”,“char”,“static”等系统关键词,跟古代时不能跟皇帝重名一样,要避尊者讳。

【55.2   函数的作用和分类。】

       函数的作用。通常把一些可能反复用到的算法或者过程封装成一个函数,函数就是一个模块,给它输入特定的参数,就可以输出想要的结果,比如一个加法函数,只要输入加数和被加数,然后就会输出相加结果之和,里面具体的算法过程只要写一次就可以重复调用,极大的节省单片机程序容量,也节省程序开发人员的工作量。还有一类函数,它从封装上看无所谓“输入输出”,这类函数往往是针对某一种可能重复使用的“过程”。
       函数的分类。暂时排除指针的情况下(指针的内容后续章节会讲到),从输入输出的角度来看,有四种常见的书写类型。分别是“无输出无输入,无输出有输入,有输出无输入,有输出有输入”。“输出”是看函数名的前缀,前缀如果是void表示“无输出”,否则就是“有输出”。“输入”是看函数名括号里的内容,如果是void或者是空着就表示“无输入”,否则就是“有输入”。“输出”和“输入”是比较通俗的说法,专业一点的说法是,“有输出”表示函数“有返回”,“无输出”表示函数“无返回”。“有输入”表示函数“有形参”,“无输入”表示函数“无形参”。下面举一个加法函数的例子,分别用四种不同的函数类型来实现,通过对比它们之间的差别,来体会它们在书写方面有哪些不同,又有哪些规律。

【55.3   第1类:“无输出”“无输入”的函数。】

    unsigned char a;//此变量用来接收最后相加结果的和。
unsigned char g=2;
unsigned char h=3;
void HanShu(void)//“无输出”“无输入”函数的定义。
{
   a=g+h;
}
main()
{
    HanShu();   //函数的调用。此处括号内的形参void要省略,否则编译不通过。
}

      分析:void HanShu(void),此函数名的前缀是void,括号内也是void,属于“无输出”“无输入”函数。这类函数表面看是“无输出”“无输入”,其实内部是通过全局变量来输入输出的,比如上面的例子就是靠a,g,h这三个全局变量来传递信息,只不过这类表达方式比较隐蔽,没有那么直观。

【55.4   第2类:“无输出”“有输入”的函数。】
unsigned char b;//此变量用来接收最后相加结果的和。
void HanShu(unsigned char i,unsigned char k)   //“无输出”“有输入”函数的定义。
{
   b=i+k;
}
main()
{
    HanShu(2,3);//函数的调用。
}

       分析:void HanShu(unsigned char i,unsigned char k),此函数名的前缀是void,括号内是(unsigned char i,unsigned char k),属于“无输出”“有输入”的函数。括号的两个变量i和k是函数内的局部变量,也是跟对外的桥梁接口,它们有一个专业的名称叫形参。外部要调用此函数时,只要给括号填入对应的变量或者数值,这些变量和数值就会被复制一份传递给作为函数形参的局部变量(比如本例子中的i和k),从而外部调用者跟函数内部就发生了数据信息的传递。这种书写方式的特点是把输入接口封装了出来。

【55.5   第3类:“有输出”“无输入”的函数。】

unsigned char c;   //此变量用来接收最后相加结果的和。
unsigned char m=2;
unsigned char n=3;
unsigned char HanShu(void)   //“有输出”“无输入”函数的定义。
{
   unsigned char p;
   p=m+n;
   return p;
}
main()
{
    c=HanShu();//函数的调用。此处括号内的形参void要省略,否则编译不通过。
}

       分析:unsigned char HanShu(void),此函数名的前缀是unsigned char类型,括号内是void,属于“有输出”“无输入”的函数。函数前缀的unsigned char表示此函数最后退出时会返回一个unsigned char类型的数据给外部调用者。而且这类函数内部必须有一个return语句配套,表示立即退出当前函数并且返回某个变量或者常量的数值给外部调用者。这种书写方式的特点是把输出接口封装了出来。

【55.6   第4类:“有输出”“有输入”的函数。】

unsigned char d;    //此变量用来接收最后相加结果的和。
unsigned char HanShu(unsigned char r,unsigned char s)   //“有输出”“有输入”函数的定义
{
    unsigned char t;
    t=r+s;
    return t;
}
main()
{
    d=HanShu(2,3);//函数的调用。
}

       分析:unsigned char HanShu(unsigned char r,unsigned char s),此函数名的前缀是unsigned char类型,括号内是(unsigned char r,unsigned char s),属于“有输出”“有输入”的函数。输入输出的特点跟前面介绍的函数一样,不多讲。这种书写方式的特点是把输出和输入接口都封装了出来。

【55.7   函数在被“调用”时需要注意的地方。】

      函数的三要素是“声明,定义,调用”。函数在被“调用”的时候,对于“无输入”的函数,形参的void关键词要省略,否则编译不通过,这里仅仅是指在函数在被“调用”的时候。

【55.8   例程练习和分析。】

      现在编写一个练习程序,要求编写4个不同“输入输出”封装的函数,它们每个函数所实现的功能都是一样的,都是加法的算法函数,它们之间仅仅是外观的封装接口不同而已。

/*---C语言学习区域的开始。-----------------------------------------------*/

void hanshu_1(void);
void hanshu_2(unsigned char i,unsigned char k);   
unsigned char hanshu_3(void);   
unsigned char hanshu_4(unsigned char r,unsigned char s);

unsigned char a;    //此变量用来接收第1个函数最后相加结果的和。
unsigned char g=2;
unsigned char h=3;

unsigned char b;    //此变量用来接收第2个函数最后相加结果的和。

unsigned char c;    //此变量用来接收第3个函数最后相加结果的和。
unsigned char m=2;
unsigned char n=3;

unsigned char d;    //此变量用来接收第4个函数最后相加结果的和。

void hanshu_1(void)//第1类:“无输出”“无输入”。
{
   a=g+h;
}
void hanshu_2(unsigned char i,unsigned char k)//第2类:“无输出”“有输入”。
{
   b=i+k;
}

unsigned char hanshu_3(void)   //第3类:“有输出”“无输入”。
{
   unsigned char p;
   p=m+n;
   return p;
}

unsigned char hanshu_4(unsigned char r,unsigned char s)//第4类:“有输出”“有输入”。
{
   unsigned char t;
   t=r+s;
   return t;
}

    void main() //主函数
{
    hanshu_1();       //第1类:“无输出”“无输入”的函数调用。这里的形参的void要省略。
    hanshu_2(2,3);    //第2类:“无输出”“有输入”的函数调用。
    c=hanshu_3();   //第3类:“有输出”“无输入”的函数调用。这里的形参的void要省略。
    d=hanshu_4(2,3);//第4类:“有输出”“有输入”的函数调用。
    View(a);//把a发送到电脑端的串口助手软件上观察。
    View(b);//把b发送到电脑端的串口助手软件上观察。
    View(c);//把c发送到电脑端的串口助手软件上观察。
    View(d);//把d发送到电脑端的串口助手软件上观察。
    while(1)
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/

       在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:5
十六进制:5
二进制:101

第2个数
十进制:5
十六进制:5
二进制:101

第3个数
十进制:5
十六进制:5
二进制:101

第4个数
十进制:5
十六进制:5
二进制:101

分析:
       变量a为5。
       变量b为5。
       变量c为5。
       变量d为5。

【55.9   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2017-2-12 09:57:15

本帖最后由 jianhong_wu 于 2017-2-12 10:14 编辑

第五十六节: return在函数中的作用以及四个容易被忽略的功能。

【56.1   return深入讲解。】

       return在英语单词中有“返回”的意思,上一节提到,凡是“有输出”的函数,函数内部必须有一个“return+变量或者常量”与之配套,表示返回的结果给外部调用者接收,这个知识点很容易理解,但是容易被忽略的是另外四个功能:
       第一个是return语句隐含了立即退出的功能。退出哪?退出当前函数。只要执行到return语句,就马上退出当前函数。即使return语句身陷多层while或者for的循环中,它也毫不犹豫立即退出当前函数。
       第二个是return语句可以出现在函数内的任何位置。可以出现在第一行代码,也可以出现在中间的某行代码,也可以出现在最后一行的代码,它的位置不受限制。很多初学者有个错觉,以为return只能出现在最后一行,这是错的。
       第三个是return语句不仅仅可以用在“有输出”的函数,也可以用在“无输出”的函数,也就是可以用在前缀是void的函数里。回顾上一节,在“有输出”的函数里,return后面紧跟一个变量或者常量,表示返回的数,但是在“无输出”的函数里,因为是“无输出”,此时return后面不用跟任何变量或者常量,这种写法也是合法的,表示返回的是空的。此时return主要起到立即退出当前函数的作用。
       第四个是return语句可以在一个函数里出现N多次,次数不受限制,不一定必须只能一次。不管一个函数内有多少个return语句,只要任何一个return语句被单片机执行到,就立即退出当前函数。

【56.2   中途立即退出的功能。】

       下面的书写格式是合法的:

void HanShu(void)//“无输出”函数的定义。
{
      语句1;
      return; //立即退出当前函数。对于这类“无输出”函数,return后面没有跟任何变量或者常量。
      语句2;
      return; //立即退出当前函数。对于这类“无输出”函数,return后面没有跟任何变量或者常量。
      语句3;
      return; //立即退出当前函数。对于这类“无输出”函数,return后面没有跟任何变量或者常量。

}

       分析:当HanShu此函数被调用时,单片机从“语句1”往下执行,当遇到第一个return语句后,马上退出当前函数。后面的“语句2”和“语句3”等代码永远不会被执行到。多说一句,大家仔细看看return后面跟了什么数没有?什么都没有。因为此函数的前缀是void的,是“无输出”的。

【56.3   身陷多层while或者for的循环时的惊人表现。】

      下面的书写格式是合法的:

void HanShu(void)//“无输出”函数的定义。
{
    语句1;
    while(1)//第一个循环
    {
      while(1)//第二个循环中的循环
       {
          return; //立即退出当前函数。
       }
       语句2;
       return; //立即退出当前函数。
    }
    语句3;
    return; //立即退出当前函数。
}

       分析:当HanShu此函数被调用时,单片机从“语句1”往下执行,先进入第一个循环,接着进入第二个循环中的循环,然后遇到第一个return语句,于是马上退出当前函数。后面的“语句2”和“语句3”等代码永远不会被执行到。此函数中,虽然表面看起来有那么多可怕的循环约束着,但是一旦碰上return语句都是浮云,立刻退出当前函数。

【56.4   在“有输出”函数里的书写格式。】

      把上面例子中“无输出”改成“有输出”的函数后:

unsigned char HanShu(void)//“有输出”函数的定义。
{
    unsigned char a=9;
    语句1;
    while(1)//第一个循环
    {
      while(1)//第二个循环中的循环
      {
          return a; //返回a变量的值,并且立即退出当前函数。
      }
      语句2;
         return a; //返回a变量的值,并且立即退出当前函数。
    }
    语句3;
    return a; //返回a变量的值,并且立即退出当前函数。
}

       分析:因为此函数是“有输出”的函数,所以return语句后面必须配套一个变量或者常量,此例子中配套的是a变量。当HanShu函数被调用时,单片机从“语句1”往下执行,先进入第一个循环,接着进入第二个循环中的循环,然后遇到第一个“return a”语句,马上退出当前函数。而后面的“语句2”和“语句3”等代码是永远不会被执行到的。再一次说明了,return语句不仅有返回某数的功能,还有立即退出的重要功能。

【56.5   项目中往往是跟if语句搭配使用。】

       前面的例子只是为了解释return语句的执行顺序和功能,实际项目中,如果中间有多个return语句,中间的return语句不可能像前面的例子那样单独使用,它往往是跟if语句一起搭配使用,否则单独用return就没有什么意义。比如:

void HanShu(void)//“无输出”函数的定义。
{
    语句1;
    if(某条件满足)
    {
       return; //立即退出当前函数。
    }
    语句2;
    if(某条件满足)
    {
       return; //立即退出当前函数。
    }
    语句3;
}

       分析:单片机从“语句1”开始往下执行,至于在哪个“return”语句处退出当前函数,就要看哪个if的条件满不满足了,如果所有的if的条件都不满足,此函数会一直执行完最后的“语句3”才退出当前函数。

【56.6   例程练习和分析。】

      写一个简单的除法函数,在除法运算中,除数不能为0,如果发现除数为0,就立即退出当前函数,并且返回运算结果默认为0。

/*---C语言学习区域的开始。-----------------------------------------------*/

//函数的声明。
unsigned int ChuFa(unsigned int BeiChuShu,unsigned int ChuShu);

//变量的定义。
unsigned int a;//此变量用来接收除法的运算结果。
unsigned int b;//此变量用来接收除法的运算结果。

//函数的定义。
unsigned int ChuFa(unsigned int BeiChuShu,unsigned int ChuShu)
{
    unsigned int Shang;//返回的除法运算结果:商。
    if(0==ChuShu)   //如果除数等于0,就立即退出当前函数,并返回0
    {
      return 0; // 退出当前函数并且返回0.此时后面的代码不会被执行。
    }

    Shang=BeiChuShu/ChuShu;//除法运算的算法
    return Shang;//返回最后的运算结果:商。并且退出当前函数。
}

    void main() //主函数
{
    a=ChuFa(128,0);//函数调用。128除以0,把商返回给a变量。
    b=ChuFa(128,2);//函数调用。128除以2,把商返回给b变量。

    View(a);//把a发送到电脑端的串口助手软件上观察。
    View(b);//把b发送到电脑端的串口助手软件上观察。
    while(1)
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:0
十六进制:0
二进制:0

第2个数
十进制:64
十六进制:40
二进制:1000000

分析:
       变量a为0。
       变量b为64。

【56.7   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


页: 1 2 [3] 4 5 6 7
查看完整版本: 从单片机基础到程序框架(连载)