搜索
bottom↓
楼主: 吴坚鸿

从单片机基础到程序框架(连载)

  [复制链接]

出50入0汤圆

发表于 2016-4-24 09:55:31 | 显示全部楼层
chun_hua82 发表于 2016-4-11 00:29
《从业将近十年,手把手教你单片机程序框架》写的非常好!通俗易懂,从中学到了很多东西!感谢楼主! 支持! ...

也是,感谢分享精神

出0入0汤圆

发表于 2016-4-27 21:31:25 | 显示全部楼层
太好了,支持楼主连载。

出0入0汤圆

发表于 2016-4-27 21:50:03 | 显示全部楼层
牛人贴,顶

出0入0汤圆

发表于 2016-4-27 22:11:50 | 显示全部楼层
先mark一下,坐等大神杰作

出0入0汤圆

发表于 2016-4-27 22:36:23 | 显示全部楼层
好佩服楼主的毅力!

出0入0汤圆

 楼主| 发表于 2016-5-2 10:52:01 | 显示全部楼层
第十六节:十进制与十六进制。


【16.1   十进制与十六进制各自的应用场合。】

       C语言程序里只用了十进制和十六进制这两种书写格式,有的初学者会问,为什么没有用二进制?我的回答是:不是没有用二进制,而是十六进制已经代表了二进制,因为十六进制就是二进制的缩写形式,所以可以把十六进制和二进制看作是同一个东西。
      十进制和十六进制各自有什么应用场合?十六进制方便人们理解机器,通常应用在配置寄存器,底层通讯驱动,底层IO口驱动,以及数据的移位、转换、合并等场合,在底层驱动程序方面经常要用到。而十进制则方便人们直观理解数值的大小,在程序应用层要经常用到。总之,进制只是数据的表现形式而已,不管是什么进制的数,最终经过编译后都可以看做是二进制的数据。

【16.2   十进制与十六进制相互转换的方法。】

       十进制与十六进制如何相互转换?其实很多教科书上都有介绍它们之间如何通过手工计算进行转换的方法,这种方法当然是有助于我们深入理解数据的含义和转换关系,有兴趣的朋友可以自己找相关书籍来看看,但是在实际应用中,我本人是从来没有用过这种手工计算方法,而我用的方法是最简单直接的,就是借助电脑自带的计算器进行数制转换即可。现在把这种方法介绍给大家,以WIND7系统的电脑为例来讲解详细的操作步骤。

                  
                   图16.2.1.1  点击“所有程序”选项切换到系统自带程序的窗口      

                  
                   图16.2.1.2  在“附件”子菜单下点击“计算器”启动此软件

                  
                   图16.2.1.3  已启动的“计算器”软件界面

       第一步:打开电脑自带的计算器。
       点击电脑左下角“开始” 菜单,在菜单中点击“所有程序”选项切换到自带程序的窗口,在此窗口下,再点击“附件”的文件夹图标,在“附件”子菜单下点击“计算器”启动此软件。

----------------------------------步骤之间的分割线----------------------------------------

                  
                    图16.2.2.1  把“计算器”的主界面切换到“程序员”界面      

                  
                    图16.2.2.2  已打开的“程序员”界面   

       第二步:把“计算器”的主界面切换到“程序员”界面。
点击打开左上角“查看”的下拉菜单,在下拉菜单中选择“程序员”选项。

----------------------------------步骤之间的分割线----------------------------------------

                     
                     图16.2.3.1  在十进制的选项下输入十进制的数据     

                     
                     图16.2.3.2  把十进制的数据转换成十六进制的数据

       第三步:十进制转换成十六进制的方法。
       点击勾选中“十进制”选项,在此选项下输入十进制的数据,输入数据后,再切换点击勾选“十六进制”,即可完成从十进制到十六进制的数据转换。比如输入十进制的“230”,切换到十六进制后就变成了“E6”。

----------------------------------步骤之间的分割线----------------------------------------

                     
                      图16.2.4.1  在十六进制的选项下输入十六进制的数据     

                     
                      图16.2.4.2  把十六进制的数据转换成十进制的数据

       第四步:十六进制转换成十进制的方法。
       点击勾选中“十六进制”选项,在此选项下输入十六进制的数据,输入数据后,再切换点击勾选“十进制”,即可完成从十六进制到十进制的数据转换。比如输入十六进制的“AC”,切换到十进制后就变成了“172”。

----------------------------------步骤之间的分割线----------------------------------------

       第五步:十六进制,十进制,八进制,二进制它们四者之间相互转换的方法。
       我们看到“计算器”软件里已经包含了十六进制,十进制,八进制,二进制这四个选项,所以它们之间相互转换的方法跟上面介绍的步骤是一样的。

----------------------------------步骤之间的分割线----------------------------------------

【16.3   例程练习和分析。】

      现在我们编写一个程序来验证上面讲到的两个例子:
     (1)输入十进制的230,看看它的十六进制是什么样的。
     (2)输入十六进制的AC,看看它的十进制是什么样的。

       程序代码如下:

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

  2. void main() //主函数
  3. {
  4.        unsigned char a;    //定义一个变量a,并且分配了1个字节的RAM空间。
  5.        unsigned char b;    //定义一个变量b,并且分配了1个字节的RAM空间。

  6.        a=230;    //把十进制的230赋值给变量a,在串口助手上观察一下它的十六进制是不是E6。
  7.        b=0xAC;   //把十六进制的AC赋值给变量b,在串口助手上观察一下它的十进制是不是172。
  8.         View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
  9.     View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。

  10.     while(1)  
  11.     {
  12.     }
  13. }

  14. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:230
  4. 十六进制:E6
  5. 二进制:11100110

  6. 第2个数
  7. 十进制:172
  8. 十六进制:AC
  9. 二进制:10101100
复制代码


分析:        
       通过实验结果,发现在单片机上转换的结果和在电脑自带“计算器”上转换的结果是一样的。

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-5-9 14:35:47 | 显示全部楼层
第十七节:加法运算的5种常用格式。

【17.1   单片机本身具备基础的数学算术能力。】

       单片机本身是一个成品,本身就具备了基础的加减乘除能力,把单片机当做一个大人,我们需要做的只是沟通而已,叫他做加法他就做加法,叫他做减法就他就做减法,至于他是如何计算出来的不用管,“他”本身内部的电路结构就具备了这种基础运算的能力。人机沟通依然是用C语言,本节讲的加法运算,用的C语言符号跟我们日常用的数学加法符号是一样的,都是符号“+”。多说一句,单片机这种内置的基础运算能力并不是无限大的,而是数值不能超过某个范围,如果在加数或者运算结果的数值范围超过4294967295的情况下,要继续实现这类加法运算,这个就需要我们在单片机本身基础的运算能力上专门去编写一套大数据算法的程序才能实现,这个大家暂时不用深入理解,先学好当前基础再说。

【17.2   加法语法格式。】

      加法语法格式:
      “保存变量”=“加数1”+“加数2”+...+“加数N”;
      含义:右边的“加数”与“加数”相加(这里统一把平时所说的被加数也归类为加数),并且把最终的运算结果赋值给左边的“保存变量”。注意,这里的符号“=”不是等于号的意思,而是赋值的意思。左边的“保存变量”必须是变量,不能是常量,否则编译时会报错。而右边的“加数”既可以是变量,也可以是常量,也可以是“保存变量”本身自己。多说一句,什么是变量和什么是常量?变量就是可以在程序中被更改的,是分配的一个RAM空间。而常量往往就是常数值,或者是被分配在ROM空间的一个具体数值。下面根据右边“加数”与“加数”的不同组合,列出了加法运算的5种常用格式。
       第1种:“加数1”是常量,“加数2”是常量。比如:
  1.        unsigned char a;
  2.        a=3+15;
复制代码

       分析:数字“3”和“15”都是常量。执行上述语句后,保存变量a变成了18。

       第2种:“加数1”是变量,“加数2”是常量。比如:
  1.        unsigned char b;
  2.        unsigned char x=10;
  3.        b=x+15;
复制代码

       分析:x是变量,“15”是常量。由于原来x变量里面的数值是10,执行上述语句后,保存变量b变成   了25。而变量x则保持不变,执行完所有语句后x还是10。

      第3种:“加数1”是变量,“加数2”是变量。比如:
  1.       unsigned char c;
  2.       unsigned char x=10;
  3.       unsigned char y=6;
  4.       c=x+y;
复制代码

      分析:x是变量,y也是变量。由于原来x变量里面的数值是10,y变量里面的数值是6,执行上述语句后,保存变量c变成了16。而变量x和y则保持不变,x还是10,y还是6。

      第4种:“加数1”是保存变量本身,“加数2”是常量。比如:
  1.       unsigned char d=2;
  2.       d=d+18;
  3.       d=d+7;
复制代码

      分析:d是保存变量本身,“18”是常量。这类语句有一个特点,具备了自加功能,可以更改自己本身的数值。比如原来保存变量d的数值是2,执行“d=d+18;”语句后,d变成了20,接着再执行完“d=d+7;”语句后,d最后变成了27。

       第5种:“加数1”是保存变量本身,“加数2”是变量。比如:
  1.        unsigned char e=2;
  2.        unsigned char x=10;
  3.        unsigned char y=6;
  4.        e=e+x;
  5.        e=e+y;
复制代码

       分析:e是保存变量,x与y都是变量。这类语句有一个特点,具备了自加功能,可以更改自己本身的数值。比如原来保存变量e的数值是2,x的数值是10,执行“e=e+x;”语句后,e变成了12。由于y的数值是6,接着再执行完“e=e+y;”语句后,所以e最后变成了18。

【17.3   例程练习和分析。】

        现在我们编写一个程序来验证上面讲到的5个加法例子:
        程序代码如下:

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

  2. void main() //主函数
  3. {
  4.         unsigned char a;      //定义一个变量a,并且分配了1个字节的RAM空间。
  5.         unsigned char b;      //定义一个变量b,并且分配了1个字节的RAM空间。
  6.         unsigned char c;      //定义一个变量c,并且分配了1个字节的RAM空间。
  7.         unsigned char d=2;    //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为2.
  8.         unsigned char e=2;    //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为2.

  9.         unsigned char x=10;   //定义一个变量x,并且分配了1个字节的RAM空间。初始化默认为10.
  10.         unsigned char y=6;    //定义一个变量y,并且分配了1个字节的RAM空间。初始化默认为6.        

  11.         //第1种:“加数1”是常量,“加数2”是常量。
  12.         a=3+15;

  13.         //第2种:“加数1”是变量,“加数2”是常量。
  14.         b=x+15;

  15.         //第3种:“加数1”是变量,“加数2”是变量。
  16.         c=x+y;


  17.         //第4种:“加数1”是保存变量本身,“加数2”是常量。
  18.         d=d+18;
  19.         d=d+7;

  20.         //第5种:“加数1”是保存变量本身,“加数2”是变量。
  21.         e=e+x;
  22.         e=e+y;

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

  28.     while(1)  
  29.     {
  30.     }
  31. }

  32. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:18
  4. 十六进制:12
  5. 二进制:10010

  6. 第2个数
  7. 十进制:25
  8. 十六进制:19
  9. 二进制:11001

  10. 第3个数
  11. 十进制:16
  12. 十六进制:10
  13. 二进制:10000

  14. 第4个数
  15. 十进制:27
  16. 十六进制:1B
  17. 二进制:11011

  18. 第5个数
  19. 十进制:18
  20. 十六进制:12
  21. 二进制:10010
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-5-9 15:50:06 | 显示全部楼层
感谢鸿哥,我也是希望能早点见到鸿哥的书,为了知识,也为了情怀,更多是为了感谢,新书出来后,肯定要买一本!

出0入0汤圆

发表于 2016-5-11 11:59:50 | 显示全部楼层
吴哥太有毅力了,必须顶下!

出0入0汤圆

 楼主| 发表于 2016-5-15 12:19:15 | 显示全部楼层
第十八节:连加、自加、自加简写、自加1。

【18.1   连加。】

      上一节的加法例子中,右边的加数只有两个。实际上,C语言规则没有限制加数的个数,它的通用格式如下:
      “保存变量”=“加数1”+“加数2”+...+“加数N”;
       当右边的加数个数超过两个的时候,这种情况就是我所说的“连加”,每个加数的属性没有限定,可以是常量,也可以是变量。比如:
  1.        a=1+69+102;   //加数全部是常量。
  2.        b=q+x+y+k+r;  //加数全部是变量。
  3.        c=3+x+y+5+k;  //加数有的是常量,有的是变量。
复制代码

       连加的运行顺序是,赋值符号“=”右边的加数挨个相加,把每一次的运算结果放在一个临时的隐蔽变量里,这个隐蔽的变量我们看不到,是单片机系统内部参与运算时的专用寄存器,等右边所有的加数连加的计算结果出来后,再把这个隐蔽变量所保存的计算结果赋值给左边的“保存变量”。

【18.2   自加、自加简写、自加1。】

       什么是自加?当赋值符号“=”右边的加数只要其中有一个是“保存变量”本身时,这种情况就是“自加”,自加在程序里有一个特点,只要加数不为0,那么每执行一次这行代码,“保存变量”本身就会增大一次,不断执行这行代码,“保存变量”本身就会不断增大,而每次的增大量就取决于赋值符号“=”右边所有加数之和。自加的常见格式如下:
      “保存变量”=“保存变量”+“加数1”;
      “保存变量”=“保存变量”+“加数1”+“加数2”+...+“加数N”;
      在这类自加计算式中,当右边的加数有且仅有一个是“保存变量”本身时,那么上述自加计算式可以简写成如下格式:
      “保存变量”+=“加数1”;
      “保存变量”+=“加数1”+“加数2”+...+“加数N”;
      这种格式就是“自加简写”。现在举几个例子如下:
  1.       d+=6;  //相当于d=d+6;
  2.       e+=x;  //相当于e=e+x;
  3.       f+=18+y+k; //相当于f=f+18+y+k;
复制代码

      这些例子都是很常规的自加简写,再跟大家讲一种很常用的特殊简写。当右边只有两个加数,当一个加数是“保存变量”,另一个加数是常数1时,格式如下:
      “保存变量”=“保存变量”+1;
      这时候,可以把上述格式简写成如下两种格式:
      “保存变量”++;
      ++“保存变量”;
     这两种格式也是俗称的“自加1”操作。比如:
  1.      g++;  //相当于g=g+1或者g+=1;
  2.      ++h;  //相当于h=h+1或者h+=1;
复制代码

     也就是说自加1符号“++”可以在变量的左边,也可以在变量的右边,它们在这里本质是一样的,没有差别,但是,如果是在某些特定情况下,这时自加1符号“++”在左边还是在右边是有差别的,有什么差别呢?这个内容以后再讲。

【18.3   例程练习和分析。】

      现在我们编写一个程序来验证上面讲到的例子:
      程序代码如下:

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

  2. void main() //主函数
  3. {
  4.         unsigned char a;      //定义一个变量a,并且分配了1个字节的RAM空间。
  5.         unsigned char b;      //定义一个变量b,并且分配了1个字节的RAM空间。
  6.         unsigned char c;      //定义一个变量c,并且分配了1个字节的RAM空间。
  7.         unsigned char d=5;    //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为5.
  8.         unsigned char e=5;    //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为5.
  9.         unsigned char f=5;    //定义一个变量f,并且分配了1个字节的RAM空间。初始化默认为5.
  10.         unsigned char g=5;    //定义一个变量g,并且分配了1个字节的RAM空间。初始化默认为5.   


  11.         unsigned char h=5;    //定义一个变量h,并且分配了1个字节的RAM空间。初始化默认为5.

  12.         unsigned char q=1;    //定义一个变量q,并且分配了1个字节的RAM空间。初始化默认为1.
  13.         unsigned char x=3;    //定义一个变量x,并且分配了1个字节的RAM空间。初始化默认为3.
  14.         unsigned char y=6;    //定义一个变量y,并且分配了1个字节的RAM空间。初始化默认为6.   


  15.         unsigned char k=2;    //定义一个变量k,并且分配了1个字节的RAM空间。初始化默认为2.
  16.         unsigned char r=8;    //定义一个变量r,并且分配了1个字节的RAM空间。初始化默认为8.

  17.         //第1个知识点:连加。
  18.         a=1+69+102;     //加数全部是常量。a的结果为:172。
  19.         b=q+x+y+k+r;    //加数全部是变量。b的结果为:20。
  20.         c=3+x+y+5+k;   //加数有的是常量,有的是变量。c的结果为:19。

  21.         //第2个知识点:自加。
  22.         d+=6;  //相当于d=d+6;  d的结果为:11。
  23.         e+=x;  //相当于e=e+x;  e的结果为:8。
  24.         f+=18+y+k; //相当于f=f+18+y+k;  f的结果为:31。

  25.         //第3个知识点:自加1。
  26.         g++;  //相当于g=g+1或者g+=1;  g的结果为:6。
  27.         ++h;  //相当于h=h+1或者h+=1;  h的结果为:6。

  28.         View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
  29.     View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。
  30.         View(c);   //把第3个数c发送到电脑端的串口助手软件上观察。
  31.     View(d);   //把第4个数d发送到电脑端的串口助手软件上观察。
  32.         View(e);   //把第5个数e发送到电脑端的串口助手软件上观察。
  33.         View(f);   //把第6个数f发送到电脑端的串口助手软件上观察。
  34.     View(g);   //把第7个数g发送到电脑端的串口助手软件上观察。
  35.         View(h);   //把第8个数h发送到电脑端的串口助手软件上观察。

  36.     while(1)  
  37.     {
  38.     }
  39. }

  40. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:172
  4. 十六进制:AC
  5. 二进制:10101100

  6. 第2个数
  7. 十进制:20
  8. 十六进制:14
  9. 二进制:10100

  10. 第3个数
  11. 十进制:19
  12. 十六进制:13
  13. 二进制:10011

  14. 第4个数
  15. 十进制:11
  16. 十六进制:B
  17. 二进制:1011

  18. 第5个数
  19. 十进制:8
  20. 十六进制:8
  21. 二进制:1000

  22. 第6个数
  23. 十进制:31
  24. 十六进制:1F
  25. 二进制:11111

  26. 第7个数
  27. 十进制:6
  28. 十六进制:6
  29. 二进制:110

  30. 第8个数
  31. 十进制:6
  32. 十六进制:6
  33. 二进制:110
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-5-15 20:45:17 | 显示全部楼层
支持一下!

出0入0汤圆

发表于 2016-5-19 09:27:10 | 显示全部楼层
很不错的帖子,顶一下

出0入0汤圆

发表于 2016-5-19 10:04:08 | 显示全部楼层
值得学习,NICE

出0入0汤圆

发表于 2016-5-19 10:22:58 | 显示全部楼层
收藏,学习中……

出0入0汤圆

发表于 2016-5-19 13:31:06 | 显示全部楼层
支持!!!

出0入0汤圆

 楼主| 发表于 2016-5-22 11:32:49 | 显示全部楼层
第十九节:加法运算的溢出。

【19.1   什么是加法运算的溢出?】

       前面章节介绍的三种数据类型unsigned char ,unsigned int ,unsigned long,它们的数值都是有最大范围的,分别是255,65535,4294967295。如果运算结果超过了变量本身的最大范围,会出现什么结果、有什么规律,这就是本节要讲的溢出。
      (1)什么是溢出?先看一个例子如下:
  1.        unsigned char a;
  2.        a=0x8536;
复制代码

       分析:
       因为a是unsigned char变量,位数是8位,也就是一个字节,而0x8536是16位,两个字节,这种情况下,把两字节的0x8536强行赋值给单字节变量a,变量a只能接收到最低8位的一个字节0x36,而高8位的一个字节0x85就被丢失了,这个就是本节所说的溢出。
     (2)再看一个例子如下:
  1.       unsigned char b=0xff;
  2.       b=b+1;
复制代码

      分析:
      b默认值是0xff,再加1后,变成了0x0100保存在一个隐藏的中间变量,然后再把这个中间变量赋值给单字节变量b,b只能接收到低8位的一个字节0x00,所以运算后b的数值由于溢出变成了0x00。
     (3)再看一个例子如下:
  1.       unsigned char c=0xff;
  2.       c=c+2;
复制代码

      分析:
     c默认值是0xff,再加2后,变成了0x0101保存在一个隐藏中间变量,然后再把这个中间变量赋值给单字节变量c,c只能接收到低8位的一个字节0x01,所以运算后c的数值由于溢出变成了0x01。
    (4)再看一个例子如下:
  1.       unsigned int d=0xfffe;
  2.       d=d+5;
复制代码

      分析:
      d默认值是0xfffe,再加5后,变成了0x10003保存在一个隐藏中间变量,由于这个隐藏的中间变量是unsigned int类型,只能保存两个字节的数据,所以在中间变量这个环节就溢出了,实际上隐藏的中间变量只保存了0x0003,然后再把这个中间变量赋值给16位的两字节变量d,d理所当然就是0x0003。
     (5)再看一个例子如下:
  1.       unsigned long e=0xfffffffe;
  2.       e=e+5;
复制代码

      分析:
      e默认值是0xfffffffe,再加5后,变成了0x100000003保存在一个隐藏中间变量,由于这个隐藏的中间变量是unsigned long类型,只能保存四个字节的数据,所以在中间变量这个环节就溢出了,实际上隐藏的中间变量只保存了0x00000003,然后再把这个中间变量赋值给32位的四字节变量e,e理所当然也是0x00000003。

【19.2   例程练习和分析。】

       现在我们编写一个程序来验证上面讲到的例子:
       程序代码如下:

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

  2. void main() //主函数
  3. {
  4.         unsigned char a;             //一个字节
  5.         unsigned char b=0xff;        //一个字节
  6.         unsigned char c=0xff;        //一个字节
  7.         unsigned int  d=0xfffe;      //两个字节
  8.         unsigned long e=0xfffffffe;  //四个字节

  9.         a=0x8536;
  10.         b=b+1;
  11.         c=c+2;
  12.         d=d+5;
  13.         e=e+5;

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

  19.     while(1)  
  20.     {
  21.     }
  22. }

  23. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:54
  4. 十六进制:36
  5. 二进制:110110

  6. 第2个数
  7. 十进制:0
  8. 十六进制:0
  9. 二进制:0

  10. 第3个数
  11. 十进制:1
  12. 十六进制:1
  13. 二进制:1

  14. 第4个数
  15. 十进制:3
  16. 十六进制:3
  17. 二进制:11

  18. 第5个数
  19. 十进制:3
  20. 十六进制:3
  21. 二进制:11
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-5-29 11:55:32 | 显示全部楼层
第二十节:隐藏中间变量为何物?

【20.1   隐藏中间变量为何物?】

      “隐藏中间变量”虽然视之不见摸之不着,但是像空气一样无处不在。它有什么规律,是什么类型,数值范围是多大,研究它有什么实用价值?这就是本节要解开之谜。
      前面章节提到,两个加数相加,其结果暂时先保存在一个“隐藏中间变量”里,运算结束后才把这个“隐藏中间变量”赋值给左边的“保存变量”。这里的“隐藏中间变量”到底是unsigned int类型还是unsigned long类型?为了研究它的规律,我在keil自带的C51编译环境下,专门编写了几个测试程序来观察实际运行的结果。
      “保存变量”=“加数1”+“加数2”;
      下面分别变换“保存变量”、“加数1”、“加数2”这三个元素的数据类型,来观察“隐藏中间变量”背后的秘密。

      (1)“unsigned int”=“unsigned char”+“unsigned char”;
  1.        unsigned int a;
  2.        unsigned char x1=0x12;
  3.        unsigned char y1=0xfe;
  4.        a=x1+y1;
复制代码

      运算结果:a等于0x0110。
      分析过程:两个char类型的数相加其运算结果暂时保存在“隐藏中间变量”,当运算结果大于两个“加数”unsigned char本身时,并没有发生溢出现象,unsigned int类型的“保存变量”a最终得到了完整的结果0x0110。
      初步结论:这种情况,“隐藏中间变量”估计为unsigned int 类型。

     (2)“unsigned long”=“unsigned int”+“unsigned char”;
  1.       unsigned long b;
  2.       unsigned int x2=0xfffe;
  3.       unsigned char y2=0x12;
  4.       b=x2+y2;
复制代码

      运算结果:b等于十六进制的0x0010。
      分析过程:一个unsigned int类型的数与一个unsigned char类型的数相加,当运算结果大于其中最大加数unsigned int类型本身时,因为左边的“保存变量”本来就是unsigned long类型,所以我本来以为运算结果应该是unsigned long类型的0x00010010,但是实际结果出乎我的意料,最终结果是unsigned int类型的0x0010,显然发生了溢出现象。
      初步结论:这种情况,“隐藏中间变量”估计为unsigned int 类型。

     (3)“unsigned long”=“常量”+“常量”;
  1.       unsigned long c;
  2.       c=50000+50000;
复制代码

      运算结果:c等于100000。
      分析过程:unsigned  int的最大数据范围是65535,而两个常量相加,其结果超过了65535却还能完整保存下来。
      初步结论:这种右边加数都是常量的情况下,“隐藏中间变量”估计等于左边的“保存变量”类型。

     (4)“unsigned long”=“unsigned int”+“常量”;
  1.       unsigned long d;
  2.       unsigned long e;
  3.       unsigned int x3=50000;
  4.       d=x3+30000;
  5.       e=x3+50000;
复制代码

     运算结果:d等于14464,e等于100000。
     分析过程:本来以为d应该等于80000的,结果却是14464显然发生了溢出。而同样的条件下,e是100000却没有发生溢出。
     个人结论:这个现象让我突然崩溃,实在研究不下去了。这是一种很怪异的现象,为什么同样的类型,因为常量的不同,一个发生了溢出,另外一个没有发生溢出?这时的“隐藏中间变量”到底是unsigned int类型还是unsigned long类型?我无法下结论。经过上述简单的测试,我发现规律是模糊的,模糊的规律就不能成为规律。如果真要按这种思路研究下去,那真是没完没了,因为还有很多情况要研究,当超过3个以上加数相加,同时存在unsigned long,unsigned int,unsigned char,以及“常量”这4种类型时又是什么规律?在不同的C编译器里又会是什么现象?即使把所有情况的规律摸清楚了又能怎么样,因为那么繁杂很容易忘记导致出错。有什么解决的办法吗?

【20.2   解决办法。】

       “当遇到有争议的问题时,与其参与争议越陷越深,还不如想办法及时抽身绕开争议。”根据这个指导思想,我提出一种解决思路“为了避免出现意想不到的溢出,在实际项目中,所有参与运算的变量都预先转化为unsigned long变量,再参与运算。”
       当然,也可能有人会问,如果计算结果超过了unsigned long最大范围时怎么办?我的回答是:首先,大多数项目的计算量都比较简单,一般情况下都不会超过unsigned long最大范围,但是,如果真遇到有可
能超过unsigned long最大范围的运算项目时,那么就要用另外一种BCD码数组的运算算法来解决,而这个方法本节暂时不介绍,等以后再讲。       继续回到刚才的话题,“为了避免出现意想不到的溢出,在实际项目中,所有参与运算的变量都预先转化为unsigned long变量,再参与运算。”如何把所有的运算变量都转化为unsigned long变量?现在介绍一下这个方法。
       第一个例子:比如上述第(2)个例子,其转换方法如下:
  1. unsigned long f;
  2. unsigned int x2=0xfffe;
  3. unsigned char y2=0x12;
  4. unsigned  long t;  //多增加一个long类型的变量,用来变换类型。
  5. unsigned  long r;  //多增加一个long类型的变量,用来变换类型。
  6. t=0;    //把变量的高位和低位全部清零。
  7. t=x2;   //把x2的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
  8. r=0;    //把变量的高位和低位全部清零。
  9. r=y2;   //把y2的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
  10. f=t+r;
复制代码

       运算结果:f等于十六进制的0x00010010,没有发生溢出现象。

       第二个例子:比如上述第(4)个例子,其转换方法如下:
  1. unsigned long g;
  2. unsigned long h;
  3. unsigned  int x3=50000;
  4. unsigned  long t;  //多增加一个long类型的变量,用来变换类型
  5. t=0;  //把变量的高位和低位全部清零。
  6. t=x3;   //把x3的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
  7. g=t+30000;
  8. h=t+50000;
复制代码

       运算结果:g等于80000,h等于100000。都没有发生溢出。

【20.3   例程练习和分析。】

        现在我们编写一个程序来验证上面讲到的例子:
        程序代码如下:

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

  2. void main() //主函数
  3. {
  4.     unsigned int a;   //第(1)个例子
  5.     unsigned char x1=0x12;
  6.     unsigned char y1=0xfe;

  7.     unsigned long b;  //第(2)个例子
  8.     unsigned int x2=0xfffe;
  9.     unsigned char y2=0x12;

  10.     unsigned long c;  //第(3)个例子

  11.         unsigned long d;  //第(4)个例子
  12.         unsigned long e;
  13.         unsigned int x3=50000;

  14.     unsigned long f;  //第(2)个例子改进之后

  15.     unsigned long g;  //第(4)个例子改进之后
  16.     unsigned long h;

  17.     unsigned  long t;  //多增加一个long类型的变量,用来变换类型。
  18.     unsigned  long r;  //多增加一个long类型的变量,用来变换类型。


  19.     //第(1)个例子
  20.     a=x1+y1;  

  21.     //第(2)个例子
  22.     b=x2+y2;

  23.     //第(3)个例子
  24.     c=50000+50000;

  25.     //第(4)个例子
  26.         d=x3+30000;
  27.         e=x3+50000;

  28.     //第(2)个例子改进之后
  29.     t=0;   //把变量的高位和低位全部清零。
  30.     t=x2;  //把x2的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
  31.     r=0;   //把变量的高位和低位全部清零。
  32.     r=y2;   //把y2的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
  33.     f=t+r;

  34.     //第(4)个例子改进之后
  35.     t=0;    //把变量的高位和低位全部清零。
  36.     t=x3;   //把x3的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
  37.     g=t+30000;
  38.     h=t+50000;


  39.         View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
  40.     View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。
  41.         View(c);   //把第3个数c发送到电脑端的串口助手软件上观察。
  42.     View(d);   //把第4个数d发送到电脑端的串口助手软件上观察。
  43.         View(e);   //把第5个数e发送到电脑端的串口助手软件上观察。
  44.     View(f);   //把第6个数f发送到电脑端的串口助手软件上观察。
  45.         View(g);   //把第7个数g发送到电脑端的串口助手软件上观察。
  46.         View(h);   //把第8个数h发送到电脑端的串口助手软件上观察。

  47.     while(1)  
  48.     {
  49.     }
  50. }

  51. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:272
  4. 十六进制:110
  5. 二进制:100010000

  6. 第2个数
  7. 十进制:16
  8. 十六进制:10
  9. 二进制:10000

  10. 第3个数
  11. 十进制:100000
  12. 十六进制:186A0
  13. 二进制:11000011010100000

  14. 第4个数
  15. 十进制:14464
  16. 十六进制:3880
  17. 二进制:11100010000000

  18. 第5个数
  19. 十进制:100000
  20. 十六进制:186A0
  21. 二进制:11000011010100000

  22. 第6个数
  23. 十进制:65552
  24. 十六进制:10010
  25. 二进制:10000000000010000

  26. 第7个数
  27. 十进制:80000
  28. 十六进制:13880
  29. 二进制:10011100010000000

  30. 第8个数
  31. 十进制:100000
  32. 十六进制:186A0
  33. 二进制:11000011010100000
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-6-3 23:04:16 | 显示全部楼层
继续跟进。说得相当透彻,

出0入0汤圆

发表于 2016-6-4 08:13:30 | 显示全部楼层
好帖子, 留名

出0入0汤圆

发表于 2016-6-4 09:21:36 | 显示全部楼层
深入浅出,图文并茂,我是单片机爱好者,虽然工作总也经常用到,但是原理和深入的东西还理解不到为,来向老师学习

出0入0汤圆

 楼主| 发表于 2016-6-5 16:17:33 | 显示全部楼层
第二十一节:减法运算的5种常见格式。

【21.1   减法语法格式。】

        减法语法格式:
       “保存变量”=“减数1”-“减数2”-...-“减数N”;
       含义:右边的“减数”与“减数”相减(这里暂时把平时所说的被减数也归类为减数),并且把最终的运算结果赋值给左边的“保存变量”。注意,这里的符号“=”不是等于号的意思,而是赋值的意思。左边的“保存变量”必须是变量,不能是常量,否则编译时会报错。右边的“减数”既可以是变量,也可以是常量,也可以是“保存变量”本身自己。多说一句,什么是变量和常量?变量是可以在程序中被更改的,被分配的一个RAM空间。常量往往是数字,或者被分配在ROM空间的一个具体数值。下面根据右边“减数”与“减数”的不同组合,列出了减法运算的5种常见格式。

        第1种:“减数1”是常量,“减数2”是常量。比如:
  1.         unsigned char a;
  2.         a=15-3;
复制代码

        分析:数字“15”和“3”都是常量。执行上述语句后,保存变量a变成了12。

        第2种:“减数1”是变量,“减数2”是常量。比如:
  1.         unsigned char b;
  2.         unsigned char x=15;
  3.         b=x-10;
复制代码

        分析:x是变量,“10”是常量。由于原来x变量里面的数值是15,执行上述语句后,保存变量b变成了5。而变量x则保持不变,x还是15。

        第3种:“减数1”是变量,“减数2”是变量。比如:
  1.         unsigned char c;
  2.         unsigned char x=15;
  3.         unsigned char y=6;
  4.         c=x-y;
复制代码

        分析:x是变量,y也是变量。由于原来x变量里面的数值是15,y变量里面的数值是6,执行上述语句后,保存变量c变成了9。而变量x和y则保持不变,x还是15,y还是6。

        第4种:“减数1”是保存变量本身,“减数2”是常量。比如:
  1.         unsigned char d=18;
  2.         d=d-2;
  3.         d=d-7;
复制代码

        分析:d是保存变量,“2”和“7”都是常量。这类语句有一个特点,具备了自减功能,可以更改自己本身的数值。比如原来保存变量d的数值是18,执行“d=d-2;”语句后,d变成了16,接着再执行完“d=d-7;”语句后,d最后变成了9。

        第5种:“减数1”是保存变量本身,“减数2”是变量。比如:
  1.         unsigned char e=28;
  2.         unsigned char x=15;
  3.         unsigned char y=6;
  4.         e=e-x;
  5.         e=e-y;
复制代码

        分析:e是保存变量,x与y都是变量。这类语句有一个特点,具备了自减功能,可以更改自己本身的数值。比如原来保存变量e的数值是28,执行“e=e-x;”语句后,e变成了13,接着再执行完“e=e-y;”语句后,e最后变成了7。

【21.2   例程练习和分析。】

       现在我们编写一个程序来验证上面讲到的5个减法例子:
       程序代码如下:

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

  2. void main() //主函数
  3. {
  4.     unsigned char a;     //定义一个变量a,并且分配了1个字节的RAM空间。
  5.     unsigned char b;     //定义一个变量b,并且分配了1个字节的RAM空间。
  6.     unsigned char c;     //定义一个变量c,并且分配了1个字节的RAM空间。
  7.     unsigned char d=18;  //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为18.
  8.     unsigned char e=28;  //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为28.

  9.     unsigned char x=15;  //定义一个变量x,并且分配了1个字节的RAM空间。初始化默认为15.
  10.     unsigned char y=6;   //定义一个变量y,并且分配了1个字节的RAM空间。初始化默认为6.        

  11.     //第1种:“减数1”是常量,“减数2”是常量。
  12.     a=15-3;

  13.     //第2种:“减数1”是变量,“减数2”是常量。
  14.     b=x-10;

  15.     //第3种:“减数1”是变量,“减数2”是变量。
  16.     c=x-y;

  17.     //第4种:“减数1”是保存变量本身,“减数2”是常量。
  18.     d=d-2;
  19.     d=d-7;

  20.     //第5种:“减数1”是保存变量本身,“减数2”是变量。
  21.     e=e-x;
  22.     e=e-y;

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

  28.     while(1)  
  29.     {
  30.     }
  31. }

  32. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:12
  4. 十六进制:C
  5. 二进制:1100

  6. 第2个数
  7. 十进制:5
  8. 十六进制:5
  9. 二进制:101

  10. 第3个数
  11. 十进制:9
  12. 十六进制:9
  13. 二进制:1001

  14. 第4个数
  15. 十进制:9
  16. 十六进制:9
  17. 二进制:1001

  18. 第5个数
  19. 十进制:7
  20. 十六进制:7
  21. 二进制:111
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-6-12 10:17:46 | 显示全部楼层
本帖最后由 吴坚鸿 于 2016-6-12 16:09 编辑

第二十二节:连减、自减、自减简写、自减1。

【22.1   连减。】

       上一节的减法例子中,右边的减数只有两个。实际上,C语言规则没有限制减数的个数,它的通用格式如下:
  1.         “保存变量”=“减数1”-“减数2”-“减数3”-...-“减数N”;
复制代码

       当右边的减数个数超过两个的时候(这里暂时把平时所说的被减数也归类为减数),这种情况就是“连减”。每个减数的属性没有限定,可以是常量,也可以是变量。比如:
  1.        a=68-3-15;    //减数全部是常量。
  2.        b=q-x-y-k;    //减数全部是变量。
  3.        c=63-x-5-k;   //减数有的是常量,有的是变量。
复制代码

       连减的运行顺序是,赋值符号“=”右边的减数挨个相减,把每一次的运算结果放在一个临时的隐藏中间变量里,这个隐藏的变量我们看不到,是单片机系统内部参与运算时的专用寄存器,等右边所有减数连
减的计算结果出来后,再把隐藏变量所保存的计算结果赋值给左边的“保存变量”。

【22.2   自减、自减简写、自减1。】

      什么是自减?当赋值符号“=”右边的第1个减数是“保存变量”本身时(这里暂时把平时所说的被减数也归类为减数),这种情况就是“自减”。自减在程序里有一个特点,只要第2个减数不为0,那么每执行一次这行代码,“保存变量”本身就会减小一次,不断执行这行代码,“保存变量”本身就会不断减小,而每次的减小量就取决于赋值符号“=”右边从第2个减数开始后面所有减数之和。自减的常见格式如下:
  1.       “保存变量”=“保存变量”-“减数2”;
  2.       “保存变量”=“保存变量”-“减数2”-“减数3”-...-“减数N”;
复制代码

      在这类自减计算式中,当只有右边的第1个减数是“保存变量”本身时,那么上述自减计算式可以简写成如下格式:
  1.       “保存变量”-=“减数2”;
  2.       “保存变量”-=(“减数2”+“减数3”+...+“减数N”);
复制代码

      这种格式就是“自减简写”。现在举几个例子如下:
  1.       d-=6;  //相当于d=d-6;
  2.       e-=x;  //相当于e=e-x;
  3.       f-=18-y-k; //相当于f=f-(18-y-k);
复制代码

      这些例子都是很常规的自减简写,再跟大家讲一种很常用的特殊简写。当右边只有两个减数,而第1个减数是“保存变量”,第2个减数是常数1时,格式如下:
  1.       “保存变量”=“保存变量”-1;
复制代码

      这时候,可以把上述格式简写成如下两种格式:
  1.       “保存变量”--;
  2.        --“保存变量”;
复制代码

      这两种格式也是俗称的“自减1”操作。比如:
  1.       g--;  //相当于g=g-1或者g-=1;
  2.       --h;  //相当于h=h-1或者h-=1;
复制代码

      自减1符号“--”可以在变量的左边,也可以在变量的右边,它们在这里本质是一样的,没有差别。当然,如果是在循环条件语句中,这时自减1符号“--”在左边还是在右边是有一点点微弱的差别,这方面的内容以后再讲。

【22.3   例程练习和分析。】

       现在我们编写一个程序来验证上面讲到的例子:
       程序代码如下:

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

  2. void main() //主函数
  3. {
  4.         unsigned char a;     //定义一个变量a,并且分配了1个字节的RAM空间。
  5.         unsigned char b;     //定义一个变量b,并且分配了1个字节的RAM空间。
  6.         unsigned char c;     //定义一个变量c,并且分配了1个字节的RAM空间。
  7.         unsigned char d=65;  //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为65.
  8.         unsigned char e=38;  //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为38.
  9.         unsigned char f=29;  //定义一个变量f,并且分配了1个字节的RAM空间。初始化默认为29.
  10.         unsigned char g=5;   //定义一个变量g,并且分配了1个字节的RAM空间。初始化默认为5.   


  11.         unsigned char h=5;   //定义一个变量h,并且分配了1个字节的RAM空间。初始化默认为5.  

  12.         unsigned char q=50;  //定义一个变量q,并且分配了1个字节的RAM空间。初始化默认为50.
  13.         unsigned char x=3;   //定义一个变量x,并且分配了1个字节的RAM空间。初始化默认为3.
  14.         unsigned char y=6;   //定义一个变量y,并且分配了1个字节的RAM空间。初始化默认为6.   


  15.         unsigned char k=2;   //定义一个变量k,并且分配了1个字节的RAM空间。初始化默认为2.

  16.         //第1个知识点:连减。
  17.         a=68-3-15;           //减数全部是常量。a的结果为:50。
  18.         b=q-x-y-k;           //减数全部是变量。b的结果为:39。
  19.         c=63-x-5-k;          //减数有的是常量,有的是变量。c的结果为:53。

  20.         //第2个知识点:自减简写。   
  21.         d-=6;               //相当于d=d-6;  d的结果为:59。
  22.         e-=x;               //相当于e=e-x;  e的结果为:35。
  23.         f-=18-y-k;          //相当于f=f-(18-y-k);  f的结果为:19。

  24.         //第3个知识点:自减1。
  25.         g--;                //相当于g=g-1或者g-=1;  g的结果为:4。
  26.         --h;                //相当于h=h-1或者h-=1;  d的结果为:4。

  27.         View(a);            //把第1个数a发送到电脑端的串口助手软件上观察。
  28.     View(b);            //把第2个数b发送到电脑端的串口助手软件上观察。
  29.         View(c);            //把第3个数c发送到电脑端的串口助手软件上观察。
  30.     View(d);            //把第4个数d发送到电脑端的串口助手软件上观察。
  31.         View(e);            //把第5个数e发送到电脑端的串口助手软件上观察。
  32.         View(f);            //把第6个数f发送到电脑端的串口助手软件上观察。
  33.     View(g);            //把第7个数g发送到电脑端的串口助手软件上观察。
  34.         View(h);            //把第8个数h发送到电脑端的串口助手软件上观察。

  35.     while(1)  
  36.     {
  37.     }
  38. }

  39. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:50
  4. 十六进制:32
  5. 二进制:110010

  6. 第2个数
  7. 十进制:39
  8. 十六进制:27
  9. 二进制:100111

  10. 第3个数
  11. 十进制:53
  12. 十六进制:35
  13. 二进制:110101

  14. 第4个数
  15. 十进制:59
  16. 十六进制:3B
  17. 二进制:111011

  18. 第5个数
  19. 十进制:35
  20. 十六进制:23
  21. 二进制:100011

  22. 第6个数
  23. 十进制:19
  24. 十六进制:13
  25. 二进制:10011

  26. 第7个数
  27. 十进制:4
  28. 十六进制:4
  29. 二进制:100

  30. 第8个数
  31. 十进制:4
  32. 十六进制:4
  33. 二进制:100
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-6-19 12:52:08 | 显示全部楼层
第二十三节:减法溢出与假想借位。

【23.1   减法溢出与假想借位。】

       英文“unsigned”的中文意思就是”无符号的”,延伸含义是“无负号无负数”的意思,所以unsigned char ,unsigned int ,unsigned long这三种类型数据都是无负号无负数的,取值只能是0和正数,那么问题来了,当被减数小于减数的时候,运算结果会是什么样子,有什么规律?这就是本节要研究的减法溢出。
       第一个例子:
  1.        unsigned char a;
  2.        a=0-1;
复制代码

       分析:
       左边的“保存变量”a的数据长度是1个字节8位,a=0-1可以看成是十六进制的a=0x00-0x01。由于0x00比0x01小,所以假想一下需要向高位借位,借位后成了a=0x100-0x01。所以a的最终结果是0xff(十进制是255),这个“假想一下需要向高位借位”的过程就是本节制造的新概念“假想借位”。根据“假想借位”这个规律,如果是b也是unsigned char 类型,那么b=2-5自然就相当于b=0x102-0x05,运算结果b等于0xfd(十进制是253)。
       第二个例子:
  1.        unsigned int c;
  2.        c=0-1;
复制代码

       分析:
       左边的“保存变量”c的数据长度是2个字节16位,c=0-1可以看成是十六进制的c=0x0000-0x0001。由于0x0000比0x0001小,所以假想一下需要向高位借位,借位后成了c=0x10000-0x0001。所以c的最终结果是0xffff(十进制是65535)。根据“假想借位”这个规律,如果是d也是unsigned  int 类型,那么d=2-5自然就相当于d=0x10002-0x0005,运算结果d等于0xfffd(十进制是65533)。
       综合分析:
       为什么上述例子中会出现数据越减越大的奇葩现象?是因为减法溢出,是因为“假想借位”中的“借”是“光借不还”。一句话,根本问题就是溢出问题。

【23.2   因为减法溢出,所以加减顺序......】

       第三个例子:请分析下面例子中e和f因加减运算顺序不同而引发什么问题。
  1.        unsigned char e;
  2.        unsigned char f;
  3.        e=1-6+7;
  4.        f=1+7-6;
复制代码

       用两种思路分析:
       第一种思路:只看过程不看结果。加减法的运算优先级是从左到右,e先减法后加法,1减去6就有溢出了,所以过程有问题。而f先加法后减法,整个过程没有问题。
       第二种思路:先看结果再分析过程。e的运算结果居然是2,f的运算结果也是2。好奇怪,既然e的过程有问题,为什么运算结果却没有问题?其实e发生两次溢出,第一次是减法溢出,第二次是加法溢出,所以“溢溢得正”(这句话是开玩笑的)。1-6“假想借位”后相当于0x101-0x06,运算结果等于0xfb(十进制是251),然后0xfb再加上0x07等于0x102,因为e是unsigned char 类型只有1个字节,根据加法溢出的规律,最后只保留了低8位的一个字节0x02,所以运算结果就是十进制的2。
      结论:
      虽然e的运算结果侥幸是对的,但是其运算过程发生了溢出是有问题的,当运算式子更复杂一些,比如有不同类型的变量时,就有可能导致运算结果也出错。所以得出的结论是:在加减法运中,为了减少出现减法溢出的现象,建议先加法后减法。在后续章节讲到的乘除法运算中,为了减小运算带来的误差也建议大家先乘法后除法。

【23.3   例程练习和分析。】

      现在我们编写一个程序来验证上面讲到的例子:
      程序代码如下:

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

  2. void main() //主函数
  3. {
  4.         unsigned char a;       //定义一个变量a,并且分配了1个字节的RAM空间。
  5.         unsigned char b;       //定义一个变量b,并且分配了1个字节的RAM空间。
  6.         unsigned int c;        //定义一个变量c,并且分配了2个字节的RAM空间。
  7.         unsigned int d;        //定义一个变量d,并且分配了2个字节的RAM空间。
  8.         unsigned char e;       //定义一个变量e,并且分配了1个字节的RAM空间。
  9.         unsigned char f;       //定义一个变量f,并且分配了1个字节的RAM空间。

  10.         //第一个例子,针对a与b都是unsigned char类型数据。     
  11.         a=0-1;  
  12.         b=2-5;

  13.         //第二个例子,针对c与d都是unsigned int类型的数据。
  14.         c=0-1;
  15.         d=2-5;        

  16.         //第三个例子,e与f的加减顺序不一样。
  17.     e=1-6+7;
  18.     f=1+7-6;

  19.         View(a);            //把第1个数a发送到电脑端的串口助手软件上观察。
  20.     View(b);            //把第2个数b发送到电脑端的串口助手软件上观察。
  21.         View(c);            //把第3个数c发送到电脑端的串口助手软件上观察。
  22.     View(d);            //把第4个数d发送到电脑端的串口助手软件上观察。
  23.         View(e);            //把第5个数e发送到电脑端的串口助手软件上观察。
  24.         View(f);            //把第6个数f发送到电脑端的串口助手软件上观察。

  25.     while(1)  
  26.     {
  27.     }
  28. }

  29. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:255
  4. 十六进制:FF
  5. 二进制:11111111

  6. 第2个数
  7. 十进制:253
  8. 十六进制:FD
  9. 二进制:11111101

  10. 第3个数
  11. 十进制:65535
  12. 十六进制:FFFF
  13. 二进制:1111111111111111

  14. 第4个数
  15. 十进制:65533
  16. 十六进制:FFFD
  17. 二进制:1111111111111101

  18. 第5个数
  19. 十进制:2
  20. 十六进制:2
  21. 二进制:10

  22. 第6个数
  23. 十进制:2
  24. 十六进制:2
  25. 二进制:10
复制代码

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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-6-27 11:15:00 | 显示全部楼层
第二十四节:借用unsigned long类型的中间变量可以减少溢出现象。

【24.1   为什么要借用unsigned long类型的中间变量?】

       为什么要借用unsigned long类型的中间变量进行算术运算?其实就是为了减少溢出的问题。溢出是因为数据超过了它的最大范围,unsigned char ,unsigned int ,unsigned long三种数据类型中,unsigned long的取值是最大的。当参与运算变量中存在非unsigned long类型的时候,在运算前,先让每个非unsigned long类型的变量借用一个unsigned long类型的中间变量,然后才开始运算,可以大大减少运算中的溢出问题。 unsigned long的取值是从0到4294967295,万一数据超过了4294967295怎么办?可用BCD码的数组方式进行运算,这种数组运算的方法我以后会跟大家介绍,初学者现在暂时不用深入了解它。

【24.2   如何借用unsigned long类型的中间变量?】

        借用中间变量的方法是引入中间变量,有多少个非unsigned long类型变量就引入多少个unsigned long中间变量,再借这个“壳”进行运算,最后再把中间变量的计算结果返回给实际变量。请看下面例子。

        转换之前:
  1. unsigned int  a;
  2. unsigned char x=195;
  3. unsigned long y=101;
  4. a=x-y;      //进行算术减法运算
复制代码

       分析:
      上述公式用到3个变量,其中a和x都不是unsigned long变量,因此需要为它们分别引入两个unsigned long类型的中间变量t和s,于是乎,继续往下看......

       转换之后:
  1. unsigned int  a;
  2. unsigned char x=195;
  3. unsigned long y=101;

  4. unsigned long t; //引入的中间变量t,用来给a借用。
  5. unsigned long s; //引入的中间变量s,用来给x借用。

  6. //第一步:使用之前先清零
  7. t=0;             //t在用之前,先把t的32位全部清零。
  8. s=0;             //s在用之前,先把s的32位全部清零。

  9. s=x;             //s接收x原数据,等效于x借用unsigned long中间变量s这个壳。
  10. t=s-y;           //此处unsigned long类型的t就默认代表了unsigned int类型的变量a。

  11. //第二步:因为其它的变量都是临时的,所以运算结束后再返回计算结果给原来的变量。
  12. a=t;             //运算结束后再把计算结果返回给原来的变量a。
复制代码

      分析:
      第一步:unsigned long类型的中间变量在转换之前为什么要先赋值0进行清零,比如上述代码的“s=0;”?因为它是32位的数据类型,它也是一个随机数,如果不清零,后续的其它类型的变量可能是16位或者8位的类型变量,这些宽度不一的变量在给32位的变量赋值的时候,只能覆盖到32位变量的低16位或者低8位,无法等效于实际借用者变量的数值,所以有可能会出错。
      第二步:因为其它的变量都是临时的,所以运算结束后应该再返回计算结果给原来的实际变量。在这里要多说一句,实际项目中,最后接收运算结果的变量应该根据项目所需去选择它的类型,建议尽量选择unsigned long类型吧,否则,如果中间变量的计算结果大于接收变量本身的类型范围,也会发生溢出。比如,上述最后一行代码a=t,如果此时t的数值大于65535,a也会发生溢出的现象。 但是如果a本身是unsigned long 类型,就不会发生这种现象。
       加法,乘法,除法在借用中间变量的时候,跟本节减法例子中的思路也大同小异。

【24.3   建议在算术运算中确保所有的变量都是unsigned long类型。】

       不管是以前讲的加法,现在讲的减法,还是未来讲的乘法和除法,我都会建议“在加减乘除四则运算中,凡是非unsigned long类型的变量,都应该借用unsigned long类型的中间变量进行运算,最后再返回计算结果给实际的变量。”unsigned long变量是三种数据类型中取值范围最大的数,借用此类型的中间变量,可以减少在简单运算中可能出现的溢出问题。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-7-4 09:18:30 | 显示全部楼层
第二十五节:乘法运算中的5种常用组合。

【25.1   乘法语法格式。】

      乘法语法格式:
      “保存变量”=“乘数1”*“乘数2”*..*“乘数N”;
      含义:为什么C语言的乘法符号并不是我们熟悉的“X”而是“*”?我猜测是因为“X”跟键盘的大写字母“X”重复有冲突了,而“*”轮廓跟“X”很相似,并且也可以在键盘上通过“Shift+8”的组合键直接键入“*”,所以用“*”作为乘法符号。上述乘法格式中,右边的“乘数”与“乘数”相乘(这里暂时把平时所说的被乘数也归类为乘数),并且把最终的运算结果赋值给左边的“保存变量”。注意,这里的符号“=”不是等于号的意思,而是赋值的意思。左边的“保存变量”必须是变量,不能是常量,否则编译时会报错。右边的“乘数”既可以是变量,也可以是常量,也可以是“保存变量”本身自己。多说一句,什么是变量和常量?变量是可以在程序中被更改的,被分配的一个RAM空间。常量往往是数字,或者被分配在ROM空间的一个具体数值。下面根据右边“乘数”与“乘数”的不同组合,列出了乘法运算的5种常用组合。

       第1种:“乘数1”是常量,“乘数2”是常量。比如:
  1.        unsigned char a;
  2.        a=15*3;
复制代码

       分析:数字“15”和“3”都是常量。执行上述语句后,保存变量a变成了45。

       第2种:“乘数1”是变量,“乘数2”是常量。比如:
  1.        unsigned char b;
  2.        unsigned char x=15;
  3.        b=x*10;
复制代码

       分析:x是变量,“10”是常量。由于原来x变量里面的数值是15,执行上述语句后,保存变量b变成了150。而变量x则保持不变,x还是15。

       第3种:“乘数1”是变量,“乘数2”是变量。比如:
  1.        unsigned char c;
  2.        unsigned char x=15;
  3.        unsigned char y=6;
  4.        c=x*y;
复制代码

       分析:x是变量,y也是变量。由于原来x变量里面的数值是15,y变量里面的数值是6,执行上述语句后,保存变量c变成了90。而变量x和y则保持不变,x还是15,y还是6。

       第4种:“乘数1”是保存变量本身,“乘数2”是常量。比如:
  1.        unsigned char d=18;
  2.        d=d*2;
  3.        d=d*7;
复制代码

       分析:d是保存变量,“2”和“7”都是常量。这类语句有一个特点,具备了自乘功能,可以更改自己本身的数值。 比如原来保存变量d的数值是18,执行“d=d*2;”语句后,d变成了36,接着再执行完“d=d*7;”语句后,d最后变成了252。

      第5种:“乘数1”是保存变量本身,“乘数2”是变量。比如:
  1.       unsigned char e=2;
  2.       unsigned char x=15;
  3.       unsigned char y=6;
  4.       e=e*x;
  5.       e=e*y;
复制代码

      分析:e是保存变量,x与y都是变量。这类语句有一个特点,具备了自乘功能,可以更改自己本身的数值。比如原来保存变量e的数值是2,执行“e=e*x;”语句后,e变成了30,接着再执行完“e=e*y;”语句后,e最后变成了180。

【25.2   例程练习和分析。】

      现在我们编写一个程序来验证上面讲到的5个乘法例子:
      程序代码如下:

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

  2. void main() //主函数
  3. {
  4.     unsigned char a;     //定义一个变量a,并且分配了1个字节的RAM空间。
  5.     unsigned char b;     //定义一个变量b,并且分配了1个字节的RAM空间。
  6.     unsigned char c;     //定义一个变量c,并且分配了1个字节的RAM空间。
  7.     unsigned char d=18;  //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为18.
  8.     unsigned char e=2;   //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为2.

  9.     unsigned char x=15;  //定义一个变量x,并且分配了1个字节的RAM空间。初始化默认为15.
  10.     unsigned char y=6;   //定义一个变量y,并且分配了1个字节的RAM空间。初始化默认为6.        

  11.     //第1种:“乘数1”是常量,“乘数2”是常量。
  12.     a=15*3;

  13.     //第2种:“乘数1”是变量,“乘数2”是常量。
  14.     b=x*10;

  15.     //第3种:“乘数1”是变量,“乘数2”是变量。
  16.     c=x*y;

  17.     //第4种:“乘数1”是保存变量本身,“乘数2”是常量。
  18.     d=d*2;
  19.     d=d*7;

  20.     //第5种:“乘数1”是保存变量本身,“乘数2”是变量。
  21.     e=e*x;
  22.     e=e*y;

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

  28.      while(1)  
  29.      {
  30.      }
  31. }

  32. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:45
  4. 十六进制:2D
  5. 二进制:101101

  6. 第2个数
  7. 十进制:150
  8. 十六进制:96
  9. 二进制:10010110

  10. 第3个数
  11. 十进制:90
  12. 十六进制:5A
  13. 二进制:1011010

  14. 第4个数
  15. 十进制:252
  16. 十六进制:FC
  17. 二进制:11111100

  18. 第5个数
  19. 十进制:180
  20. 十六进制:B4
  21. 二进制:10110100
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-7-12 10:15:47 | 显示全部楼层
第二十六节:连乘、自乘、自乘简写,溢出。

【26.1   连乘。】

      上一节的乘法例子中,右边的乘数只有两个。实际上,C语言规则没有限制乘数的个数,它的通用格式如下:
  1.        “保存变量”=“乘数1”*“乘数2”...*“乘数N”;
复制代码

      当右边的乘数个数超过两个的时候(这里暂时把平时所说的被乘数也归类为乘数),这种情况就是“连乘”。每个乘数的属性没有限定,可以是常量,也可以是变量。比如:
  1.       unsigned char x=3;   //定义一个变量x,初始化默认为3.
  2.       unsigned char y=6;   //定义一个变量y,初始化默认为6.      
  3.       unsigned char k=2;   //定义一个变量k,初始化默认为2.
  4.       a=2*5*3;  //乘数全部是常量。a的结果为30。
  5.       b=k*x*y;  //乘全部是变量。b的结果为36。
  6.       c=x*5*y;  //乘数,有的是常量,有的是变量。c的结果为90。
复制代码

      连乘的运行顺序是,赋值符号“=”右边的乘数挨个相乘,把每一次的运算结果放在一个临时的隐蔽中间变量里,这个隐蔽的变量我们看不到,是单片机系统内部参与运算时的专用寄存器,等右边所有乘数连乘的计算结果出来后,再把隐蔽变量所保存的计算结果赋值给左边的“保存变量”。

【26.2   自乘与自乘简写。】

      什么是自乘?当赋值符号“=”右边的乘数只要其中有一个是“保存变量”本身时,这种情况就是“自乘”,常见格式如下:
  1.       “保存变量”=“保存变量”*“乘数1”;
  2.       “保存变量”=“保存变量”*(“乘数1”*“乘数2”...*“乘数N”);
复制代码

      上述自乘计算式可以简写成如下格式:
  1.        “保存变量”*=“乘数1”;
  2.        “保存变量”*=“乘数1”*“乘数2”...*“乘数N”;
复制代码

       这种格式就是“自乘简写”。现在举几个例子如下:
  1.        unsigned char d=5;       //定义一个变量d,初始化默认为5.
  2.        unsigned char e=5;       //定义一个变量e,初始化默认为5.
  3.        unsigned char f=5;       //定义一个变量f,初始化默认为5.

  4.        unsigned char x=3;   //定义一个变量x,初始化默认为3.
  5.        unsigned char y=6;   //定义一个变量y,初始化默认为6.      
  6.        unsigned char k=2;   //定义一个变量k,初始化默认为2.
  7.        d*=6;     //相当于d=d*6;最后d的结果为30。
  8.        e*=x;     //相当于e=e*x;最后e的结果为15。
  9.        f*=2*y*k; //相当于f=f*(2*y*k);最后f的结果为120。
复制代码


【26.3   有没有“自乘1”的特殊写法?】

      之前在讲加法的自加和减法的自减运算时,还给大家介绍了它们另外一种特殊的简写方式。比如减法运算,当右边只有2减数,当一个减数是“保存变量”,另一个是常数1时,格式如下:
  1.        “保存变量”=“保存变量”-1;
复制代码

       这时候,可以把上述格式简写成如下两种格式:
  1.        “保存变量”--;
  2.       --“保存变量”;
复制代码

      这两种格式也是俗称的“自减1”操作。比如:
  1.        g--;  //相当于g=g-1或者g-=1;
  2.        --h;  //相当于h=h-1或者h-=1;
复制代码

      那么,本节所讲的自乘运算,有没有“g**”或者“**h”这种特殊的“自乘1”写法?答案很明显,C语言里没有“自乘1”这种特殊写法。因为任何一个数“自乘1”还是等于它本身,所以在乘法运算中这种特殊写法就没有存在的意义。多说一句,如果某天有朋友在某个地方看到“**h”这类语句,它的本意跟“自乘”没关系,而是跟C语言的另一块知识点“指针”有关。

【26.4   乘法的溢出。】

      乘法的溢出规律跟加减法的溢出规律是一样的。举一个例子如下:
  1.       unsigned char m=30;
  2.       unsigned char n=10;
  3.       unsigned char a;
  4.       a=m*n;  
复制代码

      分析:m与n相乘,相当于30乘以10,运算结果是300(十六进制是0x012c)保存在一个隐藏中间变量,根据前面加减法运算的规律,我猜测这个隐藏中间变量可能是unsigned int类型,然后再把这个中间变量赋值给单字节变量a,a只能接收十六进制的低8位字节0x2c,所以运算后a的数值由于溢出变成了十六进制的0x2c(十进制是44)。由于乘法的溢出规律跟加减法的溢出规律是一样的,所以不再多举例子。在实际项目中,为了减少溢出的现象,我建议,不管加减乘除,凡是参与运算的变量全部都应该转化成unsigned long变量,转化的方法已经在前面章节讲过,不再重复讲解这方面的内容。

【26.5   例程练习和分析。】

       现在编写一个程序来验证刚才讲到的连乘和自乘简写:
       程序代码如下:

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

  2. void main() //主函数
  3. {
  4.     unsigned char a;      
  5.     unsigned char b;      
  6. unsigned char c;      
  7.     unsigned char d=5;      //定义一个变量d,初始化默认为5.
  8.     unsigned char e=5;      //定义一个变量e,初始化默认为5.
  9.     unsigned char f=5;      //定义一个变量f,初始化默认为5.

  10.     unsigned char x=3;      //定义一个变量x,初始化默认为3.
  11.     unsigned char y=6;      //定义一个变量y,初始化默认为6.        
  12.      unsigned char k=2;     //定义一个变量k,初始化默认为2.

  13.      //第1个知识点:连乘。
  14.      a=2*5*3;              //乘数全部是常量。a的结果为30。
  15.      b=k*x*y;              //乘数全部是变量。b的结果为36。
  16.      c=x*5*y;              //乘数,有的是常量,有的是变量。c的结果为90。

  17.      //第2个知识点:自乘的简写。
  18.      d*=6;                 //相当于d=d*6;最后d的结果为30。
  19.      e*=x;                 //相当于e=e*x;最后e的结果为15。
  20.      f*=2*y*k;             //相当于f=f*(2*y*k);最后f的结果为120。

  21.      View(a);              //把第1个数a发送到电脑端的串口助手软件上观察。
  22.      View(b);              //把第2个数b发送到电脑端的串口助手软件上观察。
  23.      View(c);              //把第3个数c发送到电脑端的串口助手软件上观察。
  24.      View(d);              //把第4个数d发送到电脑端的串口助手软件上观察。
  25.      View(e);              //把第5个数e发送到电脑端的串口助手软件上观察。
  26.      View(f);              //把第6个数f发送到电脑端的串口助手软件上观察。

  27.      while(1)  
  28.      {
  29.      }
  30. }

  31. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:30
  4. 十六进制:1E
  5. 二进制:11110

  6. 第2个数
  7. 十进制:36
  8. 十六进制:24
  9. 二进制:100100

  10. 第3个数
  11. 十进制:90
  12. 十六进制:5A
  13. 二进制:1011010

  14. 第4个数
  15. 十进制:30
  16. 十六进制:1E
  17. 二进制:11110

  18. 第5个数
  19. 十进制:15
  20. 十六进制:F
  21. 二进制:1111

  22. 第6个数
  23. 十进制:120
  24. 十六进制:78
  25. 二进制:1111000
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-7-17 14:07:32 | 显示全部楼层
第二十七节:整除求商。

【27.1   什么叫整除?】

       最小的细分单位是“1”的除法运算就是整除,“1”不能再往下细分成小数点的除法运算就是整除。比如:
       10除以4,商等于2.5。------(带小数点,这个不是整除)
       10除以4,商等于2,余数是2。------(这才是整除)
       什么时候带小数点,什么时候是整除?取决于参与运算的变量类型。标准的C语言中,其实远远不止我前面所说的unsigned char ,unsigned int ,unsigned long这三种类型,比如还有一种叫浮点数类型的float,当参与运算的变量存在float类型时,就可能存在小数点。关于小数点的问题以后再讲,现在暂时不深入讲解,现在要知道的是,unsigned char ,unsigned int ,unsigned long这三种变量类型的除法都是属于整除运算,不带小数点的。

【27.2   整除的运算符号是什么样子的?】

       10除以4,商等于2,余数是2,这个整除的过程诞生了两个结果,一个是商,一个是余数,与此对应,整除就诞生出两个运算符号,你如果想计算结果返回商就用“整除求商”的符号“/”,你如果想计算结果返回余数就用“整除求余”的符号“%”。咋一看,整除运算中用到的两个符号“/”和“%”都不是我们日常生活中熟悉的除号“÷”,我个人猜测是因为“÷”这个符号在电脑键盘上不方便直接输入,因此C语言的语法规则选用“/”和“%”作为整除的运算符号。

【27.3   整除求商“/”。】

        整除求商的通用格式:
  1.         “保存变量”=“被除数” /  “除数1” /  “除数2”... /  “除数N”;
复制代码

        跟之前讲的加减运算一样,赋值符号“=”左边的“保存变量”必须是变量,右边的可以是变量和常量的任意组合。如果右边只有两个参与运算的数据,就是整除求商的常见格式。
        整除求商的常见格式:
  1.         “保存变量”=“被除数” /  “除数” ;
复制代码

        现在深入分析一下整除求商的运算规律。

       (1)当除数等于0时。
         我们都知道,数学运算的除数是不允许等于0的,如果在51单片机中非要让除数为0,商会出现什么结果?我测试了一下,发现有一个规律:在unsigned char的变量类型下,如果“除数”是变量的0,商等于十进制的255(十六进制是0xff)。如果“除数”是常量的0,商等于十进制的1。比如:
  1.     unsigned char a;
  2.     unsigned char b;
  3.     unsigned char y=0;
  4.     a=23/y;  //除数变量y里面是0,那么a的结果是255(十六进制的0xff)。
  5.     b=23/0;  //除数是常量0,那么b的结果是1。
复制代码

        平时做项目要尽量避免“除数是0”的情况,离它越远越好,但是既然除数不能为0,为什么我非要做“除数为0”时的实验呢?意义何在?这个实验的意义是,虽然我知道除数为0时会出错,但是我不知道这个错到底严不严重,会不会导致整个程序崩溃,当我做了这个实验后,我心中的石头才放下了,万一除数为0时,最多只是运算出错,但是不至于整个程序会崩溃,这样我心里就有了一个底,当哪天我某个程序崩溃跑飞时,我至少可以排除了“除数为0”这种情况,引导我从其它方面去找bug。

       (2)当被除数小于除数时。商等于0。比如:
  1.     unsigned char c;
  2.     c=7/10;   //c的结果是0。
复制代码


        (3)当被除数等于除数时。商等于1。比如:
  1.     unsigned char d;
  2.     d=10/10;  //d的结果是1。
复制代码


        (4)当被除数大于除数时。商大于0。比如:
  1.     unsigned char e;
  2.     unsigned char f;
  3.     e=10/4;  //e的结果是2,大于0。
  4.     f=10/3;  //f的结果是3,大于0。
复制代码


【27.4   整除求商的自除简写。】

         当被除数是“保存变量”时,存在自除运算的简写。
  1.           “保存变量”=“保存变量” /  “除数” ;
复制代码

         上述自除运算的简写如下:
  1.          “保存变量” / =“除数” ;
复制代码

          比如:
  1.     unsigned char e;
  2.     g/=5;  //相当于g=g/5;
复制代码

【27.5   整除求商有没有“自除1”的特殊写法?】

          加减法有自加1“++g”和自减1“g--”的特殊写法,但是除法不存在这种自除1的特殊写法,因为一个数除以1还是等于它本身,所以自除1没有任何意义,因此C语言语法中没有这种写法。

【27.6   整除求商的溢出。】

          除法的溢出规律跟加法的溢出规律是一样的,所以不再多举例子。在实际项目中,为了避免一不小心就溢出的问题,我建议,不管加减乘除,凡是参与运算的变量全部都应该转化成unsigned long变量,转化的方法已经在前面章节讲过,不再重复讲解这方面的内容。

【27.7   例程练习和分析。】

          现在编写一个程序来验证刚才讲到的整除求商:
          程序代码如下:

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

  2. void main() //主函数
  3. {
  4.      unsigned char a;
  5.      unsigned char b;
  6.      unsigned char c;
  7.      unsigned char d;
  8.      unsigned char e;
  9.      unsigned char f;
  10.      unsigned char g=10;  //初始化为10
  11.      unsigned char y=0;   //除数变量初始化为0。

  12.      //(1)当除数等于0时。
  13.      a=23/y;
  14.      b=23/0;  //这行代码在编译时会引起一条警告“Warning”,暂时不用管它。

  15.      //(2)当被除数小于除数时。
  16.      c=7/10;

  17.      //(3)当被除数等于除数时。
  18.      d=10/10;

  19.      //(4)当被除数大于除数时。
  20.      e=10/4;
  21.      f=10/3;

  22.      //(5)整除求商的简写。
  23.      g/=5;  //相当于g=g/5;

  24.      View(a);              //把第1个数a发送到电脑端的串口助手软件上观察。
  25.      View(b);              //把第2个数b发送到电脑端的串口助手软件上观察。
  26.      View(c);              //把第3个数c发送到电脑端的串口助手软件上观察。
  27.      View(d);              //把第4个数d发送到电脑端的串口助手软件上观察。
  28.      View(e);              //把第5个数e发送到电脑端的串口助手软件上观察。
  29.      View(f);              //把第6个数f发送到电脑端的串口助手软件上观察。
  30.      View(g);              //把第7个数g发送到电脑端的串口助手软件上观察。

  31.      while(1)  
  32.      {
  33.      }
  34. }

  35. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:255
  4. 十六进制:FF
  5. 二进制:11111111

  6. 第2个数
  7. 十进制:1
  8. 十六进制:1
  9. 二进制:1

  10. 第3个数
  11. 十进制:0
  12. 十六进制:0
  13. 二进制:0

  14. 第4个数
  15. 十进制:1
  16. 十六进制:1
  17. 二进制:1

  18. 第5个数
  19. 十进制:2
  20. 十六进制:2
  21. 二进制:10

  22. 第6个数
  23. 十进制:3
  24. 十六进制:3
  25. 二进制:11

  26. 第7个数
  27. 十进制:2
  28. 十六进制:2
  29. 二进制:10
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-7-23 20:26:17 | 显示全部楼层
写的很好··楼主很有毅力·

出0入0汤圆

 楼主| 发表于 2016-7-24 11:12:24 | 显示全部楼层
第二十八节:整除求余。

【28.1   整除求余“%”。】

       上一节讲到,求商求余都是属于整除运算,区别是:求商返回商,求余返回余,求商是“/”,求余是“%”。求余的运算符号恰好就是我们平时常用的百分号“%”,之所以选择百分号作为求余的运算符号,我猜测是因为,在小于100%的数据中,如果我们仔细回味一下百分号的分子与分母的关系,其实就隐含了一层淡淡的求余的味道。

       整除求余的通用格式:
  1.        “保存变量”=“被除数”% “除数1” % “除数2”...%  “除数N”;
复制代码

        跟之前讲的加减运算一样,赋值符号“=”左边的“保存变量”必须是变量,右边的可以是变量和常量的任意组合。如果右边只有两个参与运算的数据,就是整除求余的常见格式。
       整除求余的常见格式:
  1.        “保存变量”=“被除数” % “除数” ;      
复制代码

       现在深入分析一下整除求余的运算规律。

     (1)当除数等于0时。
       我们都知道,数**算除数是不允许等于0的,如果在单片机中非要让除数为0,余数会出现什么结果?我在keil的C51编译环境试过,发现有一个规律:如果除数是变量的0,那么余数等于被除数。如果除数是常量的0,那么余数等于1。还有一种特殊的情况是编译不通过的,这种情况是“当被除数是变量,而除数是常量的0”。比如:
  1. unsigned char a;
  2. unsigned char b;
  3. unsigned char k=10;
  4. unsigned char y=0; //除数初始化为0

  5. a=23%y;  //除数变量y里面是0,a的结果等于被除数23。
  6. b=23%0;  //除数是常量0,b的结果是1。
  7. b=k%0;   //这种特殊情况编译不通过:被除数是变量,而除数是常量的0。
复制代码

       平时做项目要尽量避免“除数是0”的情况,离它越远越好,但是既然除数不能为0,为什么我非要做“除数为0”时的实验呢?意义何在?这个实验的意义是,虽然我知道除数为0时会出错,但是我不知道这个错到底严不严重,会不会导致整个程序崩溃,当我做了这个实验后,我心中的石头才放下了,万一除数为0时,最多只是运算出错,但是不至于整个程序会崩溃,这样我心里就有了一个底,当哪天我某个程序崩溃跑飞时,我至少可以排除了“除数为0”这种情况,引导我从其它方面去找bug。

      (2)当被除数小于除数时。余数等于被除数本身。比如:
  1.     unsigned char c;
  2. c=7%10;  //c的结果是7。
复制代码


      (3)当被除数等于除数时。余数等于0。比如:
  1.     unsigned char d;
  2. d=10%10;  //d的结果是0。
复制代码


      (4)当被除数大于除数时。余数必然小于除数。比如:
  1. unsigned char e;
  2.     unsigned char f;
  3. e=10%4;  //e的结果是2。
  4. f=10%3;  //f的结果是1。
复制代码


      (5)当除数等于1时。余数必然等于0。
  1.     unsigned char g;
  2. g=7%1;  //g的结果是0。
复制代码


【28.2   整除求余的自除简写。】

        当被除数是“保存变量”时,存在自除求余的简写。
  1.         “保存变量”=“保存变量” %  “除数” ;
复制代码

        上述自除求余的简写如下:
  1.         “保存变量” % =“除数” ;
复制代码

         比如:
  1.     unsigned char h=9;
  2.     h%=5;  //相当于h=h%5; 最后余数的计算结果是4。
复制代码


【28.3   整除求余有没有“自除1”的特殊写法?】

        加减法有自加1“++g”和自减1“g--”的特殊写法,但是求余的除法不存在这种自除1的特殊写法,因为任何一个数除以1的余数必然等于0,所以求余的自除1没有任何意义,因此C语言语法中没有这种特殊写法。

【28.4   整除求余的溢出。】

        不管是求商还是求余,除法的溢出规律跟加法的溢出规律是一样的,所以不再多举例子。在实际项目中,为了避免一不小心就溢出的问题,我建议,不管加减乘除,凡是参与运算的变量全部都应该转化成unsigned long变量,转化的方法已经在前面章节讲过,不再重复讲解这方面的内容。

【28.5   例程练习和分析。】

        现在编写一个程序来验证刚才讲到的整除求余:
        程序代码如下:
  1. /*---C语言学习区域的开始。-----------------------------------------------*/

  2. void main() //主函数
  3. {
  4.      unsigned char a;
  5.      unsigned char b;
  6.      unsigned char c;
  7.      unsigned char d;
  8.      unsigned char e;
  9.      unsigned char f;
  10.      unsigned char g;  
  11.      unsigned char h=9;  //初始化为9。

  12.      unsigned char k=10;  //初始化为10。
  13.      unsigned char y=0; //除数变量初始化为0。

  14.          //(1)当除数等于0时。
  15.      a=23%y;
  16.      b=23%0;
  17.    //  b=k%0;  //这种特殊情况编译不通过:“被除数”是变量,而“除数”是常量的0。

  18.          //(2)当被除数小于除数时。
  19.      c=7%10;

  20.          //(3)当被除数等于除数时。
  21.      d=10%10;

  22.          //(4)当被除数大于除数时。
  23.      e=10%4;
  24.      f=10%3;

  25.          //(5)当除数等于1时。
  26.      g=7%1;

  27.          //(6)自除求余的简写。
  28.      h%=5;  //相当于h=h%5;

  29.      View(a);              //把第1个数a发送到电脑端的串口助手软件上观察。
  30.      View(b);              //把第2个数b发送到电脑端的串口助手软件上观察。
  31.      View(c);              //把第3个数c发送到电脑端的串口助手软件上观察。
  32.      View(d);              //把第4个数d发送到电脑端的串口助手软件上观察。
  33.      View(e);              //把第5个数e发送到电脑端的串口助手软件上观察。
  34.      View(f);              //把第6个数f发送到电脑端的串口助手软件上观察。
  35.      View(g);              //把第7个数g发送到电脑端的串口助手软件上观察。
  36.      View(h);              //把第8个数h发送到电脑端的串口助手软件上观察。

  37.      while(1)  
  38.      {
  39.      }
  40. }

  41. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:23
  4. 十六进制:17
  5. 二进制:10111

  6. 第2个数
  7. 十进制:1
  8. 十六进制:1
  9. 二进制:1

  10. 第3个数
  11. 十进制:7
  12. 十六进制:7
  13. 二进制:111

  14. 第4个数
  15. 十进制:0
  16. 十六进制:0
  17. 二进制:0

  18. 第5个数
  19. 十进制:2
  20. 十六进制:2
  21. 二进制:10

  22. 第6个数
  23. 十进制:1
  24. 十六进制:1
  25. 二进制:1

  26. 第7个数
  27. 十进制:0
  28. 十六进制:0
  29. 二进制:0

  30. 第8个数
  31. 十进制:4
  32. 十六进制:4
  33. 二进制:100
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-7-31 11:57:08 | 显示全部楼层
第二十九节:“先余后商”和“先商后余”提取数据某位,哪家强?

【29.1   先余后商。】

       求商求余除了数学运算外,在实际单片机项目中还有一个很常用的功能,就是提取某个数的个十百千位。提取这些位有什么用呢?用途可大了,几乎凡是涉及界面显示的项目都要用到,比如数码管的显示,液晶屏的显示。提取某个数的个十百千位是什么意思呢?比如8562这个数,提取处理后,就可以得到千位的8,百位的5,十位的6,个位的2。这里提到的“个,十,百,千”位只是一个虚数,具体是多少应该根据实际项目而定,也有可能是“个,十,百,千,万,十万,百万...”等位,总之,提取的思路和方法都是一致的。下面以8562这个数为例开始介绍提取的思路和方法。

       第一步:先把8562拆分成8562,562,62,2这四个数。怎么拆分呢?用求余的算法。比如:
  1. 8562等于8562%10000;
  2. 562等于8562%1000;
  3. 62等于8562%100;
  4. 2等于8562%10;
复制代码


       第二步:再从8562,562,62,2这四个数中分别提取8,5,6,2这四个数。怎么提取呢?用求商的算法。比如:
  1. 8等于8562/1000;
  2. 5等于562/100;
  3. 6等于62/10;
  4. 2等于2/1;
复制代码


       第三步:最后,把第一步和第二步的处理思路连写在一起如下:
  1. 8等于8562%10000/1000;
  2. 5等于8562%1000/100;
  3. 6等于8562%100/10;
  4. 2等于8562%10/1;
复制代码


       仔细观察,上述处理思路的规律感特别清晰,我们很容易发现其中的规律和原因,如果要提取“万,十万,百万...”的位数,也是用一样的思路。另外,多说一句,根据我的经验,有一些单片机的C编译器可能不支持long类型数据的求余求商连写在一起,那么就要分两步走“先求余,再求商”,分开来操作。比如:
  1.        unsigned char a;
  2.        a=8562%10000/1000;  //提取千位。
复制代码

       分成两步走之后如下:
  1.        unsigned char a;
  2.        a=8562%10000;     
  3.        a=a/1000;          //提取千位。
复制代码


      提取其它位分两步走的思路也是一样,不多说。


【29.2   先商后余。】

        刚才讲到了“先余后商”的提取思路,其实也可以倒过来“先商后余”,也就是先求商再求余数。下面还是以8562这个数为例。

        第一步:先把8562拆分成8,85,856,8562这四个数。怎么拆分呢?用求商的算法。比如:
  1. 8等于8562/1000;
  2. 85等于8562/100;
  3. 856等于8562/10;
  4. 8562等于8562/1;
复制代码


        第二步:再从8,85,856,8562这四个数中分别提取8,5,6,2这四个数。怎么提取呢?用求余的算法。比如:
  1. 8等于8%10;
  2. 5等于85%10;
  3. 6等于856%10;
  4. 2等于8562%10;
复制代码


         第三步:最后,把第一步和第二步的处理思路连写在一起如下:
  1. 8等于8562/1000%10;
  2. 5等于8562/100%10;
  3. 6等于8562/10%10;
  4. 2等于8562/1%10;
复制代码


       上述的规律感也是特别清晰的。

【29.3   “先余后商”和“先商后余”哪家强?】

       上面讲了“先余后商”和“先商后余”这两种思路,到底哪种思路在实际项目中更好呢?其实我个人倾向于后者的“先商后余”,为什么呢?请看这个例子,以3100000000这个数为例,要提取该数的“十亿”位3。

       第一种:用“先余后商”的套路如下:
  1. 3等于3100000000%10000000000/1000000000;
复制代码

       这里出现了一个问题,我们知道,unsigned long类型最大的数据是0xffffffff,转换成十进制后最大的数是4294967295,但是上面出现的10000000000这个数比unsigned long类型最大的数据4294967295还要大,这个就会引来我个人的担忧,C编译器到底会怎么处理,很有可能会出现意想不到的错误,至少会让我感到心里不踏实。当然,也许会有一些朋友说,这个是多虑的,最高位完全可以把求余这一步省略,这个说法也对,但是作为一种“套路”,我还是喜欢“套路”的对称感,“套路”之所以成为“套路”,是因为有一种对称感。下面再看看如果用“先商后余”的思路来处理,会不会出现这个担忧。

       第二种:用“先商后余”的套路如下:
  1. 3等于3100000000/1000000000%10;
复制代码

       这一次,上面出现的1000000000这个数比unsigned long类型最大的数据4294967295小,所以没有刚才那种担忧,也维护了“套路”的对称感。所以我在实际项目中喜欢用这种方法。

【29.4   例程练习和分析。】

       现在编写一个程序来验证刚才讲到的两种思路:
       程序代码如下:
  1. /*---C语言学习区域的开始。-----------------------------------------------*/

  2. void main() //主函数
  3. {
  4.      unsigned char a; //千位
  5.      unsigned char b; //百位
  6.      unsigned char c; //十位
  7.      unsigned char d; //个位

  8.      unsigned char e; //千位
  9.      unsigned char f; //百位
  10.      unsigned char g; //十位  
  11.      unsigned char h; //个位  

  12.      //x初始化为8562,必须是unsignd int类型以上,不能是char类型,char最大范围是255。
  13.      unsigned int  x=8562;  //被提取的数

  14.          //第一种:先余后商。
  15.          a=x%10000/1000;  //提取千位
  16.          b=x%1000/100;    //提取百位
  17.          c=x%100/10;      //提取十位
  18.          d=x%10/1;        //提取个位

  19.          //第二种:先商后余。
  20.          e=x/1000%10;     //提取千位
  21.          f=x/100%10;      //提取百位
  22.          g=x/10%10;       //提取十位
  23.          h=x/1%10;        //提取个位

  24.      View(a);              //把第1个数a发送到电脑端的串口助手软件上观察。
  25.      View(b);              //把第2个数b发送到电脑端的串口助手软件上观察。
  26.      View(c);              //把第3个数c发送到电脑端的串口助手软件上观察。
  27.      View(d);              //把第4个数d发送到电脑端的串口助手软件上观察。
  28.      View(e);              //把第5个数e发送到电脑端的串口助手软件上观察。
  29.      View(f);              //把第6个数f发送到电脑端的串口助手软件上观察。
  30.      View(g);              //把第7个数g发送到电脑端的串口助手软件上观察。
  31.      View(h);              //把第8个数h发送到电脑端的串口助手软件上观察。

  32.      while(1)  
  33.      {
  34.      }
  35. }

  36. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:8
  4. 十六进制:8
  5. 二进制:1000

  6. 第2个数
  7. 十进制:5
  8. 十六进制:5
  9. 二进制:101

  10. 第3个数
  11. 十进制:6
  12. 十六进制:6
  13. 二进制:110

  14. 第4个数
  15. 十进制:2
  16. 十六进制:2
  17. 二进制:10

  18. 第5个数
  19. 十进制:8
  20. 十六进制:8
  21. 二进制:1000

  22. 第6个数
  23. 十进制:5
  24. 十六进制:5
  25. 二进制:101

  26. 第7个数
  27. 十进制:6
  28. 十六进制:6
  29. 二进制:110

  30. 第8个数
  31. 十进制:2
  32. 十六进制:2
  33. 二进制:10
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-8-1 22:47:25 | 显示全部楼层
传播正能量,赞一个

出0入0汤圆

发表于 2016-8-2 08:40:59 | 显示全部楼层
强烈关注!!!!

出0入0汤圆

发表于 2016-8-2 09:45:25 | 显示全部楼层
支持,佩服!

出0入46汤圆

发表于 2016-8-2 09:49:59 | 显示全部楼层
吴坚鸿 发表于 2016-1-18 11:49
第一节:我是中国最幸福的单片机工程师。

非常赞叹,楼主这辈子成就会很大

出0入0汤圆

发表于 2016-8-2 11:31:59 | 显示全部楼层
为LZ奉献精神点赞,支持。

出0入0汤圆

发表于 2016-8-2 13:44:38 来自手机 | 显示全部楼层
楼主写的都是基础,感觉写一篇单片机应用过程中的技巧更有吸引力

出0入0汤圆

发表于 2016-8-2 14:10:10 | 显示全部楼层
还是很佩服能够分享实际经验的人,

出0入0汤圆

发表于 2016-8-2 15:05:50 | 显示全部楼层
是的,都是基础, 基础的你都懒得去弄懂; 或沉不下心来去弄懂! 或似懂非懂!

支持楼主,这么有韧劲! 期待后续篇章!

出0入0汤圆

发表于 2016-8-2 15:18:54 | 显示全部楼层
给大家正能量更多的应该是LZ的韧劲

出0入0汤圆

发表于 2016-8-2 15:49:50 | 显示全部楼层
谢谢楼主这种无私分享精神

出0入0汤圆

发表于 2016-8-2 17:03:32 | 显示全部楼层
留个印记 。。。

出0入0汤圆

发表于 2016-8-2 22:24:06 | 显示全部楼层
好帖,学习。

出0入0汤圆

发表于 2016-8-3 06:11:32 来自手机 | 显示全部楼层
等您出书了,一定买一本,受益匪浅。不论是技术还是人生。

出0入0汤圆

发表于 2016-8-3 06:40:16 | 显示全部楼层
吴坚鸿 发表于 2016-1-18 11:49
第一节:我是中国最幸福的单片机工程师。

关于液晶万能测试仪能多说一点吗?

出0入0汤圆

发表于 2016-8-3 16:34:48 | 显示全部楼层
Mark 单片机学习

出50入0汤圆

发表于 2016-8-5 07:52:48 来自手机 | 显示全部楼层
不浮躁,很纯粹。支持了

出0入0汤圆

发表于 2016-8-5 09:09:17 | 显示全部楼层
非常好的学习资料!

出0入0汤圆

发表于 2016-8-6 09:17:58 | 显示全部楼层
厉害,顶//

出0入0汤圆

 楼主| 发表于 2016-8-8 16:50:21 | 显示全部楼层
第三十节:逻辑运算符的“与”运算。

【30.1   “与”运算。】

      不管是十进制还是十六进制,单片机底层的运算都是以二进制的形式进行的,包括前面章节的加减乘除运算,在单片机的底层处理也是以二进制形式进行。只不过加减乘除我们平时太熟悉了,以十进制的形式口算或者笔算也能得到正确的结果,所以不需要刻意把十进制的数据先转换成二进制,然后再模拟单片机底层的二进制运算。但是本节的逻辑“与”运算,在分析它的运算过程和规律的时候,必须把所有的数据都转化成二进制才能进行分析,因为它强调的是二进制的位与位之间的逻辑运算。我们知道,二进制中的每一位只能是0或者1,两个数的“与”运算就是两个数被展开成二进制后的位与位之间的逻辑“与”运算。
       “与”运算的运算符号是“&”。运算规律是:两个位进行“与”运算,只有两个位都同时是1运算结果才能等于1,,否则,只要其中有一位是0,运算结果必是0.比如:
  1.        0&0等于0。
  2.        0&1等于0。
  3.        1&0等于0。
  4.        1&1等于1。
复制代码

       注意,上述的0和1都是指二进制的0和1。


    现在举一个完整的例子来分析“与”运算的规律。有两个unsigned char类型的十进制数分别是12和9,求12&9的结果是多少?分析步骤如下:

      第一步:先把参与运算的两个数以二进制的格式展开。十进制转二进制的方法请参考前面第14,15,16节的内容。
  1.        十进制12的二进制格式是:00001100。
  2.        十进制9的二进制格式是: 00001001。
复制代码


      第二步:二进制数右对齐,按上下每一位进行“与”运算。
  1.        十进制的12       ->     00001100   
  2.       十进制的9        ->    &00001001
  3.        “与”运算结果是  ->    00001000
复制代码


       第三步:把二进制的00001000转换成十六进制是:0x08。转换成十进制是8。所以12&9的结果是8。

       上述举的例子只能分析“与”运算的规律,并没有看出“与”运算的意义所在。“与”运算有啥用途呢?其实用途很多,最常见的用途是可以指定一个变量二进制格式的某位清零,其它位保持不变。比如一个unsigned char类型的变量b,数据长度一共是8位,从右往左:
        想让第0位清零,其它位保持不变,只需跟十六进制的0xfe相“与”:b=b&0xfe。
        想让第1位清零,其它位保持不变,只需跟十六进制的0xfd相“与”:b=b&0xfd。
        想让第2位清零,其它位保持不变,只需跟十六进制的0xfb相“与”:b=b&0xfb。
        想让第3位清零,其它位保持不变,只需跟十六进制的0xf7相“与”:b=b&0xf7。
        想让第4位清零,其它位保持不变,只需跟十六进制的0xef相“与”:b=b&0xef。
        想让第5位清零,其它位保持不变,只需跟十六进制的0xdf相“与”:b=b&0xdf。
        想让第6位清零,其它位保持不变,只需跟十六进制的0xbf相“与”:b=b&0xbf。
        想让第7位清零,其它位保持不变,只需跟十六进制的0x7f相“与”:b=b&0x7f。
        根据上述规律,假设b原来等于十进制的85(十六进制是0x55,二进制是01010101),要想把此数据的第0位清零,只需b=b&0xfe。最终b的运算结果是十进制是84(十六进制是0x54,二进制是01010100)。把它们展开成二进制格式的运算过程如下:
  1.        十进制的85       ->     01010101   
  2.       十六进制的0xfe   ->    &11111110
  3.         “与”运算结果是  ->     01010100
复制代码


【30.2   例程练习和分析。】

    现在编写一个程序来验证刚才讲到的“与”运算:
程序代码如下:

  1. /*---C语言学习区域的开始。-----------------------------------------------*/
  2. void main() //主函数
  3. {
  4.       unsigned char a;
  5.       unsigned char b=85;  //十六进制是0x55,二进制是01010101。
  6.       a=12&9;
  7.       b=b&0xfe;   
  8.       View(a);              //把第1个数a发送到电脑端的串口助手软件上观察。
  9.       View(b);              //把第2个数b发送到电脑端的串口助手软件上观察。
  10.       while(1)  
  11.       {
  12.      }
  13. }
  14. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...
  2. 第1个数
  3. 十进制:8
  4. 十六进制:8
  5. 二进制:1000
  6. 第2个数
  7. 十进制:84
  8. 十六进制:54
  9. 二进制:1010100
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-8-11 14:21:07 | 显示全部楼层
mark一下!

出0入0汤圆

 楼主| 发表于 2016-8-14 13:52:15 | 显示全部楼层

第三十一节:逻辑运算符的“或”运算。

【31.1   “或”运算。】

      “或”运算也是以位为单位进行运算的。位是指二进制中的某一位,位只能是0或者1。两个数的“或”运算就是转换成二进制后每一位的“或”运算。
      “或”运算的符号是“|”。运算规律是:两个位的“或”运算,如果两个位都是0,那么运算结果才是0,否则只要其中有一位是1,那么运算结果必定是1。比如:
  1.       0|0等于0。
  2.       0|1等于1。
  3.       1|0等于1。
  4.       1|1等于1。
复制代码

      现在举一个完整的例子来分析“|”运算的规律。有两个unsigned char类型的十进制数分别是12和9,求12|9的结果是多少?分析步骤如下:

      第一步:先把参与运算的两个数以二进制的格式展开。十进制转二进制的方法请参考前面第13,14,15节的内容。
  1.       十进制12的二进制格式是:00001100。
  2.       十进制9的二进制格式是: 00001001。
复制代码


      第二步:二进制数右对齐,按上下每一位进行“或”运算。
  1.       十进制的12       ->     00001100   
  2.       十进制的9        ->    |00001001
  3.       “或”运算结果是  ->    00001101
复制代码


      第三步:把二进制的00001101转换成十六进制是:0x0D。转换成十进制是13。所以12|9的结果是13。

      上一节讲的“与”运算最常见的用途是可以指定一个变量的某位清0,而本节的“或”运算刚好相反,“或”运算最常见的用途是可以指定一个变量的某位置1,其它位保持不变。比如一个unsigned char类型的变量b,数据长度一共是8位,从右往左:
       想让第0位置1,其它位保持不变,只需跟十六进制的0x01相“或”:b=b|0x01。
       想让第1位置1,其它位保持不变,只需跟十六进制的0x02相“或”:b=b|0x02。
       想让第2位置1,其它位保持不变,只需跟十六进制的0x04相“或”:b=b|0x04。
       想让第3位置1,其它位保持不变,只需跟十六进制的0x08相“或”:b=b|0x08。
       想让第4位置1,其它位保持不变,只需跟十六进制的0x10相“或”:b=b|0x10。
       想让第5位置1,其它位保持不变,只需跟十六进制的0x20相“或”:b=b|0x20。
       想让第6位置1,其它位保持不变,只需跟十六进制的0x40相“或”:b=b|0x40。
       想让第7位置1,其它位保持不变,只需跟十六进制的0x80相“或”:b=b|0x80。
       根据上述规律,假设b原来等于十进制的84(十六进制是0x54,二进制是01010100),要想把此数据的第0位置1,只需b=b|0x01。最终b的运算结果是十进制是85(十六进制是0x55,二进制是01010101)。把它们展开成二进制格式的运算过程如下:
  1.        十进制的84       ->     01010100   
  2.        十六进制的0x01   ->    |00000001
  3.        “或”运算结果是  ->     01010101
复制代码


【31.2   例程练习和分析。】

       现在编写一个程序来验证刚才讲到的“或”运算:
程序代码如下:
  1. /*---C语言学习区域的开始。-----------------------------------------------*/

  2. void main() //主函数
  3. {
  4.      unsigned char a;
  5.      unsigned char b=84;  //十六进制是0x54,二进制是01010100。

  6.      a=12|9;
  7.      b=b|0x01;   

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

  10.      while(1)  
  11.      {
  12.      }
  13. }

  14. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:13
  4. 十六进制: D
  5. 二进制:1101

  6. 第2个数
  7. 十进制:85
  8. 十六进制:55
  9. 二进制:1010101
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-8-15 12:42:38 | 显示全部楼层
看看写的如何!

出0入0汤圆

发表于 2016-8-18 08:38:02 来自手机 | 显示全部楼层
佩服楼主,讲的很细,新手容易碰到的问题都将清楚了。加油。

出0入0汤圆

发表于 2016-8-18 08:49:22 | 显示全部楼层
天下武功为快不破。

出0入0汤圆

发表于 2016-8-19 12:04:32 | 显示全部楼层
吴坚鸿 发表于 2016-1-18 11:49
第一节:我是中国最幸福的单片机工程师。

支持楼主、、、

出0入0汤圆

 楼主| 发表于 2016-8-21 10:29:18 | 显示全部楼层
第三十二节:逻辑运算符的“异或”运算。

【32.1   “异或”运算。】

      “异或”运算也是以位为单位进行运算的。位是指二进制中的某一位,位只能是0或者1。两个数的“异或”运算就是转换成二进制后每一位的“异或”运算。
      “异或”运算的符号是“^”。运算规律是:两个位的“异或”运算,如果两个位都相同,那么运算结果就是0;如果两个位不同(相异),则运算结果是1。比如:
  1. 0^0等于0。(两个位相同)
  2. 0^1等于1。(两个位相异)
  3. 1^0等于1。(两个位相异)
  4. 1^1等于0。(两个位相同)
复制代码


       现在举一个完整的例子来分析“^”运算的规律。有两个unsigned char类型的十进制数分别是12和9,求12^9的结果是多少?分析步骤如下:

       第一步:先把参与运算的两个数以二进制的格式展开。十进制转二进制的方法请参考前面第14,15,16节的内容。
  1.     十进制12的二进制格式是:00001100。
  2.     十进制9的二进制格式是: 00001001。
复制代码



       第二步:二进制数右对齐,按上下每一位进行“异或”运算。
  1.     十进制的12         ->     00001100   
  2.     十进制的9          ->    ^00001001
  3.     “异或”运算结果是  ->    00000101
复制代码


       第三步:把二进制的 00000101转换成十六进制是:0x05。转换成十进制是5。所以12^9的结果是5。

【32.2   “异或”在项目中的应用。】

       “异或”在哪些项目上经常应用?以我个人的项目经验,平时很少用“异或”,我本人在项目中用过两次“异或”,第一次是在某项目做串口通讯协议时,通过“异或”算法,增加一个校验字节,此校验字节是一串数据依次相“异或”的总结果,目的是为了增加数据传送时的抗干扰能力。第二次是把它用来对某变量的某个位进行取反运算,如何用“异或”来实现对某位进行取反的功能?要实现这个功能,首先要清楚“异或”运算有一个潜在的规律:任何一个位,凡是与0进行“异或”运算都保持不变,凡是与1进行“异或”运算都会达到取反的运算效果。因此,如果想某位实现取反的功能,只要把相关的位与“1”进行“异或”运算就可以实现取反的功能。二进制中的一个位要么是0,要么是1,不管是0还是1,只要与1进行“异或”运算,是会达到取反的运算目的,0的会变成1,1的会变成0。请看以下这个例子:
  1.     0^1等于1。(两个位相异)
  2.     1^1等于0。(两个位相同)
复制代码


      以上的例子只是列举了一个位,如果把一个字节的8位展开来,只要某位与“1”进行“异或”运算,都可以实现某位取反的功能。比如,一个十六进制的0x55,如果要这个字节的低4位都取反,高4位不变,只需要把该数据与十六进制的0x0F进行“异或”运算就可以达到目的。请看以下这个例子:
  1.     十六进制的0x55         ->      01010101   
  2.     十六进制的0x0F          ->    ^00001111
  3.     “异或”运算结果是     ->      01011010
复制代码

      上述运算结果二进制的01011010转换成十六进制是0x5A,转换成十进制是90。

【32.3   例程练习和分析。】

      现在编写一个程序来验证刚才讲到的“异或”运算:
      程序代码如下:
  1. /*---C语言学习区域的开始。-----------------------------------------------*/

  2. void main() //主函数
  3. {
  4.      unsigned char a;
  5.      unsigned char b;  

  6.          a=12^9;
  7.          b=0x55^0x0F;

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

  10.      while(1)  
  11.      {
  12.      }
  13. }

  14. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:5
  4. 十六进制:5
  5. 二进制:101

  6. 第2个数
  7. 十进制:90
  8. 十六进制:5A
  9. 二进制:1011010
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-8-24 00:25:40 | 显示全部楼层
谢谢分享您的资料,学习中。

出0入0汤圆

发表于 2016-8-24 11:50:57 | 显示全部楼层
先做个记号。。   

出40入70汤圆

发表于 2016-8-25 01:03:30 | 显示全部楼层
谢谢分享您自己的经验心得,受教了,感谢!!!!

出0入0汤圆

发表于 2016-8-26 09:02:18 | 显示全部楼层
坐等,支持,谢谢分享学习

出0入0汤圆

 楼主| 发表于 2016-8-28 20:53:45 | 显示全部楼层
本帖最后由 吴坚鸿 于 2016-8-28 21:08 编辑

第三十三节:逻辑运算符的“按位取反”和“非”运算。

【前面章节漏讲的补充。】

        前面的章节中,漏讲了“与,或,异或”的简写格式,在这类运算中,当赋值语句左边的“保存变量”也是参与运算的变量本身时,存在简写的语法格式,比如:
  1.         a&=0x01;  //相当于a=a&0x01;
  2.         a|=0x01;  //相当于a=a|0x01;
  3.         a^=0x01;  //相当于a=a^0x01;
复制代码


【33.1   “按位取反”运算。】

        “按位取反”运算也是以位为单位进行运算的。位是指二进制中的某一位,位只能是0或者1。跟前面“加、减、乘、除、与、或、异或”有点不一样的地方是,“按位取反”的运算只有1个对象,它不像加法运算那样可以与其它第2个对象产生关系,比如“a加b”这里有2个对象a和b,而“a按位取反”只有1个对象a。一个数的“按位取反”运算就是把该数转换成二进制后对每一位的“取反”运算。
        “按位取反”运算的符号是波浪符号“~”。运算规律是:针对一个数的“按位取反”,先将其展开成二进制的格式,然后每个位取反,所谓取反就是1的变成0,0的变成1。现在举一个完整的例子来分析“~”运算的规律。有两个unsigned char类型的十进制数分别是5和0,求~5和~0的结果分别是多少?分析步骤如下:

        第一步:先把参与运算的两个数以二进制的格式展开。十进制转二进制的方法请参考前面第14,15,16节的内容。
  1.         十进制5的二进制格式是: 00000101。
  2.         十进制0的二进制格式是: 00000000。
复制代码


        第二步:将它们二进制格式的每一位取反,1的变成0,0的变成1。
        (a)对5的按位取反。
  1.          十进制的5                     ->    ~00000101   
  2.          “按位取反”运算结果是  ->     11111010
复制代码


        (b)对0的按位取反。
  1.          十进制的0                   ->    ~00000000   
  2.         “按位取反”运算结果是  ->     11111111
复制代码


        第三步:
        (a)把二进制的11111010转换成十六进制是:0xFA。转换成十进制是250。所以~5的结果是250。
        (b)把二进制的11111111转换成十六进制是:0xFF。转换成十进制是255。所以~0的结果是255。

【33.2   “非”运算。】

        注意,“非”运算不是以位为单位进行运算的。“非”跟“按位取反”有点相似,但是区别也明显。“按位取反”是以位为单位进行运算的,侧重在局部。而“非”是针对一个数的整体,侧重在全局。“非”只有两种状态“假”和“真”。0代表假,大于0的数值代表真,也可以说“非”假即真,“非”真即假。不是假的就是真的,不是真的就是假的。强调的是两种状态的切换。在数值表示上,用0代表假的状态,用1代表真的状态。“非”的对象也只有1个,它不像加法运算那样可以与其它第2个对象产生关系,比如“a加b”这里有2个对象a和b,而“a的非”只有1个对象a。        “非”运算的符号是感叹号“!”,注意输入这类运算符号的时候不能用汉字输入法,而是要切换到英文字符的输入法下再输入,否则编译不通过(其它运算符也一样,都要求在字符输入法下输入)。“非”运算的规律是:针对某个数的“非”,不管此数有多大,只要它大于0,那么被“非”后就一定是0。也不管此数是什么变量类型,只要它数值等于0,那么被“非”后就一定是1,而不是0xff或者0xffff之类。
        现在举一个完整的例子来分析“!”运算的规律。有两个unsigned char类型的十进制数分别是5和0,求!5和!0的结果分别是多少?分析思路如下:

       (a)针对5的“非”运算。
  1.         5大于0,是一个整体,被“非”后为0.
复制代码


       (b)针对0的“非”运算。
  1.         0就是0,是一个整体,被“非”后为1.
复制代码


【33.3   例程练习和分析。】

        现在编写一个程序来验证刚才讲到的“按位取反”和“非”运算:
        程序代码如下:

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

  2. void main() //主函数
  3. {
  4.          unsigned char a=5;
  5.          unsigned char b=5;
  6.          unsigned char c=0;
  7.          unsigned char d=0;

  8.          a=~a;
  9.          b=!b;

  10.          c=~c;
  11.          d=!d;

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

  16.      while(1)  
  17.      {
  18.      }
  19. }

  20. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:250
  4. 十六进制:FA
  5. 二进制:11111010

  6. 第2个数
  7. 十进制:0
  8. 十六进制:0
  9. 二进制:0

  10. 第3个数
  11. 十进制:255
  12. 十六进制:FF
  13. 二进制:11111111

  14. 第4个数
  15. 十进制:1
  16. 十六进制:1
  17. 二进制:1
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出75入8汤圆

发表于 2016-8-29 07:44:08 | 显示全部楼层
写有不错啊,支持!

出0入0汤圆

 楼主| 发表于 2016-9-4 12:50:31 | 显示全部楼层
本帖最后由 吴坚鸿 于 2016-9-4 13:51 编辑

第三十四节:移位运算的左移。

【34.1   “左移”运算。】

       “左移”运算也是以位为单位进行运算的。位是指二进制中的某一位,位只能是0或者1。欲理解某个数“左移”运算的内部规律,必先把该数展开成二进制的格式,然后才好分析。“左移”运算的符号是“<<”,它的通用格式如下:
  1.       “保存变量”=“被移数”<<n;
复制代码

       运算规律是:“被移数”先被复制一份放到某个隐蔽的临时变量(也称作寄存器),然后对此临时变量展开成二进制的格式,左边是高位,右边是低位,此二进制格式的临时变量被整体由右往左移动了n位,原来左边的高n位数据被直接覆盖,而右边由于数据位移动而新空出的低n位数据被直接填入0,最后再把移位运算的结果存入“保存变量”。多问一句,这行代码执行完毕后,“保存变量”和“被移数”到底哪个变量发生了变化,哪个变量维持不变?大家记住,只有赋值语句“=”左边的“保存变量”发生数值变化,而右边的“被移数”没有发生变化,因为“被移数”被操作的不是它自己本身,而是它的复制品替身(某个隐蔽的临时变量,也称寄存器)。这条规律对“加、减、乘、除、与、或、异或、非、取反”等运算都是适用的,重要的事情再重复一次,这条规律就是:只有赋值语句“=”左边的“保存变量”发生数值变化,而赋值语句“=”右边的“运算变量”本身不会发生变化,因为“运算变量”被操作的不是它自己本身,而是它的复制品替身(某个隐蔽的临时变量,也称寄存器)。
       上述通用格式中的n代表被一次左移的位数,可以取0,当n等于0的时候,代表左移0位,其实就是数值维持原来的样子没有发生变化。
       现在举一个完整的例子来分析“<<”运算的规律。有两个unsigned char类型的变量a和b,它们的数值都是十进制的5,求a=a<<1和b=b<<2的结果分别是多少?分析步骤如下:

       第一步:先把a和b变量原来的数值以二进制的格式展开。十进制转二进制的方法请参考前面第14,15,16节的内容。
  1.        a变量是十进制5,它的二进制格式是: 00000101。
  2.        b变量是十进制5,它的二进制格式是: 00000101。
复制代码


      第二步:将a左移1位,将b左移2位。
            (1)a=a<<1,就是将a左移1位。
  1.              a左移前是      ->    00000101   
  2.              a左移1位后是  ->    00001010  
复制代码

             结果分析:把二进制的00001010转换成十六进制是:0x0A。转换成十进制是10。所以a初始值是5,左移1位后的结果是10。

            (2)b=b<<2,就是将b左移2位。
  1.              b左移前是      ->    00000101   
  2.              b左移2位后是  ->    00010100  
复制代码

             结果分析:把二进制的00010100转换成十六进制是:0x14。转换成十进制是20。所以b初始值是5,左移2位后的结果是20。

【34.2   “左移”与乘法的关系。】

       上面的例子,仔细观察,发现一个规律:5左移1位就变成了10(相当于5乘以2),5左移2位就变成了20(相当于5乘以2再乘以2)。这个现象背后的规律是:在左移运算中,只要最高位不发生溢出的现象,那么每左移1位就相当于乘以2,左移2位相当于乘以2再乘以2,左移3位相当于乘以2再乘以2再乘以2......以此类推。这个规律反过来从乘法的角度看,也是成立的:某个数乘以2,就相当于左移1位,某个数乘以2再乘以2相当于左移2位,某个数乘以2再乘以2再乘以2相当于左移3位......以此类推。那么问题来了,同样是达到乘以2的运算结果,从运算速度的角度对比,“左移”和“乘法”哪家强?答案是:一条左移语句的运算速度比一条乘法语句的运算速度要快很多倍。

【34.3   “左移”的常见应用之一:不同数据类型之间的合并。】

        比如有两个unsigned char单字节的类型数据H和L,H的初始值是十六进制的0x12,L的初始值是十六进制的0x34,要将两个单字节的H和L合并成一个unsigned int双字节的数据c,其中H是高8位字节,L是低八位字节,合并成c后,c的值应该是十六进制的0x1234,此程序如何写?就需要用到左移。程序分析如下:
  1. unsigned char H=0x12;  //单字节
  2. unsigned char L=0x34;  //单字节
  3. unsigned int c;        //双字节
  4. c=H;                   //c的低8位被H覆盖,也就是c的低8位得到了H的值。
  5. c=c<<8;                //及时把c的低8位移动到高8位,同时c原来的低8位被填入0
  6. c=c+L;                 //此时c再加L,c的低8位就L的值。
复制代码

        程序运行结果:c就等于十六进制的0x1234,十进制是4660。

【34.4   “左移”的常见应用之二:聚焦在某个变量的某个位。】

        前面第31节讲到“或”运算,其中讲到可以对某个变量的某个位置1,当时是这样讲的,片段如下:
  1. “或”运算最常见的用途是可以指定一个变量的某位置1,其它位保持不变。比如一个unsigned char类型的变量b,数据长度一共是8位,从右往左:
  2. 想让第0位置1,其它位保持不变,只需跟十六进制的0x01相“或”:b=b|0x01。
  3. 想让第1位置1,其它位保持不变,只需跟十六进制的0x02相“或”:b=b|0x02。
  4. 想让第2位置1,其它位保持不变,只需跟十六进制的0x04相“或”:b=b|0x04。
  5. 想让第3位置1,其它位保持不变,只需跟十六进制的0x08相“或”:b=b|0x08。
  6. 想让第4位置1,其它位保持不变,只需跟十六进制的0x10相“或”:b=b|0x10。
  7. 想让第5位置1,其它位保持不变,只需跟十六进制的0x20相“或”:b=b|0x20。
  8. 想让第6位置1,其它位保持不变,只需跟十六进制的0x40相“或”:b=b|0x40。
  9. 想让第7位置1,其它位保持不变,只需跟十六进制的0x80相“或”:b=b|0x80。
复制代码

       但是这样写很多程序员会嫌它不直观,哪里不直观?就是0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80这些数不直观,这些数只是代表了聚焦某个变量不同的位。如果把这些十六进制的数值换成左移的写法,在阅读上就非常清晰直观了。比如:0x01可以用1<<0替代,0x02可以用1<<1替代,0x04可以用1<<2替代......0x80可以用1<<7替代。左移的n位,n就恰好代表了某个变量的某个位。于是,我们把上面的片段更改成左移的写法后,如下:
  1. “或”运算最常见的用途是可以指定一个变量的某位置1,其它位保持不变。比如一个unsigned char类型的变量b,数据长度一共是8位,从右往左:
  2. 想让第0位置1,其它位保持不变,只需:b=b|(1<<0)。
  3. 想让第1位置1,其它位保持不变,只需:b=b|(1<<1)。
  4. 想让第2位置1,其它位保持不变,只需:b=b|(1<<2)。
  5. 想让第3位置1,其它位保持不变,只需:b=b|(1<<3)。
  6. 想让第4位置1,其它位保持不变,只需:b=b|(1<<4)。
  7. 想让第5位置1,其它位保持不变,只需:b=b|(1<<5)。
  8. 想让第6位置1,其它位保持不变,只需:b=b|(1<<6)。
  9. 想让第7位置1,其它位保持不变,只需:b=b|(1<<7)。
复制代码

       分析:这样改进后,阅读就很清晰直观了,只是在程序代码的效率速度方面,因为多增加了一条左移指令,意味着要多消耗一条指令的时间,那么到底该选择哪种?其实各有利弊,应该根据个人的编程喜好和实际项目来取舍。很多32位的单片机在初始化寄存器的库函数里大量应用这种左移的方法来操作,目的就是为了增加代码可读性。
       根据上述规律,假设d原来等于十进制的84(十六进制是0x54,二进制是01010100),要想把此数据的第0位置1,只需d=d|(1<<0)。最终d的运算结果是十进制是85(十六进制是0x55,二进制是01010101)。


        刚才上面讲到第31节的“或”运算,其实在第30节的“与”运算中也是可以用这种左移的方法来聚焦,只是要多配合一条“取反”的指令才可以。“与”运算跟“或”运算刚刚相反,它是对某个变量的某个位清零,当时是这样讲的,片段如下:
  1.     “与”运算最常见的用途是可以指定一个变量二进制格式的某位清零,其它位保持不变。比如一个unsigned char类型的变量b,数据长度一共是8位,从右往左:
  2.     想让第0位清零,其它位保持不变,只需跟十六进制的0xfe相“与”:b=b&0xfe。
  3.     想让第1位清零,其它位保持不变,只需跟十六进制的0xfd相“与”:b=b&0xfd。
  4.     想让第2位清零,其它位保持不变,只需跟十六进制的0xfb相“与”:b=b&0xfb。
  5.     想让第3位清零,其它位保持不变,只需跟十六进制的0xf7相“与”:b=b&0xf7。
  6.     想让第4位清零,其它位保持不变,只需跟十六进制的0xef相“与”:b=b&0xef。
  7.     想让第5位清零,其它位保持不变,只需跟十六进制的0xdf相“与”:b=b&0xdf。
  8.     想让第6位清零,其它位保持不变,只需跟十六进制的0xbf相“与”:b=b&0xbf。
  9.     想让第7位清零,其它位保持不变,只需跟十六进制的0x7f相“与”:b=b&0x7f。
复制代码

       但是这样写很多程序员会嫌它不直观,哪里不直观?就是0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f这些数不直观,这些数只是代表了聚焦某个变量不同的位。如果把这些十六进制的数值换成左移的写法,在阅读上就非常清晰直观了,但是注意,这里左移之后还要配一条“取反”语句。比如:0xfe可以用~(1<<0)替代,0xfd可以用~(1<<1)替代,0xfb可以用~(1<<2)替代......0x7f可以用~(1<<7)替代。左移的n位后再取反,n就恰好代表了某个变量的某个位。于是,我们把上面的片段更改成左移的写法后,如下:
  1.     “与”运算最常见的用途是可以指定一个变量二进制格式的某位清零,其它位保持不变。比如一个unsigned char类型的变量b,数据长度一共是8位,从右往左:
  2.     想让第0位清零,其它位保持不变,只需:b=b&(~(1<<0))。
  3. 想让第1位清零,其它位保持不变,只需:b=b&(~(1<<1))。
  4. 想让第2位清零,其它位保持不变,只需:b=b&(~(1<<2))。
  5. 想让第3位清零,其它位保持不变,只需:b=b&(~(1<<3))。
  6. 想让第4位清零,其它位保持不变,只需:b=b&(~(1<<4))。
  7. 想让第5位清零,其它位保持不变,只需:b=b&(~(1<<5))。
  8. 想让第6位清零,其它位保持不变,只需:b=b&(~(1<<6))。
  9. 想让第7位清零,其它位保持不变,只需:b=b&(~(1<<7))。
复制代码

        分析:这样改进后,阅读就很清晰直观了,只是在程序代码的效率速度方面,因为多增加了一条左移指令和一条取反指令,意味着要多消耗两条指令的时间,那么到底该选择哪种?其实各有利弊,应该根据个人的编程喜好和实际项目来取舍。很多32位的单片机在初始化寄存器的库函数里大量应用这种左移的方法来操作,目的就是为了增加代码可读性。
        根据上述规律,假设e原来等于十进制的85(十六进制是0x55,二进制是01010101),要想把此数据的第0位清零,只需e=e&(~(1<<0))。最终e的运算结果是十进制是84(十六进制是0x54,二进制是01010100)。

【34.5  左移运算的“左移简写”。】

       当被移数是“保存变量”时,存在“左移简写”。
  1.        “保存变量”=“保存变量”<<n;
复制代码

       上述左移简写如下:
  1. “保存变量”<<=n;
复制代码

        比如:
  1. unsigned char f=1;
  2. unsigned char g=1;

  3.     f<<=1; //就相当于f=f<<1;
  4.     g<<=2; //就相当于g=g<<2;
复制代码


【34.6   例程练习和分析。】

        现在编写一个程序来验证刚才讲到的“左移”运算:
        程序代码如下:
  1. /*---C语言学习区域的开始。-----------------------------------------------*/

  2. void main() //主函数
  3. {
  4.          unsigned char a=5;
  5.          unsigned char b=5;

  6.          unsigned char H=0x12; //单字节
  7.          unsigned char L=0x34; //单字节
  8.          unsigned int c;       //双字节

  9.          unsigned char d=84;
  10.          unsigned char e=85;

  11.      unsigned char f=1;
  12.      unsigned char g=1;

  13.          //左移运算中蕴含着乘2的规律。
  14.          a=a<<1; //a左移1位,相当于a=a*2,从原来的5变成了10。
  15.          b=b<<2; //b左移2位,相当于b=b*2*2,从原来的5变成了20。

  16.          //左移的应用之一:不同变量类型的合并。
  17.          c=H;    //c的低8位被H覆盖,也就是此时c的低8位得到了H的各位值。
  18.          c=c<<8; //及时把c的低8位移动到高8位,同时c原来的低8位被填入0
  19.          c=c+L;  //此时c再加L,c的低8位就L的值。此时c得到了H和L合并而来的值。

  20.          //左移的应用之二:聚焦在某个变量的某个位。
  21.          d=d|(1<<0);      //对第0位置1。
  22.          e=e&(~(1<<0));   //对第0位清零。

  23.          //左移简写。
  24.          f<<=1;  //就相当于f=f<<1;
  25.          g<<=2;  //就相当于g=g<<2;

  26.      View(a);              //把第1个数a发送到电脑端的串口助手软件上观察。
  27.      View(b);              //把第2个数b发送到电脑端的串口助手软件上观察。
  28.      View(c);              //把第3个数c发送到电脑端的串口助手软件上观察。
  29.      View(d);              //把第4个数d发送到电脑端的串口助手软件上观察。
  30.      View(e);              //把第5个数e发送到电脑端的串口助手软件上观察。
  31.      View(f);              //把第6个数f发送到电脑端的串口助手软件上观察。
  32.      View(g);              //把第7个数g发送到电脑端的串口助手软件上观察。

  33.      while(1)  
  34.      {
  35.      }
  36. }

  37. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:10
  4. 十六进制:A
  5. 二进制:1010

  6. 第2个数
  7. 十进制:20
  8. 十六进制:14
  9. 二进制:10100

  10. 第3个数
  11. 十进制:4660
  12. 十六进制:1234
  13. 二进制:1001000110100

  14. 第4个数
  15. 十进制:85
  16. 十六进制:55
  17. 二进制:1010101

  18. 第5个数
  19. 十进制:84
  20. 十六进制:54
  21. 二进制:1010100

  22. 第6个数
  23. 十进制:2
  24. 十六进制:2
  25. 二进制:10

  26. 第7个数
  27. 十进制:4
  28. 十六进制:4
  29. 二进制:100
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-9-4 20:13:58 | 显示全部楼层
谢谢鸿哥

出0入0汤圆

 楼主| 发表于 2016-9-11 13:11:11 | 显示全部楼层
第三十五节:移位运算的右移。

【35.1   “右移”运算。】

       “右移”运算也是以位为单位进行运算的。位是指二进制中的某一位,位只能是0或者1。欲理解某个数“右移”运算的内部规律,必先把该数展开成二进制的格式,然后才好分析。“右移”运算的符号是“>>”,它的通用格式如下:
  1.        “保存变量”=“被移数”>>n;
复制代码

        运算规律是:“被移数”先被复制一份放到某个隐蔽的临时变量(也称作寄存器),然后对此临时变量展开成二进制的格式,左边是高位,右边是低位,此二进制格式的临时变量被整体由左往右移动了n位,原来左边由于数据位移动而新空出的高n位数据被直接填入0,而右边由于数据位移动而导致低n位数据被直接覆盖,最后再把移位运算的结果存入“保存变量”。多问一句,这行代码执行完毕后,“保存变量”和“被移数”到底哪个变量发生了变化,哪个变量维持不变?大家记住,只有赋值语句“=”左边的“保存变量”发生数值变化,而右边的“被移数”没有发生变化,因为“被移数”被操作的不是它自己本身,而是它的复制品替身(某个隐蔽的临时变量,也称寄存器)。
        上述通用格式中的n代表被一次右移的位数,可以取0,当n等于0的时候,代表右移0位,其实就是数值维持原来的样子没有发生变化。
        现在举一个完整的例子来分析“>>”右移运算的规律。有两个unsigned char类型的变量a和b,它们的数值都是十进制的5,求a=a>>1和b=b>>2的结果分别是多少?分析步骤如下:

        第一步:先把a和b变量原来的数值以二进制的格式展开。十进制转二进制的方法请参考前面第14,15,16节的内容。
  1.         a变量是十进制5,它的二进制格式是: 00000101。
  2.         b变量是十进制5,它的二进制格式是: 00000101。
复制代码


        第二步:将a右移1位,将b右移2位。
       (1)a=a>>1,就是将a右移1位。
  1.         a右移前是      ->    00000101   
  2.         a右移1位后是  ->    00000010  
复制代码

        结果分析:把二进制的00000010转换成十六进制是:0x02。转换成十进制是2。所以a初始值是5,右移1位后的结果是2。

       (2)b=b>>2,就是将b右移2位。
  1.         b右移前是      ->    00000101   
  2.         b右移2位后是  ->    00000001
复制代码

        结果分析:把二进制的00000001转换成十六进制是:0x01。转换成十进制是1。所以b初始值是5,右移2位后的结果是1。

【35.2   “右移”与除法的关系。】

        左移一位相当于乘以2,而右移跟左移恰恰相反,右移一位相当于除以2,注意,这里的除法是整除,不带小数点的。比如上面例子,5右移1位就变成了2(相当于5整除2等于2),5右移2位就变成了1(相当于5整除2再整除2等于1)。这个现象背后的规律是:在右移运算中,每右移1位就相当于整除2,右移2位相当于整除2再整除2,右移3位相当于整除2再整除2再整除2......以此类推。这个规律反过来从除法的角度看,也是成立的:某个数整除2,就相当于右移1位,某个数整除2再整除2相当于右移2位,某个数整除2再整除2再整除2相当于右3位......以此类推。那么问题来了,同样是达到整除2的运算结果,从运算速度的角度对比,“右移”和“整除”哪家强?答案是:一条右移语句的运算速度比一条整除语句的运算速度要快很多倍。

【35.3   “右移”的常见应用:不同数据类型之间的分解。】

        比如有一个双字节unsigned int类型的变量c,它的初始值是0x1234,要把它分解成两个unsigned char单字节的类型数据H和L,其中H是高8位字节,L是低8位字节,分解后H应该等于0x12,L应该等于0x34,此程序如何写?就需要用到右移。程序分析如下:
  1.     unsigned char H;       //单字节
  2.     unsigned char L;       //单字节
  3.     unsigned int c=0x1234; //双字节
  4.     L=c;                   //c的低8位直接赋值给单字节的L
  5.     H=c>>8;                //c先把高8位右移到低8位,然后再把这8位数据赋值给H
复制代码

        程序运行结果:H就等于十六进制的0x12,十进制是18。L就等于十六进制的0x34,十进制是52.提一个问题,请问执行完上述最后一条语句H=c>>8后,此时c的值是多少?答案是c仍然等于0x1234,因为c本身没有发生变化,只要它没有赋值给它自己,执行完语句后就不会改变它自己本身,也就是本节开篇就提到的:“被移数”被操作的不是它自己本身,而是它的复制品替身(某个隐蔽的临时变量,也称寄存器)。

【35.4  右移运算的“右移简写”。】

        当被移数是“保存变量”时,存在“右移简写”。
  1. “保存变量”=“保存变量”>>n;
复制代码

       上述右移简写如下:
  1. “保存变量”>>=n;
复制代码

        比如:
  1. unsigned char d=8;
  2. unsigned char e=8;

  3.     d>>=1; //就相当于d=d>>1;
  4.     e>>=2; //就相当于e=e>>2;
复制代码



【35.5   例程练习和分析。】

        现在编写一个程序来验证刚才讲到的“右移”运算:
        程序代码如下:
  1. /*---C语言学习区域的开始。-----------------------------------------------*/

  2. void main() //主函数
  3. {
  4.          unsigned char a=5;
  5.          unsigned char b=5;

  6.          unsigned char H;             //单字节
  7.          unsigned char L;             //单字节
  8.          unsigned int c=0x1234;       //双字节

  9.          unsigned char d=8;
  10.          unsigned char e=8;

  11.          //右移运算中蕴含着整除2的规律。
  12.          a=a>>1;                     //a右移1位,相当于a=a/2,从原来的5变成了2。
  13.          b=b>>2;                     //b右移2位,相当于b=b/2/2,从原来的5变成了1。

  14.          //右移的常见应用:不同变量类型的分解。
  15.          L=c;                        //c的低8位直接赋值给单字节的L
  16.          H=c>>8;                     //c先把高8位右移到低8位,然后再把这8位数据赋值给H

  17.          //右移简写。
  18.          d>>=1;                      //就相当于d=d>>1;
  19.          e>>=2;                      //就相当于e=e>>2;

  20.      View(a);                    //把第1个数a发送到电脑端的串口助手软件上观察。
  21.      View(b);                    //把第2个数b发送到电脑端的串口助手软件上观察。
  22.      View(H);                    //把第3个数H发送到电脑端的串口助手软件上观察。
  23.      View(L);                    //把第4个数L发送到电脑端的串口助手软件上观察。
  24.      View(d);                    //把第5个数d发送到电脑端的串口助手软件上观察。
  25.      View(e);                    //把第6个数e发送到电脑端的串口助手软件上观察。

  26.      while(1)  
  27.      {
  28.      }
  29. }

  30. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:2
  4. 十六进制:2
  5. 二进制:10

  6. 第2个数
  7. 十进制:1
  8. 十六进制:1
  9. 二进制:1

  10. 第3个数
  11. 十进制:18
  12. 十六进制:12
  13. 二进制:10010

  14. 第4个数
  15. 十进制:52
  16. 十六进制:34
  17. 二进制:110100

  18. 第5个数
  19. 十进制:4
  20. 十六进制:4
  21. 二进制:100

  22. 第6个数
  23. 十进制:2
  24. 十六进制:2
  25. 二进制:10
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-9-14 15:31:25 | 显示全部楼层
上下距鸿哥都近

出0入0汤圆

发表于 2016-9-16 18:04:20 | 显示全部楼层
吴坚鸿 发表于 2016-3-27 23:41
第十一节:一个在单片机上练习C语言的模板程序。

code unsigned char Cu8Array1[]="第N个数";编译后丢掉最后一个字节导致不能正常显示,把“数”改成“值”后编译正常,显示也正常,不知道是什么原因
TH1=TL1=256-(11059200L/12/32/9600);这里面的L应该是笔误吧,还是别有用意,对编译倒是没有影响

出0入0汤圆

 楼主| 发表于 2016-9-18 11:29:01 | 显示全部楼层
第三十六节:括号的强制功能---改变运算优先级。

【36.1   括号的强制功能。】

        C语言中的括号有强制的功能,比如本节内容的强制改变优先级,以及以后将要讲到的数据变量类型的强制转换,指针类型的强制转换,都是要用到括号。括号就是强制,强制就是括号。

【36.2   括号强制改变运算优先级。】

        C语言的“加、减、乘、除、与、或、取反、左移、右移”等运算符是有严格优先级顺序的,但是我本人记忆力有限,做项目哪能记住这么多优先级的前后顺序,只是大概明白乘除的优先级比加减的优先级高,其它方面真的记不住那么多,怎么办?为了确保万一,我用到了“括号强制改变优先级”的功能,只要用了括号,就可以不按C语言默认的优先级顺序来出牌,可以人为的改变运算优先级,达到“随心所欲而不逾矩”的美妙境界。
        括号的用法跟我们日常的数据运算公式的用法一致,先运行括号里面的运算,再执行其它运算。比如:
  1.         a=a<<2+5;
复制代码

        这行代码到底是先把变量a左移2位后再加5,还是先2加5等于7再让变量a左移7位?对于像我这样不能熟记C语言运算优先级顺序的人,这条语句很容易让我搞混。但是加上括号就明了,添加括号后如下:
  1.         a=(a<<2)+5;
  2.         a=a<<(2+5);
复制代码

        不用多说,加上括号后,上述两行代码传递了清晰的优先级顺序。同理,再看一个例子:
  1.         c=1+3*c;
复制代码

        到底是1加3的结果再乘以变量c,还是3乘以变量c的结果再加1?因为我记得乘除法的优先级比加减法的优先级高,所以答案是3乘以变量c的结果再加1。但是对于初学者,为了避免出错,加上括号就显得更加清晰了,添加括号后如下:
  1.         c=(1+3)*c;
  2.         c=1+(3*c);
复制代码

        加括号后,优先级顺序一目了然。

【36.3   括号会不会带来额外的内存开销?】

        有人会问,括号虽好,但是添加括号会不会带来额外的内存开销?答案是:不会。比如:
  1.         c=1+3*c;     //运算顺序:默认先乘,再加。
  2.         c=1+(3*c);   //运算顺序:强制先乘,再加。实现同样的功能,这里的括号也可以省略。
复制代码

       上面两行代码,它们的运算顺序一样的,第二行代码虽然添加了括号,但是不会带来额外的内存开销,这两行代码所占的内存大小是一样的。

       括号不是鸡肋,括号应该是保健品,食之有味,又完全无副作用。用了括号可以使程序更加具有可读性,也可以让自己避开优先级顺序的大坑。

【36.4   例程练习和分析。】

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

  2. void main() //主函数
  3. {
  4.          unsigned char a=0x01;
  5.          unsigned char b=0x01;

  6.          unsigned char c=0x02;
  7.          unsigned char d=0x02;

  8.          a=(a<<2)+5;  //a左移2位后变成4,再加5等于9
  9.          b=b<<(2+5);  //2加5等于7,b再左移动7位等于128

  10.          c=(1+3)*c;  //1加3等于4,再乘以变量c等于8
  11.          d=1+(3*d);  //3乘以d等于6,再加1等于7

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

  16.      while(1)  
  17.      {
  18.      }
  19. }

  20. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:9
  4. 十六进制:9
  5. 二进制:1001

  6. 第2个数
  7. 十进制:128
  8. 十六进制:80
  9. 二进制:10000000

  10. 第3个数
  11. 十进制:8
  12. 十六进制:8
  13. 二进制:1000

  14. 第4个数
  15. 十进制:7
  16. 十六进制:7
  17. 二进制:111
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-9-20 14:03:38 | 显示全部楼层
支持了。适合新手入门~

出0入0汤圆

发表于 2016-9-20 16:22:52 | 显示全部楼层
顶个

出0入0汤圆

 楼主| 发表于 2016-9-25 12:12:28 | 显示全部楼层
第三十七节:单字节变量赋值给多字节变量的疑惑。

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

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


       那么,问题来了,如果调换过来,单字节赋值给多字节变量,多字节变量除了低8位被单字节变量所直接覆盖之外,其它剩余的位会是什么状态?会被0覆盖吗?还是会保持原来的数值不变?这个就是本节将要解开的疑惑。比如:
  1. unsigned long a=0x12345678;  //多字节变量
  2. unsigned char t=0xab;     //单字节变量
  3. a=t;  //单字节赋值给多字节变量,此时,a到底是0x123456ab?还是0x000000ab?疑惑中......
复制代码

       想解开此疑惑,只要亲自上机测试一下就知道结果。经过在keil平台下的C51编译器测试后,发现结果是这样子的:a是0x000000ab!也就是说,多字节变量其余高位是默认被0覆盖的。但是,我还有一个疑惑,是不是所有的C编译器都是这样默认处理,会不会在不同的C编译器平台下,会有不同的结论?所以,下面我再介绍两种比较可靠的办法给大家。

【37.2   我以前用的办法。】

       我以前做项目的时候,每逢遇到这个疑惑,在不同变量赋值之前,我都多插入一行清零的代码,这行代码就是先把多字节变量通过直接赋值0来清零,因为我确信常量赋值都是直接覆盖的(其余高位都直接用0填充)。比如:
  1. unsigned long a=0x12345678;  //多字节变量
  2. unsigned char t=0xab;     //单字节变量
  3. a=0;  //赋值之前先清零,这是我以前用的办法。
  4. a=t;  //单字节赋值给多字节变量
复制代码

       现在反省了一下,这种办法虽然可靠实用,但是显得过于保守。

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

       前面章节提到,括号在C语言中有强制的意思,可以强制改变优先级,也可以强制促进不同变量类型的匹配。比如:
  1. unsigned long a=0x12345678;  //多字节变量
  2. unsigned char t=0xab;     //单字节变量
  3. a=(unsigned long)t;  //此处的括号就是强制把t先转变成unsigned long类型,然后再赋值。
复制代码

       这是我现在所使用的办法,推荐大家用这种。

【37.4   例程练习和分析。】

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

  2. void main() //主函数
  3. {

  4.      unsigned long a=0x12345678;  //多字节变量
  5.      unsigned long b=0x12345678;
  6.      unsigned long c=0x12345678;

  7.      unsigned char  t=0xab;   //单字节变量

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

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

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

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

  15.      while(1)  
  16.      {
  17.      }
  18. }

  19. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:171
  4. 十六进制:AB
  5. 二进制:10101011

  6. 第2个数
  7. 十进制:171
  8. 十六进制:AB
  9. 二进制:10101011

  10. 第3个数
  11. 十进制:171
  12. 十六进制:AB
  13. 二进制:10101011
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-10-2 08:39:36 | 显示全部楼层
第三十八节:第二种解决“运算过程中意外溢出”的便捷方法。

【38.1   意外溢出。】

      运算过程中的意外溢出,稍不注意,就中招,不信,请看下面的例子:
  1.     /*---C语言学习区域的开始。-----------------------------------------------*/
  2.     unsigned long  a=0;
  3.     unsigned int x=1000;
  4.     unsigned int y=3000;
  5.     void main() //主函数
  6.     {
  7.        a=x*y;    //猜猜a是多大?
  8.        View(a);  //把第1个数a发送到电脑端的串口助手软件上观察。
  9.        while(1)  
  10.        {
  11.        }
  12.     }
  13.     /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码

       猜猜a是多大?很多人以为理所当然3000000,但是实际上是50880!中招了吧。莫名其妙的50880,就是因为意外溢出所致。怎么办呢?请看下面介绍的两种解决办法。

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

       我在前面章节中曾多次说过“为了避免运算过程中的意外溢出,建议大家把所有参与运算的变量都用unsigned long类型的变量,如果不是unsigned long类型的变量,就引入unsigned long类型的中间变量。”这种老方法如下:
  1.     /*---C语言学习区域的开始。-----------------------------------------------*/
  2.     unsigned long  a=0;
  3.     unsigned int x=1000;
  4.     unsigned int y=3000;
  5.     unsigned long  s; //引入的unsigned long中间变量。
  6.     unsigned long  t; //引入的unsigned long中间变量。
  7.     void main() //主函数
  8.     {
  9.        s=x;  //先把变量的数值搬到unsigned long中间变量。
  10.        t=y;   //先把变量的数值搬到unsigned long中间变量。
  11.        a=s*t;    //中间变量代表原始变量进行运算。
  12.        View(a);  //把第1个数a发送到电脑端的串口助手软件上观察。
  13.        while(1)  
  14.        {
  15.        }
  16.     }
  17.     /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码

       这一次,运算结果是正确的3000000。
       现在反省了一下,这种办法虽然可靠实用,但是显得有点罗嗦,而且引入的中间变量也无形中增加了一点内存。还有没有更好的办法?请看下面介绍的第二种办法。

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

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

       这一次,运算结果也是正确的3000000。

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

【38.4   全局变量和局部变量。】
   
        先插入一个知识点,细心的朋友会发现,我上面的例子中,定义的变量都放在了main函数之外的上面,这种把变量定义在函数外面的变量叫全局变量,以前例子中定义在函数内的变量叫局部变量。
  1. unsigned char a;  //这个在函数之外,叫全局变量
  2. void main() //主函数
  3. {
  4.      unsigned char b;    //这个在函数之内,叫局部变量
  5.      while(1)  
  6.      {
  7.      }
  8. }
复制代码

       上面例子中,a定义在函数之外是全局变量,b定义在函数之内是局部变量。全局变量与局部变量有什么不一样呢?以后的章节会仔细讲解这方面的知识,现在暂时不讲。之所以在这里提出这个知识点,是因为我今后的例子很多变量可能都会定义成全局变量,因此先在这里给大家打个招呼,知道C语言有这样一种语法就可以。

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

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

  2. unsigned long  a=0;
  3. unsigned long  b=0;
  4. unsigned long  c=0;
  5. unsigned int x=1000;
  6. unsigned int y=3000;

  7. unsigned long  s;  //中间变量
  8. unsigned long  t;

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

  12.      s=x;    //引入中间变量
  13.      t=y;  
  14.      b=s*t;   

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

  20.      while(1)  
  21.      {
  22.      }
  23. }

  24. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:50880
  4. 十六进制:C6C0
  5. 二进制:1100011011000000

  6. 第2个数
  7. 十进制:3000000
  8. 十六进制:2DC6C0
  9. 二进制:1011011100011011000000

  10. 第3个数
  11. 十进制:3000000
  12. 十六进制:2DC6C0
  13. 二进制:1011011100011011000000
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-10-9 09:45:59 | 显示全部楼层
第三十九节:if判断语句以及常量变量的真假判断。

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

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


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

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

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


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

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


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

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

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


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


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

【39.4   例程练习和分析。】

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

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

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

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

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

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

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

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

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

  32. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:3
  4. 十六进制:3
  5. 二进制:11
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-10-9 10:52:57 | 显示全部楼层
就像追电视剧一样,追到了39集,加油!感谢!

出0入0汤圆

 楼主| 发表于 2016-10-16 08:38:09 | 显示全部楼层
第四十节:关系符的等于“==”和不等于“!=”。

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

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

       等于关系符“==”语句的常见格式如下:
  1. if(常量或变量==常量或变量)
  2. {
  3.     语句1;
  4.     语句2;
  5. }
  6. 语句3;
  7. 语句4;
复制代码


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


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


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

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


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

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

  5. if(b!=2)
  6. {
  7.     语句2;
  8. }
复制代码


         建议改成:

  1. if(5==a)
  2. {
  3.     语句1;
  4. }

  5. if(2!=b)
  6. {
  7.     语句2;
  8. }
复制代码


【40.3   例程练习和分析。】

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  82.      while(1)  
  83.      {
  84.      }
  85. }

  86. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:2
  4. 十六进制:2
  5. 二进制:10

  6. 第2个数
  7. 十进制:7
  8. 十六进制:7
  9. 二进制:111
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-10-16 09:31:34 | 显示全部楼层
离鸿哥好近

出0入0汤圆

 楼主| 发表于 2016-10-23 08:51:57 | 显示全部楼层
第四十一节:关系符的大于“>”和大于等于“>=”。

【41.1   大于“>”。】

       大于关系符“>”语句的常见格式如下:
  1. if(常量或变量>常量或变量)
  2. {
  3.     语句1;
  4.     语句2;
  5. }
  6. 语句3;
  7. 语句4;
复制代码

       上述if条件的真假判断规则是:如果左边的数大于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。例如:
  1. if(2>1)   //2肯定大于1,所以满足条件“大于的关系”,因此为真,会执行大括号内的语句。
  2. {
  3.     语句1;
  4.     语句2;
  5. }
复制代码

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

       大于关系符“>=”语句的常见格式如下:
  1. if(常量或变量>=常量或变量)
  2. {
  3.     语句1;
  4.     语句2;
  5. }
  6. 语句3;
  7. 语句4;
复制代码

       上述if条件的真假判断规则是:如果左边的数大于或者等于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。例如:
  1. if(2>=2)   //左边的2虽然不大于右边的2,但是左边的2等于右边的2,因此为真,满足条件。
  2. {
  3.     语句1;
  4.     语句2;
  5. }
复制代码


【41.3   例程练习和分析。】

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

  82.      while(1)  
  83.      {
  84.      }
  85. }

  86. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:4
  4. 十六进制:4
  5. 二进制:100

  6. 第2个数
  7. 十进制:5
  8. 十六进制:5
  9. 二进制:101
复制代码

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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-10-30 09:18:51 | 显示全部楼层
第四十二节:关系符的小于“<”和小于等于“<=”。

【42.1   小于“<”。】

      小于关系符“<”语句的常见格式如下:
  1. if(常量或变量<常量或变量)
  2. {
  3.     语句1;
  4.     语句2;
  5. }
  6. 语句3;
  7. 语句4;
复制代码

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

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

       小于关系符“<=”语句的常见格式如下:
  1. if(常量或变量<=常量或变量)
  2. {
  3.     语句1;
  4.     语句2;
  5. }
  6. 语句3;
  7. 语句4;
复制代码

       上述if条件的真假判断规则是:如果左边的数小于或者等于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。例如:
  1. if(2<=2)   //左边的2虽然不小于右边的2,但是左边的2等于右边的2,因此为真,满足条件。
  2. {
  3.     语句1;
  4.     语句2;
  5. }
复制代码


【42.3   例程练习和分析。】

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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


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

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


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


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


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

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

  82.      while(1)  
  83.      {
  84.      }
  85. }

  86. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  1. 开始...

  2. 第1个数
  3. 十进制:3
  4. 十六进制:3
  5. 二进制:11

  6. 第2个数
  7. 十进制:4
  8. 十六进制:4
  9. 二进制:100
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-11-4 23:28:28 | 显示全部楼层
向大师学习啊

出0入0汤圆

发表于 2016-11-5 09:22:27 | 显示全部楼层
非常好的教程。

出0入0汤圆

 楼主| 发表于 2016-11-6 10:54:58 | 显示全部楼层
第四十三节:关系符中的关系符:与“&&”,或“||”。

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

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

       在上述格式中,只有if语句后面小括号内所有的(条件判断)都满足的时候,整体判断才为真,才会执行到大括号内的“语句1”和“语句2”,否则,只要有1个不满足,就直接跳到“语句3”处往下执行。

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


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

       符号“||”称为“或”,它的含义是:假如有两个以上的(条件判断),只要有1个条件判断为真,则此整体判断裁定为真,否则,必须所有的(条件判断)都不满足,此整体判断才会裁定为假。这个规律,有点像很多开关在电路回路中的并联关系,并联在回路的多个开关,只要有1个开关是闭合的状态,那么这个回路肯定是畅通的,否则,必须全部开关都是断开的,整个回路才会是断开的。
       或语句“||”的常见格式如下:
  1. if(第1个条件判断||第2个条件判断…||第N个条件判断)
  2. {
  3.     语句1;
  4.     语句2;
  5. }
  6. 语句3;
  7. 语句4;
复制代码

      在上述格式中,只要if语句后面小括号内有1个(条件判断)是满足的时候,整体判断马上裁定为真,这时就会执行到大括号内的“语句1”和“语句2”,否则,必须全部的(条件判断)都不满足,整体判断才会裁定为假,这时就会直接跳到“语句3”处往下执行。

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




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

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

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

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

【43.5   例程练习和分析。】

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  82.      while(1)  
  83.      {
  84.      }
  85. }

  86. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:2
  4. 十六进制:2
  5. 二进制:10

  6. 第2个数
  7. 十进制:6
  8. 十六进制:6
  9. 二进制:110
复制代码



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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-11-6 11:49:24 | 显示全部楼层
周末还在更新,谢谢鸿哥!

出0入0汤圆

 楼主| 发表于 2016-11-13 09:31:21 | 显示全部楼层
第四十四节:小括号改变判断优先级。

【44.1   小括号的概述。】

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


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


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

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

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

  1.        if(“判断条件1”||“判断条件2 ”&&“判断条件3”)
复制代码

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

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


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


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

       插入小括号的第1种情况:
  1. if((真||真)&&假)
  2. {
  3.    语句1;
  4. }
复制代码

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

       插入小括号的第2种情况:
  1. if(真||(真&&假))
  2. {
  3.    语句1;
  4. }
复制代码

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

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

【44.3   例程练习和分析。】

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

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

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

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

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

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

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

  20.      while(1)  
  21.      {
  22.      }
  23. }

  24. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:0
  4. 十六进制:0
  5. 二进制:0

  6. 第2个数
  7. 十进制:1
  8. 十六进制:1
  9. 二进制:1
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-11-20 11:11:41 | 显示全部楼层
第四十五节: 组合判断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   二选一的组合判断。】

        先讲第一种的“二选一”的书写格式,如下:
        书写格式如下:
  1. if(条件1)   //if只能出现第一个条件,并且只能出现一次
  2. {
  3.     语句1;
  4. }
  5. else        //else只能出现最后,并且也只能出现一次。
  6. {
  7.        语句2;
  8. }
  9. 语句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的判断语句。书写格式如下:
  1. if(条件1)         //if只能出现第一个条件,并且只能出现一次
  2. {
  3.     语句1;
  4. }
  5. else if(条件2)   //else if只能出现中间或最后,可以出现多次
  6. {
  7.     语句2;
  8. }
  9. ...
  10. else if(条件N)   //else if只能出现中间或最后,可以出现多次
  11. {
  12.     语句N;
  13. }
  14. else             //else只能出现最后,并且也只能出现一次。
  15. {
  16.        语句N+1;
  17. }
  18. 语句N+2;
复制代码

        这类语句的书写特点是:第一行是if开始,最后一行以else结束,中间是N个else if判断语句。
        这类语句的执行顺序是:跟第一种“二选一”对比,判断顺序和规律大致也是一样的,也是从第一个if开始,往下逐个判断,然后到中间的else if,只要发现一个条件满足,就执行该条件后面的大括号内的代码,之后就马上结束整个组合判断语句,不再判断剩下的组合判断语句。但是,如果万一前面第一个if和中间所有的else if的条件都不满足而为假,就直接执行最后一个else大括号内的语句。所以叫“多选一”,在“第一个if、中间的else if、最后一个else”之间多选一。

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

        最后讲第三种书写格式的“多选一或者什么都不选”,这种书写格式,跟第二种对比,只有第一个if和其它的else if语句,没有最后那个else语句。书写格式如下:
  1. if(条件1)          //if只能出现第一个条件,并且只能出现一次
  2. {
  3.     语句1;
  4. }
  5. else if(条件2)    //else if只能出现中间或最后,可以出现多次
  6. {
  7.     语句2;
  8. }
  9. ...
  10. else if(条件N)   //else if只能出现中间或最后,可以出现多次
  11. {
  12.     语句N;
  13. }
  14. 语句N+1;
复制代码

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

【45.5   例程练习和分析。】

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

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

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

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

  9. void main() //主函数
  10. {

  11.   //第一种“二选一”
  12.   if(x>6)  
  13.   {
  14.      a=1;   //1代表执行了“语句1”
  15.   }
  16.   else
  17.   {
  18.      a=2;  //2代表执行了“语句2”
  19.   }

  20.   //第二种“多选一”
  21.   if(x>6)
  22.   {
  23.      b=1;   //1代表执行了“语句1”
  24.   }
  25.   else if(7==x)  
  26.   {
  27.      b=2;  //2代表执行了“语句2”
  28.   }
  29.   else
  30.   {
  31.      b=3;  //3代表执行了“语句3”
  32.   }

  33.   //第三种“多选一或者什么都不选”
  34.   if(x>6)  
  35.   {
  36.      c=1;   //1代表执行了“语句1”
  37.   }
  38.   else if(7==x)  
  39.   {
  40.      c=2;  //2代表执行了“语句2”
  41.   }
  42.   else if(8==x)  
  43.   {
  44.      c=3;  //3代表执行了“语句3”
  45.   }  

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

  49.      while(1)  
  50.      {
  51.      }
  52. }

  53. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:2
  4. 十六进制:2
  5. 二进制:10

  6. 第2个数
  7. 十进制:3
  8. 十六进制:3
  9. 二进制:11

  10. 第3个数
  11. 十进制:0
  12. 十六进制:0
  13. 二进制:0
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-11-20 20:51:52 | 显示全部楼层
鸿哥双更新的

出0入0汤圆

 楼主| 发表于 2016-11-27 11:47:16 | 显示全部楼层
第四十六节: 一维数组。

【46.1   数组是什么?】

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

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

      一维数组不带初始化时候的定义格式如下:
  1.       数据类型 数组名[数组元素总数N];
复制代码

      数据类型是指unsigned char,unsigned int,unsigned long这类关键词;数组名就是由字母和数字组合而成的字符串,遵循常用变量的命名规则;N是数字,代表此数组内部有多少个元素。比如:
  1.       unsigned char x[3];  //这里的3是数组内部元素的总数,但不是下标。
复制代码

      上述这一行代码,就相当于一条语句定义了3个变量,这3个变量分别是x[0],x[1],x[2],但是不存在x[3]这个变量。这里,具体元素中括号内的“0,1,2”称为数组的下标,代表某个具体的元素。由此可见,数组有“批量定义”的特点。同时也要发现,此数组定义的N是3,代表内含3个元素变量,但是具体到某个元素的时候,下标不是从1开始,而是从0开始,最后一个也不是3而是2。可以这样描述,某个数组有N个元素,它具体元素的下标是从0开始,到N-1结束。那么问题来,如果一个数组明明最大只有N个元素,但是我在操作某个具体的元素时,非要用下标N或者N+1,也就是说,如果超过数组的范围的操作,会出现什么问题?后果严重吗?答案是:会导致数组越界出现异常或者编译不通过,可能会破坏其它数据,后果是严重的。因此大家使用数组的时候,要注意数组不能越界的问题。

      刚刚讲了一维数组不带初始化的定义格式,现在接着讲带初始化的定义格式,如下:
  1.       数据类型 数组名[数组元素总数N]={元素0, 元素1,…元素N-1};
复制代码

      比如:
  1.       unsigned char y[3]={10,11,12};
复制代码

      此数组一行代码定义了3个变量,分别是y[0], y[1], y[2]。而y[0]初始化为10,y[1]初始化为11,y[2]初始化为12。
      在程序中,操作数组某个变量元素时,下标可以是常量,比如y[0],此时的0就是常量;下标也可以是变量,比如y的中括号内的i,此时的i就是变量。再强调一次,作为下标的常量或者变量i的数值必须小于数组定义时的元素个数,否则就会导致数组越界出现异常或者编译不通过。

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

【46.3   例程练习和分析。】

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

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

  5. void main() //主函数
  6. {

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

  13.      View(x[0]);  //把第1个数x[0]发送到电脑端的串口助手软件上观察。
  14.      View(x[1]);  //把第2个数x[1]发送到电脑端的串口助手软件上观察。
  15.      View(x[2]);  //把第3个数x[2]发送到电脑端的串口助手软件上观察。
  16.      View(y[0]);  //把第4个数y[0]发送到电脑端的串口助手软件上观察。
  17.      View(y[1]);  //把第5个数y[1]发送到电脑端的串口助手软件上观察。
  18.      View(y[2]);  //把第6个数y[2]发送到电脑端的串口助手软件上观察。

  19.      while(1)  
  20.      {
  21.      }
  22. }

  23. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:25
  4. 十六进制:19
  5. 二进制:11001

  6. 第2个数
  7. 十进制:26
  8. 十六进制:1A
  9. 二进制:11010

  10. 第3个数
  11. 十进制:28
  12. 十六进制:1C
  13. 二进制:11100

  14. 第4个数
  15. 十进制:10
  16. 十六进制:A
  17. 二进制:1010

  18. 第5个数
  19. 十进制:11
  20. 十六进制:B
  21. 二进制:1011

  22. 第6个数
  23. 十进制:12
  24. 十六进制:C
  25. 二进制:1100
复制代码


分析:        
       变量元素x[0]为25。
       变量元素x[1]为26。
       变量元素x[2]为28。
       变量元素y[0]为10。
       变量元素y[1]为11。
       变量元素y[2]为12。

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-12-4 09:51:49 | 显示全部楼层
第四十七节: 二维数组。

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

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

      上述是对二维数组的感性描述,二维数组是由一维数组发展而来,所以继承了很多一维数组的特点。二维数组的所有“网点”元素的地址都是挨个相临的,先从第0行开始“扫描”当前行的列,第0行第0列,第0行第1列,第0行第2列......再第1行第0列,第1行第1列,第1行第2列......再第2行......再第N行,上一行“尾”元素跟下一行“头”元素的地址也是相临连续的。
       二维数组未带初始化时的通用定义格式如下:
  1.        数据类型 数组名[行数Y][列数X];
复制代码

       比如:
  1.        unsigned char  a[2][3]; //此处的2代表有2行,3代表有3列。
复制代码

       分析:此二维数组定义了6个变量,跟一维数组一样,下标都是从0开始,到(N-1)时结束,此处的N代表行数或者列数。所以a[2][3]数组的元素挨个分别是a[0][0],a[0][1], a[0][2], a[1][0], a[1][1], a[1][2]这6个变量。这6个变量的地址是顺序挨个相连的。

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

       第一种逐行初始化:
  1. unsigned char a[2][3]=
  2. {
  3.    {0,1,2},
  4.    {3,4,5}
  5. };
复制代码

       在逐行初始化定义二维数组时,只要有初始化的数据,也可以省略行下标,但是列下标不能省略,比如:
  1. unsigned char a[][3]=
  2. {
  3.    {0,1,2},
  4.    {3,4,5}
  5. };
复制代码

       此时编译器会根据元素的个数来确定行数是多少。


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

       或者
  1. unsigned char a[2][3]=
  2. {
  3.    0,1,2,
  4.    3,4,5
  5. };
复制代码

都行。

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

【47.2   例程练习和分析。】

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

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

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

  10.      View(a[0][0]);  //把第1个数a[0][0]发送到电脑端的串口助手软件上观察。
  11.      View(a[0][1]);  //把第2个数a[0][1]发送到电脑端的串口助手软件上观察。
  12.      View(a[0][2]);  //把第3个数a[0][2]发送到电脑端的串口助手软件上观察。
  13.      View(a[1][0]);  //把第4个数a[1][0]发送到电脑端的串口助手软件上观察。
  14.      View(a[1][1]);  //把第5个数a[1][1]发送到电脑端的串口助手软件上观察。
  15.      View(a[1][2]);  //把第6个数a[1][2]发送到电脑端的串口助手软件上观察。

  16.      while(1)  
  17.      {
  18.      }
  19. }

  20. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:8
  4. 十六进制:8
  5. 二进制:1000

  6. 第2个数
  7. 十进制:1
  8. 十六进制:1
  9. 二进制:1

  10. 第3个数
  11. 十进制:2
  12. 十六进制:2
  13. 二进制:10

  14. 第4个数
  15. 十进制:3
  16. 十六进制:3
  17. 二进制:11

  18. 第5个数
  19. 十进制:4
  20. 十六进制:4
  21. 二进制:100

  22. 第6个数
  23. 十进制:5
  24. 十六进制:5
  25. 二进制:101
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-12-11 10:58:18 | 显示全部楼层
第四十八节: while循环语句。

【48.1   程序的“跑道”。】

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

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

       常见格式如下:
  1. while(条件)
  2. {
  3.     语句1;   
  4.     语句2;
  5.      ……   
  6.     语句N;
  7. }
  8. 语句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省略花括号,没带分号。】

  1. while(条件)
  2.     语句1;   
  3.     语句2;
  4.     ……   
  5.     语句N;
  6.     语句N+1;
复制代码

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


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

  1. while(条件);
  2.     语句1;   
  3.     语句2;
  4.     ……   
  5.     语句N;
  6.     语句N+1;
复制代码

        这次的代码跟刚才“48.3”的代码唯一的差别是,第一行代码,while(条件)后面“有分号”。所以它循环的有效范围就在第一行就结束了,不涉及“语句1”。此时,等效于下面这种添加花括号的写法:   
  1. while(条件)
  2. {
  3.    ;     //这里的分号代表一条空语句
  4. }
  5. 语句1;   
  6. 语句2;
  7. ……   
  8. 语句N;
  9. 语句N+1;
复制代码

        如果while的(条件)一直为“真”,单片机就一直在循环体内执行一条“无意义”的空语句,相当于“耗着”的状态,执行不到后面“语句1”的语句,除非,条件为“假”才罢休才会跳出循环体。
        循环体内什么都没有,只写一条“空语句”,这种写法在实际项目中也是有用武之地的,比如,等待某件事是否满足条件,如果不满足,就一直死等死磕在这里,其它事情都干不了,这种“死等死磕”的做法,专业术语叫“阻塞”,与之反面相对应的是另外一个词叫“非阻塞”。对于循环的“阻塞”用法,老练的工程师通常会多加一个超时的判断,这些内容大家暂时不用深入了解,后续章节我会讲到。

【48.5   例程练习和分析。】

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

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

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

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

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

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

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

  21.      while(1)  
  22.      {
  23.      }
  24. }

  25. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:3
  4. 十六进制:3
  5. 二进制:11

  6. 第2个数
  7. 十进制:6
  8. 十六进制:6
  9. 二进制:110
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-12-12 13:10:00 | 显示全部楼层
大伙想想办法,让中国的叫兽们看看这帖子,学学鸿哥精神。

出0入0汤圆

 楼主| 发表于 2016-12-18 18:48:12 | 显示全部楼层
本帖最后由 吴坚鸿 于 2016-12-18 19:07 编辑

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

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

       格式如下:
  1. do
  2. {
  3.          语句1;   
  4.          语句2;
  5.          ……   
  6.          语句N;
  7. } 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语句的自加格式。】

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


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


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


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

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

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


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

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


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

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

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


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


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

  1. for(i=0; i<3;i++);   //注意,这里带分号。
  2.          语句1;  
  3.          语句2;
  4.          ……   
  5.          语句N;
复制代码


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


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


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

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


【49.8   例程练习和分析。】

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

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

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

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

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

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

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

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

  25.       while(1)  
  26.       {
  27.       }
  28. }

  29. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:3
  4. 十六进制:3
  5. 二进制:11

  6. 第2个数
  7. 十进制:6
  8. 十六进制:6
  9. 二进制:110

  10. 第3个数
  11. 十进制:9
  12. 十六进制:9
  13. 二进制: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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2016-12-25 10:32:55 | 显示全部楼层
第五十节: 循环体内的continue和break语句。

【50.1   continue语句。】

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

  1. while(…)或者for(…)   //循环体的条件判断入口处
  2. {    //循环体开始
  3.     语句1;   
  4.     语句2;
  5.     continue;
  6.     语句3;
  7.     ……   
  8.     语句N;
  9. }    //循环体结束
复制代码


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

  1. while(…)或者for(…)   //循环体的条件判断入口处
  2. {    //循环体开始
  3.     语句1;   
  4.     语句2;
  5. }    //循环体结束
复制代码


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


【50.2   break语句。】

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

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

  7.     ……   
  8.     语句N;
  9. }    //循环体结束
  10. 语句(N+1);  //循环体之外语句
复制代码


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

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

  10.     ……   
  11.     语句N;
  12. }    //循环体结束
  13. 语句(N+1);  //循环体之外语句
复制代码


【50.3   break语句能跳多远?】

        break语句能跳多远?预知答案请先看以下例子:
  1. while(…)
  2. {  
  3.       语句1;   
  4.       语句2;
  5.       while(…)
  6.       {   
  7.            语句3;   
  8.            break;
  9.            语句4;
  10.      }   
  11.      语句5;
  12. }   
  13. 语句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语句的使用。
      程序代码如下:

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

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

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

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

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

  33.       while(1)  
  34.       {
  35.       }
  36. }

  37. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:6
  4. 十六进制:6
  5. 二进制:110

  6. 第2个数
  7. 十进制:3
  8. 十六进制:3
  9. 二进制:11

  10. 第3个数
  11. 十进制:4
  12. 十六进制:4
  13. 二进制:100

  14. 第4个数
  15. 十进制:3
  16. 十六进制:3
  17. 二进制:11
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-12-25 18:54:42 | 显示全部楼层
过来顶一下,写得非常好,通俗易懂。

出0入0汤圆

发表于 2017-1-2 11:44:28 | 显示全部楼层
要有个目录就更加好了

出0入0汤圆

发表于 2017-1-2 19:43:50 | 显示全部楼层
谢谢了,辛苦了。

出0入0汤圆

发表于 2017-1-3 09:13:21 | 显示全部楼层
谢谢了,辛苦了!

出0入0汤圆

发表于 2017-1-3 10:31:15 | 显示全部楼层
吴坚鸿 发表于 2016-1-18 11:49
第一节:我是中国最幸福的单片机工程师。

与LZ历练差不多!都是自由人。

出0入0汤圆

 楼主| 发表于 2017-1-4 10:21:44 | 显示全部楼层
第五十一节: for和while的循环嵌套。

【51.1   循环的嵌套。】

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

  5.     }
  6.     }
复制代码


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

        例子如下:
  1. for(i=0;i<2;i++)  //大循环
  2. {
  3.         语句1;
  4.         for(k=0;k<3;k++) //内嵌的小循环
  5.         {
  6.             语句2;
  7.         }
  8.         语句3;
  9. }
复制代码

       上述例子中,带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;   //第1次大循环开始
  2.        语句2;   
  3.        语句2;
  4.        语句2;
  5.        语句3;
  6.        语句1;   //第2次大循环开始
  7.        语句2;
  8.        语句2;
  9.        语句2;
  10.        语句3;
复制代码

       根据此顺序,再看一个具体的程序例子:
  1. a=0;  
  2. b=0;
  3. for(i=0;i<2;i++)  //大循环
  4. {
  5.      a=a+1;  //被执行了2次
  6.      for(k=0;k<3;k++) //内嵌的小循环
  7.      {
  8.            b= b+1;  //被执行了6次
  9.      }
  10. }
复制代码

      上述例子中,执行完程序后,a的值变成了2,b的值变成了6。重点分析b的变化,“b=b+1”在内嵌循环体里被执行了6次,6次从何而来?就是i乘以k等于6。这个乘法次数是循环嵌套一个很重要的特性。上述程序如果用while语句来实现,等效如下:
  1. a=0;  
  2. b=0;
  3. i=0;  //控制大循环的变量初始化
  4. while(i<2)  //大循环
  5. {
  6.      a=a+1;  //被执行了2次
  7.      k=0;    //控制小循环的变量初始化
  8.      while(k<3) //内嵌的小循环
  9.      {
  10.          b= b+1;  //被执行了6次
  11.          k=k+1;
  12.     }
  13.      i=i+1;
  14. }
复制代码


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

       二维数组a[2][3],它有6个变量,在没有学for语句之前,如果要依次把每个元素单独赋值清零真不容易,要写6次赋值语句如下:
  1. a[0][0]=0;
  2. a[0][1]=0;
  3. a[0][2]=0;
  4. a[1][0]=0;
  5. a[1][1]=0;
  6. a[1][2]=0;
复制代码


       自从懂了for嵌套语句之后,可以让同样功能的代码简洁许多。上述代码等效于如下:
  1. for(i=0;i<2;i++)  //大循环
  2. {
  3.      for(k=0;k<3;k++) //内嵌的小循环
  4.      {
  5.           a[i][k]=0;
  6.       }
  7. }
复制代码


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

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

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

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

【51.5   例程练习和分析。】

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

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

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

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

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

  21.       while(1)  
  22.       {
  23.       }
  24. }

  25. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:2
  4. 十六进制:2
  5. 二进制:10

  6. 第2个数
  7. 十进制:6
  8. 十六进制:6
  9. 二进制:110

  10. 第3个数
  11. 十进制:2
  12. 十六进制:2
  13. 二进制:10
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2017-1-8 11:29:06 | 显示全部楼层
第五十二节: 支撑程序框架的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常见的格式如下:
  1. switch(变量)    //根据变量的数值大小从对应的case入口进来
  2. {
  3.      case 0:   //入口0
  4.          语句0;
  5.          break; //switch程序体的出口之一
  6.      case 1:  //入口1
  7.          语句1;
  8.          break; //switch程序体的出口之一
  9.      case 2: //入口2
  10.          语句2;
  11.          break; //switch程序体的出口之一
  12. }    //最下面的花括号也是一个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”语句来实现,等效于如下:
  1. if(0==变量)
  2. {
  3.     语句0;
  4. }
  5. else if(1==变量)
  6. {
  7.     语句1;
  8. }
  9. else if(2==变量)
  10. {
  11.     语句2;
  12. }
复制代码

【52.3   switch的break。】

       刚才的例子中,可以看到三个关键字:switch,case,break。其实并不是每个case都必须要跟break配套,break只是起到一个出口的功能。假如没有遇到break,程序会一直往下执行,直到遇到break或者switch“最下面的花括号”为止。比如:
  1. switch(变量)    //根据变量的数值大小从对应的case入口进来
  2. {
  3.      case 0:   //入口0
  4.          语句0;
  5.          break;
  6.      case 1:  //入口1
  7.          语句1;
  8.      case 2: //入口2
  9.          语句2;
  10.          break;
  11.      case 3: //入口3
  12.          语句3;
  13.          break;
  14. }   //最下面的花括号也是一个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不按从小到大的顺序(这种格式是合法的):
  1. switch(变量)   
  2. {
  3.      case 2:
  4.          语句2;
  5.          break;
  6.      case 0:  
  7.          语句0;
  8.          break;
  9.      case 1:  
  10.          语句1;
  11.          break;
  12. }   
复制代码


       第二种:case的数字不连贯(这种格式也是合法的):
  1. switch(变量)   
  2. {
  3.     case 0:  
  4.          语句0;
  5.          break;
  6.      case 3:
  7.          语句3;
  8.          break;
  9.      case 9:  
  10.          语句9;
  11.          break;
  12. }   
复制代码


【52.5   switch的default。】

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


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


【52.6   switch中内嵌switch。】

       if语句可以内嵌if语句,while语句也可以内嵌while语句,switch语句当然也可以内嵌switch。比如:
  1. switch(a)
  2. {
  3.    case 1:
  4.         switch(b)  //内嵌的switch
  5.         {
  6.              case 1:
  7.                   Break;
  8.              case 2:
  9.                   Break;
  10.         }
  11.         Break;
  12.    case 2:
  13.         Break;
  14. }
复制代码


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

【52.7   例程练习和分析。】

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

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

  2.      unsigned char k;   //switch的入口变量
  3.      unsigned char a;   //观察此变量的变化来理解switch的执行顺序
  4. void main() //主函数
  5. {
  6.       a=0;
  7.       k=2;   //入口变量等于2
  8.   switch(k)
  9.   {
  10.      case 0:  //入口0
  11.        a++;
  12.        break; //跳出switch
  13.      case 1:  //入口1
  14.        a++;
  15.      case 2:  //入口2,上述k等于2所以从这里进来
  16.        a++;
  17.      case 3:  //入口3
  18.        a++;
  19.      case 4:  //入口4
  20.        a++;
  21.        break;  //跳出switch
  22.      case 5:  //入口5
  23.        a++;
  24.        break;  //跳出switch
  25.      default:  //当前面没有遇到匹配的case入口时,就从此default入口进来
  26.        a++;
  27.        break;  //跳出switch
  28.   }            //最后一个switch的花括号也是跳出switch

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

  30.       while(1)  
  31.       {
  32.       }
  33. }

  34. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:3
  4. 十六进制:3
  5. 二进制: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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2017-1-15 14:28:15 | 显示全部楼层
本帖最后由 吴坚鸿 于 2017-1-15 14:32 编辑

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

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

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

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

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

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

  3. unsigned char  a;   //全局变量定义的第二区域
  4. unsigned char  b;
  5. unsigned char  c;

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

  12. void main() //主函数
  13. {
  14.    a=0;
  15.    b=0;
  16.    c=0;
  17.    HanShu() ;      //子函数被调用的第四区域
  18.    c=a+b;
  19.    while(1)  
  20.    {

  21.    }
  22. }
  23. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

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

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

  2.    void HanShu(void);   //子函数的声明
  3.    void HanShu(void)    //子函数的定义
  4.    {
  5.       语句1;
  6.       语句2;
  7.    }
  8.    void main() //主函数
  9.    {
  10.       语句3;
  11.       HanShu() ;      //子函数的被调用
  12.       语句4;

  13.       while(1)  
  14.       {

  15.       }
  16. }
  17. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

【53.4   例程练习和分析。】

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

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

  3. unsigned char  a;   //全局变量定义的第二区域

  4. void HanShu(void)    //子函数定义的第三区域
  5. {
  6.    a++;    //子函数的代码语句
  7. }
  8. void main() //主函数
  9. {
  10.    a=0;
  11.    a++;
  12.    HanShu() ;      //子函数被调用的第四区域
  13.    a++;
  14.    View(a);  //把第1个数a发送到电脑端的串口助手软件上观察。
  15.    while(1)  
  16.    {

  17.    }
  18. }
  19. /*---C语言学习区域的结束。-----------------------------------------------*/
复制代码


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

  2. 第1个数
  3. 十进制:3
  4. 十六进制:3
  5. 二进制:11
复制代码


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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2017-1-18 15:29:39 | 显示全部楼层
来补基础
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-20 13:41

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表