独闷闷网

 找回密码
 立即注册
搜索
楼主: jianhong_wu
打印 上一主题 下一主题
收起左侧

[原创] 从单片机基础到程序框架(连载)

[复制链接]
21#
 楼主| 发表于 2016-5-9 14:08:00 | 只看该作者
本帖最后由 jianhong_wu 于 2016-5-9 14:37 编辑

第十七节:加法运算的5种常用格式。
第十七节_pdf文件.pdf (75.82 KB, 下载次数: 5004)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


乐于分享,勇于质疑!
22#
 楼主| 发表于 2016-5-15 12:02:27 | 只看该作者
本帖最后由 jianhong_wu 于 2016-5-15 12:29 编辑

第十八节:连加、自加、自加简写、自加1。
第十八节_pdf文件.pdf (63.83 KB, 下载次数: 5043)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


乐于分享,勇于质疑!
23#
 楼主| 发表于 2016-5-22 11:15:59 | 只看该作者
本帖最后由 jianhong_wu 于 2016-5-22 11:34 编辑

第十九节:加法运算的溢出。
第十九节_pdf文件.pdf (51.85 KB, 下载次数: 5006)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


乐于分享,勇于质疑!
24#
 楼主| 发表于 2016-5-29 11:38:14 | 只看该作者
本帖最后由 jianhong_wu 于 2016-5-29 11:57 编辑

第二十节:隐藏中间变量为何物?
第二十节_pdf文件.pdf (88.58 KB, 下载次数: 4536)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。




乐于分享,勇于质疑!
25#
 楼主| 发表于 2016-6-5 16:00:58 | 只看该作者
本帖最后由 jianhong_wu 于 2016-6-5 16:24 编辑

第二十一节:减法运算的5种常见格式。
第二十一节_pdf文件.pdf (67.93 KB, 下载次数: 4352)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


乐于分享,勇于质疑!
26#
 楼主| 发表于 2016-6-12 10:03:54 | 只看该作者
本帖最后由 jianhong_wu 于 2016-6-12 16:05 编辑

第二十二节:连减、自减、自减简写、自减1。
第二十二节_pdf文件.pdf (74.81 KB, 下载次数: 4089)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。



乐于分享,勇于质疑!
27#
 楼主| 发表于 2016-6-19 12:37:10 | 只看该作者
本帖最后由 jianhong_wu 于 2016-6-19 12:54 编辑

第二十三节:减法溢出与假想借位。
第二十三节_pdf文件.pdf (74.93 KB, 下载次数: 3849)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。
乐于分享,勇于质疑!
28#
 楼主| 发表于 2016-6-27 11:04:53 | 只看该作者
本帖最后由 jianhong_wu 于 2016-6-27 11:17 编辑

第二十四节:借用unsigned long类型的中间变量可以减少溢出现象。
第二十四节_pdf文件.pdf (62.79 KB, 下载次数: 3263)
【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变量是三种数据类型中取值范围最大的数,借用此类型的中间变量,可以减少在简单运算中可能出现的溢出问题。



乐于分享,勇于质疑!
29#
 楼主| 发表于 2016-7-4 09:04:26 | 只看该作者
本帖最后由 jianhong_wu 于 2016-7-4 09:42 编辑

第二十五节:乘法运算中的5种常用组合。
第二十五节_pdf文件.pdf (72.44 KB, 下载次数: 2827)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


乐于分享,勇于质疑!
30#
 楼主| 发表于 2016-7-12 10:02:48 | 只看该作者
本帖最后由 jianhong_wu 于 2016-7-12 10:23 编辑

第二十六节:连乘、自乘、自乘简写,溢出。
第二十六节_pdf文件.pdf (79.58 KB, 下载次数: 2636)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


乐于分享,勇于质疑!
31#
 楼主| 发表于 2016-7-17 13:53:25 | 只看该作者
本帖最后由 jianhong_wu 于 2016-7-17 14:15 编辑

第二十七节:整除求商。
第二十七节_pdf文件.pdf (83.7 KB, 下载次数: 2540)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


乐于分享,勇于质疑!
32#
 楼主| 发表于 2016-7-24 10:40:55 | 只看该作者
本帖最后由 jianhong_wu 于 2016-7-24 11:39 编辑

第二十八节:整除求余。
第二十八节_pdf文件.pdf (79.55 KB, 下载次数: 2407)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。
乐于分享,勇于质疑!
33#
 楼主| 发表于 2016-7-31 11:47:20 | 只看该作者
本帖最后由 jianhong_wu 于 2016-7-31 12:03 编辑

第二十九节:“先余后商”和“先商后余”提取数据某位,哪家强?
第二十九节_pdf文件.pdf (79.95 KB, 下载次数: 2186)
【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. 第1个数
  2. 十进制:8
  3. 十六进制:8
  4. 二进制:1000

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

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

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

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

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

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

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


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

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

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


乐于分享,勇于质疑!
34#
 楼主| 发表于 2016-8-8 16:48:50 | 只看该作者
本帖最后由 jianhong_wu 于 2016-8-8 17:24 编辑

第三十节:逻辑运算符的“与”运算。
第三十节_pdf文件.pdf (71.26 KB, 下载次数: 2063)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。



乐于分享,勇于质疑!
35#
 楼主| 发表于 2016-8-14 13:34:46 | 只看该作者
本帖最后由 jianhong_wu 于 2016-8-14 14:04 编辑

第三十一节:逻辑运算符的“或”运算。
第三十一节_pdf文件.pdf (64.15 KB, 下载次数: 1867)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


乐于分享,勇于质疑!
36#
 楼主| 发表于 2016-8-21 10:11:39 | 只看该作者
本帖最后由 jianhong_wu 于 2016-8-21 10:40 编辑

第三十二节:逻辑运算符的“异或”运算。
第三十二节_pdf文件.pdf (67.98 KB, 下载次数: 1892)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


乐于分享,勇于质疑!
37#
 楼主| 发表于 2016-8-28 20:29:15 | 只看该作者
本帖最后由 jianhong_wu 于 2016-8-28 21:16 编辑

第三十三节:逻辑运算符的“按位取反”和“非”运算。
第三十三节_pdf文件.pdf (73.59 KB, 下载次数: 1615)
【前面章节漏讲的补充。】

        前面的章节中,漏讲了“与,或,异或”的简写格式,在这类运算中,当赋值语句左边的“保存变量”也是参与运算的变量本身时,存在简写的语法格式,比如:
  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
复制代码

        第三步:
  1.         (a)把二进制的11111010转换成十六进制是:0xFA。转换成十进制是250。所以~5的结果是250。
  2.         (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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。



乐于分享,勇于质疑!
38#
 楼主| 发表于 2016-9-4 12:32:18 | 只看该作者
本帖最后由 jianhong_wu 于 2016-9-4 13:38 编辑

第三十四节:移位运算的左移。
第三十四节_pdf文件.pdf (98.42 KB, 下载次数: 1753)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


乐于分享,勇于质疑!
39#
 楼主| 发表于 2016-9-11 11:55:22 | 只看该作者
本帖最后由 jianhong_wu 于 2016-9-11 13:43 编辑

第三十五节:移位运算的右移。
第三十五节_pdf文件.pdf (80.92 KB, 下载次数: 1761)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

乐于分享,勇于质疑!
40#
 楼主| 发表于 2016-9-18 10:20:08 | 只看该作者
本帖最后由 jianhong_wu 于 2016-9-18 11:32 编辑

第三十六节:括号的强制功能---改变运算优先级。
第三十六节_pdf文件.pdf (74.14 KB, 下载次数: 1720)
【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学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

乐于分享,勇于质疑!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|独闷闷网 ( 粤ICP备12007667号-2 )

GMT+8, 2024-11-29 13:49 , Processed in 0.591533 second(s), 15 queries .

快速回复 返回顶部 返回列表