打印
[51单片机]

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

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
161
学习学习。。

使用特权

评论回复
162
杉侠| | 2016-8-15 14:01 | 只看该作者
厉害厉害

使用特权

评论回复
163
jianhong_wu|  楼主 | 2016-8-21 10:24 | 只看该作者
第三十二节:逻辑运算符的“异或”运算。
第三十二节_pdf文件.pdf (67.98 KB)
【32.1   “异或”运算。】

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



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

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




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



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

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

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



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


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

【32.3   例程练习和分析。】

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

void main() //主函数
{
     unsigned char a;
     unsigned char b;  

         a=12^9;
         b=0x55^0x0F;

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

     while(1)  
     {
     }
}

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



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

开始...

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

第2个数
十进制:90
十六进制:5A
二进制:1011010



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

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

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

使用特权

评论回复
164
wzh8158| | 2016-8-21 22:18 | 只看该作者
加油呀!急死人了!

使用特权

评论回复
165
长的帅怪我落| | 2016-8-23 15:20 | 只看该作者
顶楼主!

使用特权

评论回复
166
jianhong_wu|  楼主 | 2016-8-28 20:48 | 只看该作者
本帖最后由 jianhong_wu 于 2016-8-28 21:05 编辑

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

        前面的章节中,漏讲了“与,或,异或”的简写格式,在这类运算中,当赋值语句左边的“保存变量”也是参与运算的变量本身时,存在简写的语法格式,比如:
        a&=0x01;  //相当于a=a&0x01;
        a|=0x01;  //相当于a=a|0x01;
        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节的内容。
        十进制5的二进制格式是: 00000101。
        十进制0的二进制格式是: 00000000。


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


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


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

【33.2   “非”运算。】

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

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


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


【33.3   例程练习和分析。】

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

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

void main() //主函数
{
         unsigned char a=5;
         unsigned char b=5;
         unsigned char c=0;
         unsigned char d=0;

         a=~a;
         b=!b;

         c=~c;
         d=!d;

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

     while(1)  
     {
     }
}

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


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

开始...

第1个数
十进制:250
十六进制:FA
二进制:11111010

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

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

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


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

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

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



使用特权

评论回复
167
panqh| | 2016-9-2 11:56 | 只看该作者
谢谢,MARK

使用特权

评论回复
168
416356084| | 2016-9-3 11:18 | 只看该作者
jianhong_wu 发表于 2016-1-18 11:14
第一节:我的价值观。      我2006年毕业,2009年就出来做自由职业者在深圳以接单片机项目谋生,到现在我有 ...

楼主精神可嘉,希望大家不要总是说技术日新月异,什么什么的,万丈高楼平地起,何况到时候8位机不一定就一点儿市场都没有了,从我学单片机的时候开始,都说8位机要淘汰了,现在我毕业十年了,我看学校依然在教8位机,市场上很多小产品也一直用八位机。

即使是一个小小的三极管,你都会用了么?

感谢楼主,加油

使用特权

评论回复
169
jianhong_wu|  楼主 | 2016-9-4 12:54 | 只看该作者
本帖最后由 jianhong_wu 于 2016-9-4 13:53 编辑

第三十四节:移位运算的左移。
第三十四节_pdf文件.pdf (98.42 KB)
【34.1   “左移”运算。】

       “左移”运算也是以位为单位进行运算的。位是指二进制中的某一位,位只能是0或者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节的内容。
       a变量是十进制5,它的二进制格式是: 00000101。
       b变量是十进制5,它的二进制格式是: 00000101。


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

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

            (2)b=b<<2,就是将b左移2位。
             b左移前是      ->    00000101   
             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,此程序如何写?就需要用到左移。程序分析如下:
unsigned char H=0x12;  //单字节
unsigned char L=0x34;  //单字节
unsigned int c;        //双字节
c=H;                   //c的低8位被H覆盖,也就是c的低8位得到了H的值。
c=c<<8;                //及时把c的低8位移动到高8位,同时c原来的低8位被填入0
c=c+L;                 //此时c再加L,c的低8位就L的值。

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

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

        前面第31节讲到“或”运算,其中讲到可以对某个变量的某个位置1,当时是这样讲的,片段如下:
“或”运算最常见的用途是可以指定一个变量的某位置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。

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

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


        刚才上面讲到第31节的“或”运算,其实在第30节的“与”运算中也是可以用这种左移的方法来聚焦,只是要多配合一条“取反”的指令才可以。“与”运算跟“或”运算刚刚相反,它是对某个变量的某个位清零,当时是这样讲的,片段如下:
    “与”运算最常见的用途是可以指定一个变量二进制格式的某位清零,其它位保持不变。比如一个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。

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

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

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

       当被移数是“保存变量”时,存在“左移简写”。
       “保存变量”=“保存变量”<<n;

       上述左移简写如下:
“保存变量”<<=n;

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

    f<<=1; //就相当于f=f<<1;
    g<<=2; //就相当于g=g<<2;



【34.6   例程练习和分析。】

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

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

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

         unsigned char d=84;
         unsigned char e=85;

     unsigned char f=1;
     unsigned char g=1;

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

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

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

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

     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发送到电脑端的串口助手软件上观察。

     while(1)  
     {
     }
}

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


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

开始...

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

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

第3个数
十进制:4660
十六进制:1234
二进制:1001000110100

第4个数
十进制:85
十六进制:55
二进制:1010101

第5个数
十进制:84
十六进制:54
二进制:1010100

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

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


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

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

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


使用特权

评论回复
170
whirt_noob| | 2016-9-6 13:15 | 只看该作者
以前的框架编程的没有了?

使用特权

评论回复
171
zhoujunfeng| | 2016-9-6 23:06 | 只看该作者
辛苦了,对于科普很有帮助

使用特权

评论回复
172
jianhong_wu|  楼主 | 2016-9-11 13:15 | 只看该作者
第三十五节:移位运算的右移。
第三十五节_pdf文件.pdf (80.92 KB)
【35.1   “右移”运算。】

       “右移”运算也是以位为单位进行运算的。位是指二进制中的某一位,位只能是0或者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节的内容。
        a变量是十进制5,它的二进制格式是: 00000101。
        b变量是十进制5,它的二进制格式是: 00000101。


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

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

       (2)b=b>>2,就是将b右移2位。
        b右移前是      ->    00000101   
        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,此程序如何写?就需要用到右移。程序分析如下:
    unsigned char H;       //单字节
    unsigned char L;       //单字节
    unsigned int c=0x1234; //双字节
    L=c;                   //c的低8位直接赋值给单字节的L
    H=c>>8;                //c先把高8位右移到低8位,然后再把这8位数据赋值给H

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

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

        当被移数是“保存变量”时,存在“右移简写”。
“保存变量”=“保存变量”>>n;

       上述右移简写如下:
“保存变量”>>=n;

        比如:
unsigned char d=8;
unsigned char e=8;

    d>>=1; //就相当于d=d>>1;
    e>>=2; //就相当于e=e>>2;



【35.5   例程练习和分析。】

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

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

         unsigned char H;             //单字节
         unsigned char L;             //单字节
         unsigned int c=0x1234;       //双字节

         unsigned char d=8;
         unsigned char e=8;

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

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

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

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

     while(1)  
     {
     }
}

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


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

开始...

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

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

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

第4个数
十进制:52
十六进制:34
二进制:110100

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

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


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

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

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

使用特权

评论回复
173
jianhong_wu|  楼主 | 2016-9-18 10:35 | 只看该作者
第三十六节:括号的强制功能---改变运算优先级。
第三十六节_pdf文件.pdf (74.14 KB)
【36.1   括号的强制功能。】

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

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

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

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

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

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

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

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

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

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

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

【36.4   例程练习和分析。】

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

void main() //主函数
{
         unsigned char a=0x01;
         unsigned char b=0x01;

         unsigned char c=0x02;
         unsigned char d=0x02;

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

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

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

     while(1)  
     {
     }
}

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


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

开始...

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

第2个数
十进制:128
十六进制:80
二进制:10000000

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

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


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

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

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

使用特权

评论回复
174
jianhong_wu|  楼主 | 2016-9-25 12:17 | 只看该作者
第三十七节:单字节变量赋值给多字节变量的疑惑。
第三十七节_pdf文件.pdf (69.66 KB)
【37.1   不同类型变量的赋值疑惑。】

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


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

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

【37.2   我以前用的办法。】

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

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

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

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

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

【37.4   例程练习和分析。】

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

void main() //主函数
{

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

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

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

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

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

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

     while(1)  
     {
     }
}

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


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

开始...

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

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

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


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

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

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

使用特权

评论回复
175
holychip| | 2016-9-26 10:02 | 只看该作者
支持,MARK

使用特权

评论回复
176
jianhong_wu|  楼主 | 2016-10-2 08:42 | 只看该作者
第三十八节:第二种解决“运算过程中意外溢出”的便捷方法。
第三十八节_pdf文件.pdf (75.29 KB)
【38.1   意外溢出。】

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

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

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

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

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

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

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

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

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

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

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

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

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

unsigned long  a=0;
unsigned long  b=0;
unsigned long  c=0;
unsigned int x=1000;
unsigned int y=3000;

unsigned long  s;  //中间变量
unsigned long  t;

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

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

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

     while(1)  
     {
     }
}

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


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

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

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

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


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

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

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

使用特权

评论回复
177
liuxianbing| | 2016-10-8 13:07 | 只看该作者
楼主真是有心。感谢楼主分享作品。

使用特权

评论回复
178
embassy| | 2016-10-8 13:59 | 只看该作者
本帖最后由 embassy 于 2016-10-8 14:14 编辑

很简单的 c语言, 被你用的多么多么牛叉的样子.  大部分在工作中根本不需要这么复杂,      c语言本身就是很简单的语言(小学生都会写),很多错误都是自己没注意导致. 哪来什么框架玩意
      至少, 单片机程序,我没去用什么指针
有本事,讲讲 手册,  实际工作中,很多手册就算有中文也很难理解,  
而 我们搞单片机不就是为了 控制芯片(集成电路芯片)吗? 芯片都不去了解或者不了解,你怎么去控制,控毛线啊?

楼主搞了10年单片机,这点都不懂,  楼主改行搞C#,java,asp.net编程得了!      你们说,  深入研究c语言重要,还是 研究集成芯片手册重要???




使用特权

评论回复
评论
LAA1988 2019-11-27 17:14 回复TA
沙雕 
aqa 2019-9-10 18:17 回复TA
你这太偏激了呀,难怪你看不懂芯片手册,是有原因的啊 
chentiaotiao 2018-4-15 14:05 回复TA
不管是研究C语言重要还是研究集成芯片重要,现在最重要的是认清你自己,摆正心态最重要 
179
tkgg324| | 2016-10-8 20:28 | 只看该作者
梦娇 发表于 2016-7-25 14:33
女孩子能学会吗

当然能学

使用特权

评论回复
180
jianhong_wu|  楼主 | 2016-10-9 09:52 | 只看该作者
第三十九节:if判断语句以及常量变量的真假判断。
第三十九节_pdf文件.pdf (67.75 KB)
【39.1   if语句常规的书写格式。】

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


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

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

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


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

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


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

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

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


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


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

【39.4   例程练习和分析。】

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

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

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

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

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

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

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

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

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

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


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

开始...

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


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

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

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

使用特权

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

本版积分规则