打印
[51单片机]

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

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
121
jianhong_wu|  楼主 | 2016-4-23 10:40 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
第十五节:二进制与十六进制。
第十五节_pdf文件.pdf (75.46 KB)

【15.1   十六进制是二进制的缩写。】

       在我的印象中,C51编译器好像并不支持二进制的书写格式,即使它能支持二进制的书写格式,二进制的书写还是有个弊端,就是数字太多太长了,写起来非常费劲不方便,怎么办?解决办法就是用十六进制。十六进制是二进制的缩写,之所以称它为二进制的缩写,是因为它们的转换关系非常简单直观,不需要借助计算器即可相互转换。

【15.2   何谓十六进制?】

       何谓十六进制?欲搞清楚这个问题,还得先从十进制说起。所谓十进制,就是用一位字符可以表示从0到9这十个数字。所谓二进制,就是用一位字符可以表示从0到1这二个数字。所谓十六进制,当然也就是用一位字符可以表示从0到15这十六个数字。但是十六进制马上就会面临一个问题,十六进制的10到15这6个数其实是有两位字符组成的,并不是一位呀?于是C语言用这些字符A,B,C,D,E,F分别替代10,11,12,13,14,15这6个数,10前面的0到9还是跟十进制的字符一致。A,B,C,D,E,F也可以用小写a,b,c,d,e,f来替代,在数值上不区分大小写,比如十六进制的a与A都是表示十进制的10。

【15.3   二进制与十六进制是如何转换的?】

       前面提到了十六进制是二进制的缩写,它们的转换关系非常简单直观,每1位十六进制的字符,对应4位二进制的字符。关系如下:

    十进制       二进制      十六进制
    0            0000        0
    1            0001        1
    2            0010        2
    3            0011        3
    4            0100        4
    5            0101        5
    6            0110        6
    7            0111        7
    8            1000        8
    9            1001        9
    10           1010        A
    11           1011        B
    12           1100        C
    13           1101        D
    14           1110        E
    15           1111        F

       二进制转换成十六进制的时候,如果不是4位的倍数,则最左边高位默认补上0凑合成4位的倍数。比如一个二进制的数101001,可以在左边补上2个0变成00101001,然后把每4位字符转成1个十六进制的字符。左边高4位0010对应十六进制的2,右边低4位1001对应十六进制的9,所以二进制的101001合起来最终转换成十六进制的数是29(实际上正确的写法是0x29,为什么?请继续往下看。)。

【15.4   十六进制数的标准书写格式是什么样子的?】

       十六进制的标准书写格式是什么样子的?实际上,十六进制29并不能直接写成29,否则就跟十进制的写法混淆了。为了把十六进制和十进制的书写格式进行区分,C语言规定凡是十六进制必须加一个数字0和一个字母x作为前缀,也就是十六进制必须以0x作为前缀,所以刚才的十六进制29就应该写成0x29,否则,如果直接写29编译器会认为是十进制的29,而十进制的29转换成十六进制是0x1D(十进制与十六进制之间如何转换在后面章节会讲到),0x29与0x1D可见差别很大的,凡是不加前缀的都会被默认为十进制。 多说一句,在C语言程序里,对于同样一个数值,既可以用十六进制,也可以用十进制,比如:d=0x2C与d=44的含义是一样的,因为十六进制的0x2C和十进制的44最终都会被C51编译器翻译成二进制00101100,是表示同样大小的数值。

【15.5   例程练习和分析。】

       现在我们编写一个程序来观察十六进制和二进制的关系。
       程序代码如下:

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

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

        a=0x06;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。
        b=0x0A;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。
        c=0x0e;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。
        d=0x2C;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。

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

    while(1)  
    {
    }
}

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


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

开始...

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

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

第3个数
十进制:14
十六进制:E
二进制:1110

第4个数
十进制:44
十六进制:2C
二进制:101100


分析:        
       通过实验结果,我们知道二进制与十六进制的转换关系确实非常清晰简单,所以十六进制也可以看作是二进制的缩写。

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

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

使用特权

评论回复
122
hexinhui| | 2016-4-23 14:06 | 只看该作者
jianhong_wu 发表于 2016-4-23 10:40
第十五节:二进制与十六进制。

吴老师打扰一下,我看了你写的那个程序框架的帖子,有个疑问想求解下,else if(ucKeyLock1==0)//有按键按下,且是第一次被按下,为啥不是else if(Key_sr1)? else if(ucKeyLock1==0)这个在第一个if就清零了,直接就进去了

使用特权

评论回复
123
jianhong_wu|  楼主 | 2016-5-2 10:32 | 只看该作者
第十六节:十进制与十六进制。
第十六节_pdf文件.pdf (329 KB)

【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,看看它的十进制是什么样的。

       程序代码如下:

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

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

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

    while(1)  
    {
    }
}

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


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

开始...

第1个数
十进制:230
十六进制:E6
二进制:11100110

第2个数
十进制:172
十六进制:AC
二进制:10101100


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

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

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

使用特权

评论回复
124
赵松培| | 2016-5-7 11:50 | 只看该作者
谢谢

使用特权

评论回复
125
jianhong_wu|  楼主 | 2016-5-9 14:23 | 只看该作者
本帖最后由 jianhong_wu 于 2016-5-9 14:35 编辑


第十七节:加法运算的5种常用格式。
第十七节_pdf文件.pdf (75.82 KB)
【17.1   单片机本身具备基础的数学算术能力。】

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

【17.2   加法语法格式。】

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

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

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

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

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

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

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

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

       第5种:“加数1”是保存变量本身,“加数2”是变量。比如:
       unsigned char e=2;
       unsigned char x=10;
       unsigned char y=6;
       e=e+x;
       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个加法例子:
        程序代码如下:

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

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

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

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

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

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


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

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

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

    while(1)  
    {
    }
}

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


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

开始...

第1个数
十进制:18
十六进制:12
二进制:10010

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

第3个数
十进制:16
十六进制:10
二进制:10000

第4个数
十进制:27
十六进制:1B
二进制:11011

第5个数
十进制:18
十六进制:12
二进制:10010


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

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

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

使用特权

评论回复
126
jianhong_wu|  楼主 | 2016-5-15 12:26 | 只看该作者
第十八节:连加、自加、自加简写、自加1。
第十八节_pdf文件.pdf (63.83 KB)
【18.1   连加。】

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

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

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

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

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

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

【18.3   例程练习和分析。】

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

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

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


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

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


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

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

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

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

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

    while(1)  
    {
    }
}

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


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

开始...

第1个数
十进制:172
十六进制:AC
二进制:10101100

第2个数
十进制:20
十六进制:14
二进制:10100

第3个数
十进制:19
十六进制:13
二进制:10011

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

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

第6个数
十进制:31
十六进制:1F
二进制:11111

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

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


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

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

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

使用特权

评论回复
127
haixinghp| | 2016-5-21 16:26 | 只看该作者
看了,学习到很多!谢谢鸿哥

使用特权

评论回复
128
jianhong_wu|  楼主 | 2016-5-22 11:25 | 只看该作者
第十九节:加法运算的溢出。
第十九节_pdf文件.pdf (51.85 KB)
【19.1   什么是加法运算的溢出?】

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

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

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

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

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

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

【19.2   例程练习和分析。】

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

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

void main() //主函数
{
        unsigned char a;             //一个字节
        unsigned char b=0xff;        //一个字节
        unsigned char c=0xff;        //一个字节
        unsigned int  d=0xfffe;      //两个字节
        unsigned long e=0xfffffffe;  //四个字节

        a=0x8536;
        b=b+1;
        c=c+2;
        d=d+5;
        e=e+5;

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

    while(1)  
    {
    }
}

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


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

开始...

第1个数
十进制:54
十六进制:36
二进制:110110

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

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

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

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


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

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

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

使用特权

评论回复
129
mengmeng6688| | 2016-5-26 14:53 | 只看该作者
给吴老师点赞

使用特权

评论回复
130
hugo0chen| | 2016-5-26 17:49 | 只看该作者
虽然到目前讲解的都是基础,但是能耐心整理这么多,点赞

使用特权

评论回复
131
jianhong_wu|  楼主 | 2016-5-29 11:50 | 只看该作者
本帖最后由 jianhong_wu 于 2016-5-29 11:51 编辑

第二十节:隐藏中间变量为何物?
第二十节_pdf文件.pdf (88.58 KB)
【20.1   隐藏中间变量为何物?】

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

      (1)“unsigned int”=“unsigned char”+“unsigned char”;
       unsigned int a;
       unsigned char x1=0x12;
       unsigned char y1=0xfe;
       a=x1+y1;

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

     (2)“unsigned long”=“unsigned int”+“unsigned char”;
      unsigned long b;
      unsigned int x2=0xfffe;
      unsigned char y2=0x12;
      b=x2+y2;

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

     (3)“unsigned long”=“常量”+“常量”;
      unsigned long c;
      c=50000+50000;

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

     (4)“unsigned long”=“unsigned int”+“常量”;
      unsigned long d;
      unsigned long e;
      unsigned int x3=50000;
      d=x3+30000;
      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)个例子,其转换方法如下:
unsigned long f;
unsigned int x2=0xfffe;
unsigned char y2=0x12;
unsigned  long t;  //多增加一个long类型的变量,用来变换类型。
unsigned  long r;  //多增加一个long类型的变量,用来变换类型。
t=0;    //把变量的高位和低位全部清零。
t=x2;   //把x2的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
r=0;    //把变量的高位和低位全部清零。
r=y2;   //把y2的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
f=t+r;

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

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

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

【20.3   例程练习和分析。】

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

    while(1)  
    {
    }
}

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


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

开始...

第1个数
十进制:272
十六进制:110
二进制:100010000

第2个数
十进制:16
十六进制:10
二进制:10000

第3个数
十进制:100000
十六进制:186A0
二进制:11000011010100000

第4个数
十进制:14464
十六进制:3880
二进制:11100010000000

第5个数
十进制:100000
十六进制:186A0
二进制:11000011010100000

第6个数
十进制:65552
十六进制:10010
二进制:10000000000010000

第7个数
十进制:80000
十六进制:13880
二进制:10011100010000000

第8个数
十进制:100000
十六进制:186A0
二进制:11000011010100000


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

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

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


使用特权

评论回复
132
bystone| | 2016-6-2 19:46 | 只看该作者
为楼主的想法和执着点个赞,不过说着容易做起来难,希望楼主是一个有始有终的人,加油!

使用特权

评论回复
133
jianhong_wu|  楼主 | 2016-6-5 16:10 | 只看该作者
本帖最后由 jianhong_wu 于 2016-6-5 16:11 编辑

第二十一节:减法运算的5种常见格式。
第二十一节_pdf文件.pdf (67.93 KB)
【21.1   减法语法格式。】

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

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

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

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

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

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

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

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

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

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

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

【21.2   例程练习和分析。】

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

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

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

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

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

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

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

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

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

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

    while(1)  
    {
    }
}

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


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

开始...

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

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

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

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

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



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

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

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

使用特权

评论回复
134
YLSJWGX| | 2016-6-8 08:13 | 只看该作者
请问该硬件板在哪可以买到,知道的请告知,诚挚的感谢!

使用特权

评论回复
135
jianhong_wu|  楼主 | 2016-6-12 10:17 | 只看该作者
本帖最后由 jianhong_wu 于 2016-6-12 16:11 编辑

第二十二节:连减、自减、自减简写、自减1。
第二十二节_pdf文件.pdf (74.81 KB)
【22.1   连减。】

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

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

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

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

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

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

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

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

      这时候,可以把上述格式简写成如下两种格式:
      “保存变量”--;
       --“保存变量”;

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

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

【22.3   例程练习和分析。】

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

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

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


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

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


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

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

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

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

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

    while(1)  
    {
    }
}

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


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

开始...

第1个数
十进制:50
十六进制:32
二进制:110010

第2个数
十进制:39
十六进制:27
二进制:100111

第3个数
十进制:53
十六进制:35
二进制:110101

第4个数
十进制:59
十六进制:3B
二进制:111011

第5个数
十进制:35
十六进制:23
二进制:100011

第6个数
十进制:19
十六进制:13
二进制:10011

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

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


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

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

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

使用特权

评论回复
136
炽天使11233| | 2016-6-16 14:35 | 只看该作者
吴老师,我一直是你的忠实粉丝

使用特权

评论回复
137
懒人| | 2016-6-16 17:43 | 只看该作者
赞一个。

使用特权

评论回复
138
jianhong_wu|  楼主 | 2016-6-19 12:48 | 只看该作者
第二十三节:减法溢出与假想借位。
第二十三节_pdf文件.pdf (74.93 KB)
【23.1   减法溢出与假想借位。】

       英文“unsigned”的中文意思就是”无符号的”,延伸含义是“无负号无负数”的意思,所以unsigned char ,unsigned int ,unsigned long这三种类型数据都是无负号无负数的,取值只能是0和正数,那么问题来了,当被减数小于减数的时候,运算结果会是什么样子,有什么规律?这就是本节要研究的减法溢出。
       第一个例子:
       unsigned char a;
       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)。
       第二个例子:
       unsigned int c;
       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因加减运算顺序不同而引发什么问题。
       unsigned char e;
       unsigned char f;
       e=1-6+7;
       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   例程练习和分析。】

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

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

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

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

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

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

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

    while(1)  
    {
    }
}

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


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

开始...

第1个数
十进制:255
十六进制:FF
二进制:11111111

第2个数
十进制:253
十六进制:FD
二进制:11111101

第3个数
十进制:65535
十六进制:FFFF
二进制:1111111111111111

第4个数
十进制:65533
十六进制:FFFD
二进制:1111111111111101

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

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


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

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

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

使用特权

评论回复
139
wu2388| | 2016-6-21 15:38 | 只看该作者
学习中,楼主辛苦啦

使用特权

评论回复
140
战斗机007| | 2016-6-25 13:35 | 只看该作者
鸿哥         最近忙啊!

使用特权

评论回复
发新帖 本帖赏金 42.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则