独闷闷网

标题: 从业十年,教你单片机入门基础。(连载) [打印本页]

作者: jianhong_wu    时间: 2015-2-24 14:37
标题: 从业十年,教你单片机入门基础。(连载)
本帖最后由 jianhong_wu 于 2016-5-15 20:20 编辑

    大家好,我是吴坚鸿,自从去年在本论坛写了《从业将近十年,手把手教你单片机程序框架》后,赢得了一部分网友的好评,同时也发现
了一些网友没有C语言基础,没有单片机基础,不懂keil的安装和使用,不懂74hc595的使用原理,不懂动态扫描数码管的原理,甚至连如何烧录
程序也不清楚,这样阅读我那程序框架的贴子就很吃力,所以我决定新开这个连载基础贴。我2006年毕业出来工作,到现在只工作了九年,为
何标题取名“从业十年”,是因为我打算明年2016年刚满十年后就整理出书,所以提前把书名公布出来。由于本人技术水平有限,不足之处望
各位网友指出来相互探讨。
     坚鸿51学习板原理图(第三版): 坚鸿51学习板原理图(第三版).pdf (906.06 KB, 下载次数: 4630)

第一节:跟我学单片机到底是学什么?我的两个比喻和一个规则。

第二节:我眼中学习单片机的四个阶段。

第三节:单片机一个最重要的特性。

第四节:平台软件和编译器软件的简介。

第五节:用keil软件新建,关闭,打开一个完整工程的操作流程。

第六节:把.c源代码编译成.hex机器码的操作流程。

第七节:重复利用现有工程模板进行程序开发的方法以及代码备份管理技巧。

第八节:把.hex机器码下载到单片机的操作流程。

第九节:程序从哪里开始,要到哪里去?

第十节:一个用来学习C语言的模板程序。

第十一节:变量的定义与赋值语句。

第十二节:两个变量的数据交换。

第十三节:二进制与字节单位,以及各种定义变量的取值范围。

第十四节:二进制与十六进制。

第十五节:十进制与十六进制。

第十六节:加法运算的5种常用格式。

第十七节:连加以及自加运算的简写。

第十八节:加法运算的溢出。

第十九节:加法运算中,神秘中间变量的类型以及解决“掺杂多种变量类型”的办法。

第二十节:减法运算的5种常见格式。

第二十一节:减法的连写和自减运算的简写。

第二十二节:减法运算的溢出。

第二十三节:建议把所有参与减法运算的变量都转换成unsigned long数据类型。

第二十四节:乘法运算的5种常见格式。

第二十五节:连乘以及自乘运算的简写。

第二十六节:乘法运算的溢出。

第二十七节:整除求商的运算。

第二十八节:整除求余的运算。

第二十九节:利用“整除求商求余”来拆分提取一个数的个十百千位。

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

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

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

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

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

第三十五节:移位运算的右移。

第三十六节:括号改变优先级。

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

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

第三十九节:大于关系符“>”和大于等于关系符“>=”。

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

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

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

第四十三节:if,else if,else的5种组合判断语句。

第四十四节:一维数组能批量定义变量的特点。

第四十五节:二维数组。

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

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

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

第四十九节:for和while循环体的嵌套。

第五十节:switch语句。

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

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

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

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

第五十五节:static静态局部变量在函数中的重要作用。

作者: jianhong_wu    时间: 2015-2-24 14:40
第一节:跟我学单片机到底是学什么?我的两个比喻和一个规则。

    开篇第一节,我问大家一个问题,跟我学单片机到底是学什么?我的回答是像驾驶汽车一样驾驭单片机。我教给大家的是驾驶汽车的技术而不是研发汽车的技术。因此每当别人问我学51单片机,PIC,AVR,stm32哪个更加有前途,应该先学哪个再学哪个时,我的回答是既然你是学驾驶技术,那么你用桑塔纳车来学还是用宝马车来学有差别吗?差别很小的,它们只是不同的厂家而已,只要会一种其它的就触类旁通了。把学单片机当作考驾照这是我所说的第一个比喻。
    学单片机最核心的是程序,程序跟单片机芯片是什么关系?我的回答是像歌曲跟MP3播放器的关系。我们写的程序就像预先录制好的歌曲,单片机芯片就是一个MP3播放器。把不同的歌曲下载到同一个MP3里就可以播放出不同的美妙音乐,当前下载的歌曲决定了MP3可以播放的音乐。所以我们当前编写的程序下载进单片机之后,就决定了单片机能干哪些工作,“下载程序”也俗称“烧录程序”。把单片机芯片当作MP3播放器是我第二个比喻。
    单片机芯片内部细节的工作原理是什么,为什么它能实现那么神奇的功能?我的回答是不用纠结这个问题,因为这不是我们学习的方向。考驾照的也只能告诉你汽车是由四个轮,发动机,制动系统,离合器,方向盘等部分构成,其它内部细节的原理恐怕也不会教你,不是不想教你,而确实是两个不同的学习方向。学单片机的也只能告诉你它内部是由运算器,寄存器,IO口,复位电路,晶振电路,程序存储器ROM,数据存储器RAM等部分组成,至于运算器的原理和构成也不是我们的学习方向。所以尽管我搞单片机有很多年,但是我并不是完全理解它最本质的原理。尽管我与人打交道有30多年,但是人为什么能通过大脑来灵活控制双手去活动对于我来说仍然是个迷,我只知道人是由脑袋,心脏,四肢等构成。每当有这样疑惑的时候该怎么办?我的回答是用“游戏规则”这个概念去应付它。因为游戏规则是不需要解释的,只要遵守就可以了。在应用的技术领域,把暂时不解的东西当作一种游戏规则来解读和遵守是我常用的思维方式,这个游戏规则的概念就是我所说的一个规则。
    下一节预告,我眼中学习单片机的四个阶段。

(未完待续)

作者: szdzjs    时间: 2015-2-24 15:10
来欣赏鸿哥的大作
作者: jianhong_wu    时间: 2015-2-24 15:41
szdzjs 发表于 2015-2-24 15:10
来欣赏鸿哥的大作

感谢支持。
作者: cjhk    时间: 2015-2-24 16:46
鸿哥   写的很给力   思路很好   先会用  入门后  再深究
作者: jianhong_wu    时间: 2015-2-24 16:47
cjhk 发表于 2015-2-24 16:46
鸿哥   写的很给力   思路很好   先会用  入门后  再深究

感谢精彩点评。
作者: fzwwj95    时间: 2015-2-24 23:21
顶!!!!!!!!鸿哥加油
作者: 又一个暑假    时间: 2015-2-25 19:21
鸿哥的帖子我一如既往的顶
作者: jianhong_wu    时间: 2015-2-25 19:37
又一个暑假 发表于 2015-2-25 19:21
鸿哥的帖子我一如既往的顶

感谢你一如既往的支持。
作者: 溪亭悦    时间: 2015-2-25 21:49
鸿哥,我也来参观参观嘻嘻
作者: jianhong_wu    时间: 2015-3-3 10:29
本帖最后由 jianhong_wu 于 2015-11-1 14:45 编辑

第二节:我眼中学习单片机的四个阶段。

      第一阶段:学会C语言的常用语法,熟悉51单片机开发平台软件keil的操作,了解单片机的大概原理特性,能读懂按键,数码管,跑马灯,串口的简单程序,能熟悉几个常用的外围芯片驱动。网上这方面优秀的教程很多,我正在写的这个连载《从业十年,教你单片机入门基础》也是属于这类入门教程之一。
      第二阶段:我认为这个阶段是四个阶段中最重要的阶段。很多初学者完成了第一阶段的学习,真正去面对一个小项目的时候,还是无从下手。他们不知道按键,显示,通讯,应用程序之间是如何关联起来的,他们一旦遇到多任务项目的时候不知道如何并行处理,他们最缺的是程序的框架思路。网上有很多热心牛人分享的程序框架思想,都值得大家学习和借鉴。我平时做项目是用状态机的思路,就是用switch语句实现多任务的切换,再外加一个定时中断产生不同的时间计时,有兴趣的朋友可以看看我去年在本论坛写的连载贴子《从业将近十年,手把手教你单片机程序框架》
      第三阶段:大家在做项目时,除了写单片机的软件,还不可避免的要跟整个电路的硬件打交道,掌握一些常用的硬件电路知识就显得尤其重要。电阻,电容,电感,二极管,三极管,光藕的应用,电压差和参考地的关系,两系统通信时需不需要共地的原因,隔离与非隔离的本质,常见的变压整流电路,常见的外围驱动电路等等。这方面优秀的电子基础教程很多,大家应该主动找这方面的资料来学习学习,我过一两年后也打算写写这方面的连载贴子《从业十年,单片机常用硬件知识讲解》。
      第四阶段:有了前面三个阶段的主动学习和积累,就可以去做项目了,在项目中学习。根据工作的需要来选择学习哪个厂家的单片机,比如PIC,AVR,stm32等单片机厂家;根据工作的需要来决定是否需要学习汇编语言,有一些台湾厂家的单片机并不能用C语言开发,只能用汇编;根据工作的需要来深入研究相关行业所需的硬件电路知识;根据工作的需要来学习相关的外围芯片驱动程序。我本人也打算过两三年后写写这方面的贴子,作为大家项目开发时的参考工具书来用,叫《从业十年,单片机常用外围驱动程序集》。
     下节预告,单片机一个最重要的特性。
(未完待续)

作者: 海强    时间: 2015-3-3 10:34
好好好,需要这段话来做我的指导方向。
作者: 重庆-风雪    时间: 2015-3-4 11:00
我要把这个帖子分享给我那些正在学,基础又很差的同学
作者: 101MHz    时间: 2015-3-4 12:57
很不错哦,多谢鸿哥分享,很希望鸿哥能够马上开始分享电路和外设的连载,,,,,,,,,,,
作者: KR770906    时间: 2015-3-4 22:07
鸿哥能把复杂的问题讲的深入浅出,特别好。
作者: 清风明月    时间: 2015-3-5 09:32
期待。。。。。。。。
作者: f晨星    时间: 2015-3-5 13:42
鸿哥的更新速度真的能等的黄花菜都凉了{:soso__3669389859068460655_4:}
作者: 西北狼    时间: 2015-3-5 21:37
很不错哦,多谢鸿哥分享                    
作者: jianhong_wu    时间: 2015-3-6 16:12
f晨星 发表于 2015-3-5 13:42
鸿哥的更新速度真的能等的黄花菜都凉了

更新慢是为了每一节的高质量。慢工出细活。
作者: jianhong_wu    时间: 2015-3-8 09:04
本帖最后由 jianhong_wu 于 2015-3-8 09:08 编辑

第三节:单片机一个最重要的特性。
      “道生一,一生二,二生三,三生万物。”《道德经》认为,世间万物,缤纷多彩,它们都起源自一个东西,这个“一”的东西就是“道”。电子世界也存在“一”这个东西,这个“一”繁衍出手机,电脑,电视机,机器人等丰富多彩的电子世界。这个“一”就是单片机一个最重要的特性:程序下载进单片机的内存后,可以识别管脚上的高低电平信号,管脚也可以输出不同时间长度的高低电平。下面我把这句话的5个重要关健词提取出来,详细解读它的含义。
       程序。有3种,C程序,汇编程序,机器程序。能下载进单片机的只有机器程序,C程序和汇编程序都不能直接下载进单片机,所以C程序和汇编程序最终要经过专用编译软件翻译成机器程序后,才能下载进单片机执行。程序就是语言,语言就是用来交流的,交流就必须存在两个对象,这两个对象分别是程序员和单片机。程序员用C语言或者汇编语言,单片机只用机器语言,他们两者交流就必需一个翻译家,这个翻译家就是编译软件,俗称编译器,它专门把C语言或者汇编语言翻译成单片机能识别的机器语言。现在单片机开发的主流是用C语言,我本人出来工作后就从来没有用过汇编语言,所以我的观点是,C语言是必修课,汇编语言是选修课;C语言是白话文简单易懂,汇编语言是文言文繁琐难读。当然汇编也有它的优点和不可替代的场合,汇编的翻译效率高,往往是一句汇编语言对应一句机器语言,而一句C语言有可能对应几句机器语言,所以很多嵌入式系统某段要求简洁高效的源代码都是用汇编来写的,也有少数一些很便宜的单片机不提供C编译器,只能用汇编语言开发。所以要不要学汇编,我的建议是最好根据个人的工作需求来决定。
       内存。既然程序可下载进单片机,那么单片机必然有一个存储程序的内存。单片机内存包括ROM和RAM两部分。ROM的优点是掉电后存储的内容不会丢失,缺点是除非在烧录(下载)过程中,否则上电后它存储的内容也不能更改。并且,虽然ROM在烧录(下载)过程中可以更改内容,但是更改的次数有限制,也就是烧录(下载)的次数有限制,一般最大次数是10万次,当然这里所说ROM是指FLASH的单片机,如果是OTP的单片机,那么最大次数是1次。而RAM恰好反过来,RAM的优点是上电后存储的内容可以被程序指令随时更改,而且还没有更改次数限制,缺点是掉电后内容会丢失。正因为ROM和RAM各有特点,所以它们的分工有所不同。程序包括指令和数据两部分。指令是指程序中的判断,跳转,赋值等指令,这些内容是程序烧录进单片机时就固定下来的,不可更改的,所以存储在ROM中。数据也分两种,程序指令可更改的数据和程序指令不可更改的数据。程序指令可更改的数据存储在RAM中,程序指令不可更改的数据存储在ROM中。那么谁在幕后进行这些分类存储?是编译器软件和下载器(烧录器)。编译器除了把C语言翻译成机器语言之外,还帮我们分好了类,分配好了存储的地址和位置,下载器(烧录器)再根据这些信息把程序存储到内存中。
       管脚。它是单片机与外部电路进行能量和信息交互的桥梁。有电源,复位,晶振和IO口这4种类型管脚。第一种电源管脚。是给单片机内部电路供电的接口。单片机有两种常用的供电电压,一般不是3.3V就是5V,有的单片机两种电压都兼容。第二种复位管脚。单片机上电后需要外部电路给它一个瞬间高电平或者低电平的复位信号,才能启动工作。这类外部的复位电路通常是用电容和电阻组成的充电电路来实现,也有一些系统是用专门的复位芯片来实现。第三种晶振管脚。任何单片机想要工作必须要有晶振。单片机执行程序指令是按一个节拍一个节拍来执行的。而晶振产生固定频率的脉冲就是这个节拍的基础源泉。所以把晶振比喻成单片机的心脏是非常恰当的。当然,现在很多单片机都把晶振集成到内部了,不用再外接晶振。第四种IO口管脚。这是跟我们编写程序关联最密切的管脚。前面提到的电源,复位,晶振这3种管脚是为了让单片机能工作,俗称单片机工作的三要素。而单片机工作的具体内容就是通过IO口管脚来体现的。比如,IO口能识别按健的输入,也能驱动继电器的开关,也能跟外围器件进行通信。
       电平。单片机IO口管脚检测到的电压低于或等于0.8V时是低电平,程序里读取到的是0数字。检测到的电压高于或等于2.4V时是高电平,程序里读取到的是1数字,当然IO口输入的最大电压不能超过单片机的供电电压。单片机输出的低电平是0V,单片机输出的高电平等于它的供电电压值。
       时间。时间是单片机程序必不可少的一个元素。跟外围芯片通信的时序节拍需要时间,驱动发光二极管闪烁需要时间,工控自动化的某些延时需要时间。单片机的时间来源自两方面。第一方面源自指令的周期时间。单片机是根据节拍来执行程序指令的,所以每执行一条指令都要消耗一点时间,只要让程序执行一些无实际意义的指令,并且通过调整所执行指令的条数就可以得到所需要的时间长度。第二方面源自单片机内部自带的定时器。假如设置定时器每20毫秒产生一次中断,现在要获取10秒钟的时间,只需程序统记500次定时中断就可以了,因为1秒等于1000毫秒。
       下节预告,平台软件和编译器软件的简介。
(未完待续)




作者: gibwater    时间: 2015-3-10 15:01
仔细的看了遍  写的很通熟易懂
作者: 西北狼    时间: 2015-3-11 19:50
学习了  很好,楼主幸苦
作者: jianhong_wu    时间: 2015-3-14 09:24
第四节:平台软件和编译器软件的简介。
      C语言代码写在哪里,谁负责把它翻译成Hex格式机器码?这就涉及到编辑和编译,从而诞生了平台和编译这两种软件。平台软件负责编辑源代码,编译软件负责把源代码翻译成Hex格式的机器码。
      不同厂家的单片机,它所用的平台和编译器软件都不一样。即使是同样一个厂家的单片机,它也有可能存在多种不同的第三方平台软件和编译器软件,下面列举的一些例子只是主流的平台和编译软件,并不是说它们是唯一的。
      PIC单片机的平台软件是MPLAB,8位单片机是PICC编译器,12位单片机是PIC18编译器,16位单片机是C30编译器。这个例子从侧面也说明了一个平台软件可以嵌入多种不同的编译器软件,平台软件和编译器软件存在一对多的关系。
      51单片机的平台软件是keil,编译器是C51。
      以上所述,单片机程序开发需要用到两种软件,但是实际项目开发的时候,我们只是跟平台软件打交道就可以了,因为编译器软件是当做一种独立配件嵌入到平台软件里,统一受平台软件控制。我在用PIC的8位单片机时,需要安装一次MPLAB平台软件,也需要独立再安装一次PICC编译器软件,然后运行MPLAB平台软件,在里面操作某个菜单设置选项,把PICC编译器跟MPLAB平台软件关联起来,也就是我所说的把PICC编译器嵌入到MPLAB平台软件里,统一接受平台软件的控制,但我写代码只需要跟MPLAB平台软件打交道就可以了。我早期在做51单片机开发时,也是需要把keil平台软件和C51软件分开安装,然后再把它们关联起来,但是现在从keil2版本开始,在安装keil平台软件时就已经默认把C51安装好了,并且自动把C51嵌入到了keil平台软件。我现在用keil4这个版本的平台软件,只需要安装一次keil平台软件就可以了,不需要像早期那样再单独安装C51编译器。
      下节预告:用keil软件新建,关闭,打开一个完整工程的操作流程。
(未完待续)


作者: 海强    时间: 2015-3-14 10:00
你这是什么脑瓜子!什么东西都能写到一块去!
作者: jianhong_wu    时间: 2015-3-14 10:23
海强 发表于 2015-3-14 10:00
你这是什么脑瓜子!什么东西都能写到一块去!

哈哈...用心,多读书,就可以融会贯通。
作者: jianhong_wu    时间: 2015-3-17 15:13
本帖最后由 jianhong_wu 于 2015-3-17 15:56 编辑

第五节:用keil软件新建,关闭,打开一个完整工程的操作流程。
     Keil平台软件的安装我就不多讲了,网上这方面的资料很多,大家可以百度一下如何安装keil的教程。下面开始讲解用keil软件新建,关闭,打开一个完整工程的操作流程。
     第一步:新建一个工程文件夹。先在电脑D盘目录下新建一个文件夹,取名为“stc89c52rc”。
     
     有2个地方需要解释:
1)文件夹以及后面所取的文件名不要用中文,请全部用英文,数字,或者下划线这些字符。keil软件支不支持中文名无所谓,但是在单片机这个行业,有一些单片机厂家的平台软件,某些版本是不支持中文名的,所以大家养成这个习惯,以后可以避免遇到一些不必要的麻烦。
2)新建的文件夹请直接放在某盘的根目录下,而不要放到某个已有文件夹的目录下。一方面是因为已有的文件名往往带有中文字,另外一方面是有一些单片机厂家的平台软件不支持嵌入层次太深的文件目录,所以大家养成这个习惯,以后可以避免遇到一些不必要的麻烦。

第二步:启动keil软件。双击桌面”keil uVision4”的图标启动keil软件。
第三步:关闭默认被打开的已有工程。打开keil软件时,如果发现此软件默认打开了一个之前已经存在的工程,请先关闭此工程。如果默认没有打开已有工程,这一步可以忽略跳过。关闭已有工程的操作是这样子的:点击上面”Project”选项,在弹出的下拉菜单中选择“Close Project”即可。
第四步:利用工具向导新建一个工程。点击上面”Project”选项,在弹出的下拉菜单中选择“new  uVision Project...”,在弹出的对话框中,选择保存的目录是刚才第一步新建的文件夹“stc89c52rc”目录下,输入跟文件夹名称一样的文件名“stc89c52rc”,然后单击“保存”按键,此时会弹出一个选择单片机型号的对话框,双击”Atmel”这个厂家,在展开的下拉选项中选中“AT89C52”这个型号,然后点击“OK”,此时会弹出一个英文询问框“是否要复制STARTUP.A51这个文件到工程里?”我们单击“否”即可。

     有3个地方需要解释:
(1)以上新建的保存文件名应该跟我们第一步在D盘新建的文件夹名称一致,因为有一些单片机厂家的平台软件是有这个要求的,所以大家养成这个习惯,以后可以避免遇到一些不必要的麻烦。
(2)上面之所以选择Atmel厂家的AT89C52单片机,是因为朱兆祺51学习板所用的单片机是STC89C52RC这个单片机,而STC89C52RCAT89C52是兼容的。
(3)在弹出的询问框“是否要复制STARTUP.A51这个文件到工程里?”中,STARTUP.A51这个文件有什么含义?STARTUP.A51是一个启动程序文件,在单片机进入.c程序执行main函数之前,先去执行这个启动程序,这个启动程序是专门用来初始化RAM和设置堆栈等,如果我们选“否”不添加这个启动程序,编译器也会自动加入一段我们不能更改的默认启动程序。如果选“是”,那么这个文件就会出现在我们工程里,我们可以根据需要进行更改。但是大多数的情况下,我们都不会去更改这个文件的,所以无论你选“是”还是“否”,只要你不更改START.A51这个文件,对我们都是一样的。我本人一般情况下都是选“否”。

     第五步:新建一个.c源文件。点击上面”File”选项,在弹出的下拉菜单中选择“New  ...”,会看到出来一个名字为”Text1”的文件。再一次点击上面”File”选项,在弹出的下拉菜单中选择“Save”,会弹出一个保存的对话框,还是选择保存在第一步新建的文件夹目录下,文件名取“stc89c52rc.c”,单击“保存”。
     

     有2个地方需要解释:
(1)以上所取的文件名必须带.c这个扩展名,表示此文件是C文件格式。
(2)第五步仅仅相当于在工程文件夹里新建了一个.c格式的C文件,此C文件目前跟工程还没有任何关联。

第六步:把刚才新建的.c源文件添加到工程里,跟工程建立起关联的关系。点击左边”Porject”选项框里面的”Target 1”前面的“+”号(如果没有发现Project,请按以下第2条解释操作),在展开的下拉菜单下看到“Source Group 1”。右键单击“Source Group 1”选项,在下拉菜单中选择“Add Existing Files to Group ‘Source Group 1’...”选项,弹出一个文件选择对话框,单击选中刚才新建的.c源文件,然后单击一次“Add”按钮,此时虽然对话框没有关闭,但是已经把.c源文件添加到工程里了,这时只要再点击一次“Close”按钮即可把此对话框关闭。这时发现左边的“Source Group 1”前面多了一个”+”号,单击此”+”号展开,发现下面刚才我们新添加进去的.c源文件“stc89c52rc.c”。
     有2个地方需要解释:
1)以上有一个地方,我本人觉得keil软件的用户体验做得不够好,容易引起误解。在弹出一个文件选择对话框时,先单击选中刚才新建的.c源文件,此时单击一次“Add”按钮,已经相当于把.c文件添加进工程了,但是此时keil软件并没有自动关闭对话框,这样很容易让初学者误以为.c源文件还没有被添加进去。
2)如果没有以上操作的时候没有发现左边Project窗口,请点击左下角的Project选项来切换。
第七步:双击打开左边被添加进工程的“stc89c52rc.c.c源文件,就可以在此“stc89c52rc.c”文件下输入我们的C语言代码了,请把以下范例代码复制进去,然后再一次点击”File”选项,在弹出的下拉菜单中选择“Save”保存。此时,新建一个工程的步骤已经完成。
供复制的范例代码:
  1. #include "REG52.H"

  2. void delay_long(unsigned int uiDelayLong); //延时函数

  3. sbit led_dr=P3^5;  

  4. void main()  
  5. {
  6.    while(1)
  7.    {
  8.        led_dr=1;  //LED亮
  9.        delay_long(100);    //延时50000个空指令的时间
  10.        led_dr=0;  //LED灭
  11.        delay_long(100);    //延时50000个空指令的时间
  12.    }
  13. }

  14. void delay_long(unsigned int uiDelayLong) //延时函数
  15. {
  16.    unsigned int i;
  17.    unsigned int j;
  18.    for(i=0;i<uiDelayLong;i++)
  19.    {
  20.       for(j=0;j<500;j++);  //内嵌循环的空指令数量
  21.    }
  22. }
复制代码

1个地方需要解释:
(1)把代码复制到keil4时,中文注释出现乱码怎么办?解决办法如下:
     点击左上角"Edit",在下拉菜单中选最后一项“Configuration”,在弹出的对话框中把Encoding的选项改成“Chinese GB2312(Simplified)”.

重新复制一次代码进去就恢复正常了。


第八步:打开一个现成的工程。前面七步已经讲解完了如何新建一个工程,现在教如何打开一个现成的工程。先单击右上角”X”关闭整个keil软件,然后双击桌面”keil uVision4”的图标重新启动keil软件,如果发现此软件默认打开了一个之前已经存在的工程,请先按照前面第三步关闭此工程。然后,点击上面”Project”选项,在弹出的下拉菜单中选择“Open Project...”,在弹出的文件对话框中,找到第一步新建的工程文件夹,单击选中“stc89c52rc.uvproj”这个文件名,然后点击“打开”,就可以打开一个现有的工程文件了。

下节预告:把.c源代码编译成.hex机器码的操作流程。
(未完待续)








作者: 西北狼    时间: 2015-3-18 10:28
通俗易懂,非常适合初学者 顶一个{:soso_e179:}
作者: jianhong_wu    时间: 2015-3-18 10:59
本帖最后由 jianhong_wu 于 2015-3-18 14:30 编辑

第六节:把.c源代码编译成.hex机器码的操作流程。
     第一步:打开一个现成的工程。双击桌面”keil uVision4”的图标启动keil软件,如果发现此软件默认打开了一个之前已经存在的工程,请点击上面”Project”选项,在弹出的下拉菜单中选择“Close Project”先关闭当前工程。然后,继续点击上面”Project”选项,在弹出的下拉菜单中选择“Open Project...”,在弹出的文件对话框中,在D盘找到上一节已经建立的工程文件夹stc89c52rc,单击选中“stc89c52rc.uvproj”这个文件名,点击“打开”,就可以打开一个现有的工程了。
     
第二步:设置编译环境让它允许产生.hex格式的机器码文件。鼠标右键点击选中左边”Porject”选项框里面的”Target 1”选项,在右键下拉菜单中选择“Options for Target‘Target 1’...”选项,弹出一个编译环境设置对话框,左键单击上面子菜单切换到“Output”窗口下,把“Create Hex File”勾选上。点击“OK”退出。
     有1个地方需要解释:
1)这个选项很重要,必须把“Create Hex File”选项勾上,否则后续的操作不能在工程文件夹的目录里生成.Hex的机器码文件。对于一个工程模板,只需要设置一次就可以保存起来的,下次开电脑重新打开此工程模板时不需要再设置,这些被设置的参数都是能掉电保存起来的。

第三步:启动编译。在确保stc89c52rc.c源文件里面有C语言源代码的情况下,点击上面”Project”选项,在弹出的下拉菜单中点击“Rebuild all target files”编译命令,编译器开始编译工作。
第四步:在”Build Output”窗口下观察编译结果。可以在最下方的”Build Output”窗口下观察到编译的过程提示。如果没有发现”Build Output”窗口,请把鼠标的光标移动到最下方的滑动条下边,当它呈现移动光标的形状时,按住左键往上拖动就可以看到“Build Output”窗口了。当“Build Output”窗口提示显示“creating hex file from "stc89c52rc"..."stc89c52rc" - 0 Error(s), 0 Warning(s).”等信息时,表示翻译工程结束了。其中0 Error(s)代表编译成功,没有任何错误。0 Warning(s)代表没有任何警告。只要有一个错误Error产生,就说明编译不通过。如果没有任何错误Error产生,但是有几个警告Warning产生,在这种情况下很多时候都不影响程序的正常运行,只有少数情况下是会影响代码的正常运行的,因此我本人建议哪怕是一个警告,大家也不要放过它,要找到产生这个警告的原因。查找错误的时候,只需要双击错误提示error那行内容,光标就会自动跳到源代码错误的附近,方便大家寻找语法错误。
最终观察到的Build Output窗口如下:
     第五步:编译后生成.hex机器码文件的目录位置。以上编译成功后,我们只要打开电脑D盘的stc89c52rc文件夹,就可以找到.hex扩展名的机器码文件,这个文件就是我们要下载到单片机的机器码文件。
下节预告:利用现有工程模板编译不同项目源代码的方法以及代码备份管理技巧。
(未完待续)








作者: zengmiao    时间: 2015-3-18 19:30
jianhong_wu 发表于 2015-2-24 14:40
第一节:跟我学单片机到底是学什么?我的两个比喻和一个规则。

    开篇第一节,我问大家一个问题,跟我 ...

不错的比喻,值得引用,引用你的思路没关系吧!
作者: jianhong_wu    时间: 2015-3-18 22:32
zengmiao 发表于 2015-3-18 19:30
不错的比喻,值得引用,引用你的思路没关系吧!

当然没关系啦。写出来就是跟大家分享的。
作者: jianhong_wu    时间: 2015-3-22 09:52
本帖最后由 jianhong_wu 于 2015-3-22 09:55 编辑

第七节:重复利用现有工程模板进行程序开发的方法以及代码备份管理技巧。
    是不是每做一个新项目都要新建一个工程?在同一个项目中,是不是每修改一次源代码都要新建一个工程?很多情况下都不用。这节介绍如何重复利用现有工程模板进行程序开发的方法以及代码备份管理技巧。
    重复利用现有工程模板,有三个必须。第一个必须是一个源文件的,而不是多文件编程(大家暂时不了解啥叫多文件编程也没关系)。第二个必须是同样的厂家同样的单片机型号。第三个必须进行代码备份管理,每完成一个项目的小进度,都要及时把源代码存储到电脑硬盘里,电脑硬盘里每个项目对应一个项目文件夹,每个项目文件夹里包含很多不同版本编号的源代码文件,每个源代码文件名必须有流水编号,方便识别最新版本的程序,每天下班前都要把最新版本的源代码文件上传到网盘备份,在互联网时代,把源代码存到自己的网盘,可以随时异地存取,即使遇到电脑故障损坏也不担心数据永久丢失。
    现在举一个例子来介绍它的操作流程。要修改一个LED项目的源代码,电脑D盘上已经有一个“LED项目”的文件夹,文件夹里已经有一个名称为LED_1的源代码文件,这个文件是.txt格式的文本文档,文件名称的后缀_1代表流水编号,要求修改此源代码后,再保存在此文件夹目录下的LED_2文本文档里,并且上传到网盘进行备份。
     第一步:打开一个现有的keil工程。双击桌面”keil uVision4”的图标启动keil软件,如果发现此软件默认打开了一个之前已经存在的工程,请点击上面Project选项,在弹出的下拉菜单中选择“Close Project”先关闭当前工程。然后,继续点击上面”Project”选项,在弹出的下拉菜单中选择“Open Project...”,在弹出的文件对话框中,在D盘目录下找到之前已经建立的工程文件夹stc89c52rc,单击选中“stc89c52rc.uvproj”这个文件名,点击“打开”,就可以打开一个现有的工程了。
     第二步:把当前keil工程的全部源代码清空。Ctrl+A快捷键选中当前工程的全部源代码,按下Backspace退格按键就可以清空当前工程的全部源代码。
     第三步:把最新版本的源代码导入到当前的keil工程中。在电脑D盘的“LED项目”文件夹目录下,双击打开“LED_1”的文本文档,用Ctrl+A快捷键选中文本文档的全部源代码,再用Ctrl+C快捷键复制此源代码,切换到keil工程中,把光标移动到工程的源代码编辑区,再用Ctrl+V快捷键粘贴此源代码到keil工程里。以下是复制粘贴到keil工程的源代码:

  1. #include "REG52.H"

  2. void delay_long(unsigned int uiDelayLong); //延时函数

  3. sbit led_dr=P3^5;

  4. void main()
  5. {
  6. while(1)
  7. {
  8. led_dr=1; //LED亮
  9. delay_long(100); //延时50000个空指令的时间
  10. led_dr=0; //LED灭
  11. delay_long(100); //延时50000个空指令的时间
  12. }
  13. }

  14. void delay_long(unsigned int uiDelayLong) //延时函数
  15. {
  16. unsigned int i;
  17. unsigned int j;
  18. for(i=0;i<uiDelayLong;i++)
  19. {
  20. for(j=0;j<500;j++); //内嵌循环的空指令数量
  21. }
  22. }
复制代码




     第四步:在keil工程中修改此源代码。把“led_dr=0;  //LED灭”这行代码删掉,修改后变成以下代码:
  1. #include "REG52.H"

  2. void delay_long(unsigned int uiDelayLong); //延时函数

  3. sbit led_dr=P3^5;

  4. void main()
  5. {
  6. while(1)
  7. {
  8. led_dr=1; //LED亮
  9. delay_long(100); //延时50000个空指令的时间

  10. delay_long(100); //延时50000个空指令的时间
  11. }
  12. }

  13. void delay_long(unsigned int uiDelayLong) //延时函数
  14. {
  15. unsigned int i;
  16. unsigned int j;
  17. for(i=0;i<uiDelayLong;i++)
  18. {
  19. for(j=0;j<500;j++); //内嵌循环的空指令数量
  20. }
  21. }
复制代码

第五步:启动编译。点击上面”Project”选项,在弹出的下拉菜单中点击“Rebuild all target files”编译命令,编译结束后显示编译操作成功。



第六步:把在keil工程里修改后的源代码备份到电脑硬盘里。
(1)先在D盘的”LED项目”文件夹目录下,点击鼠标右键新建一个文本文档,再右键选中此文本文档图标,重命名为”LED_2”,然后双击打开此文本文档。

(2)切换到keil工程的源代码中,用Ctrl+A快捷键选中keil工程的全部源代码,用Ctrl+C快捷键复制此代码,接着切换回D盘的”LED_2”的文本文档,用Ctrl+V快捷键把修改后的代码粘贴到D盘的”LED_2”的文本文档,并且打开文本文档左上角“文件”的下拉菜单,点击“保存”按钮保存,最后关闭此文本文档。
第七步:把"LED_2"文本文档上传到网盘里备份。我本人比较喜欢用115网盘。关于115网盘的操作,大家可以百度搜索“115网盘”。
下节预告:把.hex机器码下载到单片机的操作流程。
(未完待续)





作者: jianhong_wu    时间: 2015-3-29 20:53
本帖最后由 jianhong_wu 于 2015-6-3 18:20 编辑

第八节:把.hex机器码下载到单片机的操作流程。
烧录程序也叫下载程序。下载程序的本质是什么?把单片机当做一个存储器,每一条程序指令都对应一个唯一的存储地址,把这些指令一条条存储到指定的存储地址中,这就是下载程序的本质。对于STC89C52RC单片机,在下载程序时需要上位机界面软件和一根USB转串口线。上位机界面软件负责把指定.hex格式的机器码文件打开,.hex格式的机器码文件里面记录着每条程序指令对应的地址信息,在下载过程中,上位机界面软件根据.hex记录的指令内容和对应的地址信息,经过USB转串口线,跟单片机的内置引导程序进行串口通讯,从而把.hex记录的信息传输到单片机内部的flash存储器中,实现了程序的下载。
在讲操作流程之前,请读者先把以下一个LED灯闪烁的代码编译成.hex格式的文件,这个.hex文件保存在D盘的”stc89c52rc”文件夹里。
  1. #include "REG52.H"

  2. void delay_long(unsigned int uiDelayLong); //延时函数

  3. sbit led_dr=P3^5;  

  4. void main()  
  5. {
  6.    while(1)
  7.    {
  8.        led_dr=1;  //LED亮
  9.        delay_long(100);    //延时50000个空指令的时间
  10.        led_dr=0;  //LED亮
  11.        delay_long(100);    //延时50000个空指令的时间
  12.    }
  13. }

  14. void delay_long(unsigned int uiDelayLong) //延时函数
  15. {
  16.    unsigned int i;
  17.    unsigned int j;
  18.    for(i=0;i<uiDelayLong;i++)
  19.    {
  20.       for(j=0;j<500;j++);  //内嵌循环的空指令数量
  21.    }
  22. }
复制代码


下面详细讲解把.hex机器码下载到单片机的操作流程。

第一步:安装USB转串口驱动程序的操作流程。所谓上位机界面软件就是安装在电脑端的界面软件,电脑跟单片机进行通讯,需要一根USB转串口线,欲使USB转串口线正常工作,必须预先安装一个USB转串口的驱动程序。具体的操作是这样的:在网盘中下载”51CTO下载-CH340SER(win7 64位可用).zip”这个压缩包文件,解压后分成“CH341SER”和“INSTALL”这两个文件夹,双击打开“CH341SER”这个文件夹,找到“SETUP.EXE”这个安装应用程序,双击启动,在弹出的界面中,单击“安装”按钮即可完成驱动程序的安装。
   第二步:记录串口号。我用的电脑是XP系统,现在以XP系统为例。插入USB转串口线,右击桌面“我的电脑”,选择下拉菜单的“设备管理器”,在弹出的窗口中,点击“端口”前面的+号,在展开的选项中,会看到“USB-SERTAL CH340(COM6)”这个信息,这个COM6就是要我们记住的串口号。你们的串口号不一定是COM6,请以你们电脑显示的串口号为准。
   第三步:打开上位机界面软件“STC_ISP”。这个软件可以在宏晶单片机的官网下载获取此软件。双击打开“STC_ISP.exe”这个上位机界面软件。
   第四步:选择单片机型号。在“单片机型号”的下拉菜单中选择“STC89C/LE52RC”这个型号。如果中途弹出推荐选用其它型号的窗口,可以按确定忽略它,我们只要认准“STC89C/LE52RC”这个型号就可以了。
   第五步:设置串口号。在“串口号”的下拉菜单中,选择跟前面第二步所记录一样的串口号。
   第六步:设置最高波特率。在“最高波特率”的下拉菜单中,选择9600波特率。
   第七步:连接硬件USB转串口线和电源线。USB转串口线一端已经连接电脑USB口,另外一端9针串口端跟坚鸿51学习板的串口端连接。电源线一端用智能手机充电器的USB端口供电5V,电源线另一端连接坚鸿51学习板的USB供电端口。

   第八步:导入.hex格式的机器码文件。点击上位机界面软件的“打开程序文件”的按钮,在弹出的对话框中,选择D盘下“stc89c52rc”文件夹目录下的“stc89c52rc.hex”,双击把“stc89c52rc.hex”导入到上位机界面软件。
   第九步:启动下载。点击上位机界面软件的“下载/编程”的按钮,发现“正在检测目标单片机..”的提示信息,此时需要把51学习板重新断电后再上电,很多人也把这个重新上电的过程称为“冷启动”。
   第十步:“冷启动”后观察是否操作成功的信息。执行完前面第九步的“冷启动”后,如果发现有“...操作成功!”的提示信息,就说明下载成功了。
   第十一步:坚鸿51学习板下载程序失败时的解决办法。
(1)可以先松一下卡座,稍微挪动一下单片机,然后再卡紧单片机。卡座必须卡紧单片机,              避免接触不良。
2)改变供电电源,很多电脑的USB口供电电源干扰非常大,严重影响下载程序,请把USB电源线插入到手机充电器5VUSB接口,效果显著,明显提高了下载的成功率。
3)检查确保选择单片机型号是STC89C/LE52RC,如果软件弹出推荐其它型号的单片机窗口,不用管它,我们就选STC89C/LE52RC
4)检查STC-ISP烧写软件是否选择匹配的COM口。
5)单片机是靠串口烧录程序进去的,单片机的串口是P3.0,P3.1两根线,在烧录程序时,确保P3.0,P3.1这两根线的黄颜色跳帽必须插上,同时P3.0,P3.1两个IO口不能跳线到外围器件上。
6)点击“下载/编程”后,记得再断电并且重新上电一次。看看是否烧录成功。
7)最低波特率一直设置为2400,然后把最高波特率先改成9600试一下,如果还不行再把最高波特率改成2400试试。
8)如果还不行,就退出软件,拔掉USB转串口线,同时断电(必须把整根电源线拔掉!),重新插入USB串口线,重新插入电源线开电,重新打开软件。
9)如果还不行,学习板先断电(必须把整根电源线拔掉!),然后重启一次电脑。
10)总之:如果还不行,就按上述步骤多折腾几次。最后实在不行,就尝试更换到其它USB口,或者尝试更换到其它电脑上试试。

下节预告:主程序的两个区域:初始化和循环。
(未完待续)










作者: jianhong_wu    时间: 2015-3-31 17:58
本帖最后由 jianhong_wu 于 2015-6-3 18:21 编辑

第九节:程序从哪里开始,要到哪里去?
程序从哪里开始,要到哪里去?为了让初学者了解C语言程序的执行顺序,我把程序分成三个区域:进入主程序前的区域,主程序的初始化区域,主程序的循环区域。
进入主程序前的区域。这是上电后,在单片机执行主程序代码之前就已经完成了的工作。包括头文件的包含,宏定义,内存分配这些工作。这部分的内容可以暂时不用去了解,我会在后面的一些章节中陆续深入讲解。
主程序的初始化区域。这是上电后,单片机进入主程序后马上就要执行的程序代码,这部分区域的代码有一个特点,大家也必须记住的,就是单片机只执行一次。只要单片机不重启,不复位,那么上电后这部分的代码只被执行一次。
主程序的循环区域。单片机在主程序中执行完了初始化区域的代码,紧接着就进入这片循环区域的代码。单片机一直在循环执行这段代码,这就是上电后单片机的最终归宿,一直处在循环的状态。
下面我跟大家分析一个程序源代码的三个区域和执行顺序,大家先看中文解释部分的内容,暂时不用理解每行指令的语法。该源代码实现的功能是:上电后,蜂鸣器鸣叫一声就停止,然后看到一个LED灯一直在闪烁。本程序是基于坚鸿51单片机学习板。

  1. #include "REG52.H"  //进入主程序前的区域:头文件包含

  2. sbit beep_dr=P2^7;  //进入主程序前的区域:宏定义
  3. sbit led_dr=P3^5;   //进入主程序前的区域:宏定义

  4. unsigned long i;    //进入主程序前的区域:内存分配

  5. void main()                    //主程序入口,即将进入初始化区域
  6. {
  7.          beep_dr=0;                  //第一步:初始化区域:蜂鸣器开始鸣叫。
  8.    for(i=0;i<6250;i++);       //第二步:初始化区域:延时0.5秒左右。也就是蜂鸣器鸣叫的持续时间。
  9.          beep_dr=1;                  //第三步:初始化区域:蜂鸣器停止鸣叫。
  10.    while(1)                    //执行完上面的初始化区域,即将进入循环区域
  11.    {
  12.        led_dr=1;               //第四步:循环区域:LED开始点亮。
  13.        for(i=0;i<6250;i++);   //第五步:循环区域:延时0.5秒左右。也就是LED点亮的持续时间。
  14.        led_dr=0;  //LED灭      //第六步:循环区域:LED开始熄灭。
  15.        for(i=0;i<6250;i++);   //第七步:循环区域:延时0.5秒左右。也就是LED熄灭的持续时间。马上返回上面第四步继续循环往下执行。
  16.    }
  17. }

  18. //解释:
  19. //单片机进入主程序后,第一步到第三步是属于初始化区域,只被执行一次。然后进入循环区域,从第四步执行到第七步,
  20. //执行完第七步之后,马上返回上面第四步继续循环往下执行,单片机一直处于第四步到第七步的循环区域中。
复制代码


经过以上的分析,可以看出这三个区域的大概分布如下:


//...进入主程序前的区域
void main()               
    {
   //...初始化区域
   while(1)                     
   {
       //...循环区域
   }
}


下节预告:一个用来学习C语言的模板程序。
(未完待续)




作者: jianhong_wu    时间: 2015-4-2 13:10
本帖最后由 jianhong_wu 于 2015-4-2 13:19 编辑

第十节:一个用来学习C语言的模板程序。
目前,几乎所有的初学者在学习和上机练习C语言的时候,都是在电脑上安装VC这个调试软件,在源代码里只要调用打印语句printf就可以观察到不同的变量结果,挺方便的。但是现在我要提出另外一种方法,学习单片机的C语言,不一定非要用VC调试软件,也可以直接在坚鸿51学习板上学习和上机练习的。我可以做一个调试模板程序给初学者使用,利用8位数码管和16个LED灯来显示不同的变量结果,利用3个按键来切换显示不同的变量,这样就能达到类似在VC平台下用printf语句来观察变量的效果。甚至我个人认为这样比用VC调试的效果还更加直观。现在重点介绍这个模板程序的使用。
在模板程序里,初学者只需要在主程序的初始化区域填入自己练习的C语言代码,最后把需要观察的变量赋值给窗口变量就可以了,其它部分的代码属于模板的监控调试代码,大家暂时不用读懂它,直接复制过来就可以了。上述所谓的“赋值”,就是“=”这个语句,它表面上像我们平时用的等于号,实际上不是等于号,而是代表“给”的意思,把“=”符号右边的数复制一份给左边的变量,比如“a=36;”就是代表把36这个数值复制一份给变量a,执行这条指令后,a就等于36了。这里的分号“;”代表一条程序指令的结束。窗口变量有几个?有哪些?一共有10个,分别是GuiWdData0,GuiWdData1,GuiWdData2,GuiWdData3,GuiWdData4,GuiWdData5,GuiWdData6,GuiWdData7,GuiWdData8,GuiWdData9。这10个窗口变量是给大家调试专用的,8位数码管可以切换显示10个窗口变量,最左边2位数码管代表窗口变量号,剩下6位数码管显示十进制的窗口变量数值,另外16个LED实时显示此数据的二进制格式。最左边2位数码管从“0-”到“9-”代表从第0个窗口变量到第9个窗口变量,也就是GuiWdData0依次到GuiWdData9。用S1和S5按键可以切换显示不同的窗口变量,按住S9不放可以观察到当前窗口变量的十六进制格式数据,松开S9按键后,又自动返回显示当前窗口变量的十进制数据。
该模板程序是基于坚鸿51学习板,现在跟大家分享这个程序,要让这10个窗口变量分别显示10,11,12,13,14,15,16,17,18,19这10个数,用S1按键可以切换显示从小往大的窗口变量号,用S5按键可以切换显示从大往小的窗口变量号。再强调一次,大家只需要关注主程序main函数的初始化区域就可以了,其它的代码请直接复制过来,不用理解。比如:
void main()  //主程序   
{
   //...初始化区域
   while(1)                     
   {
      
   }
}
   详细的源代码如下:
  1. #include "REG52.H"
  2. #define const_voice_short  40  
  3. #define const_key_time1  20  
  4. #define const_key_time2  20  
  5. #define const_key_time3  20   
  6. void initial(void);
  7. void delay_short(unsigned int uiDelayShort);
  8. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  9. void display_drive(void);
  10. void display_service(void);
  11. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  12. void T0_time(void);
  13. void key_service(void);
  14. void key_scan(void);
  15. sbit beep_dr=P2^7;
  16. sbit key_sr1=P0^0;
  17. sbit key_sr2=P0^1;
  18. sbit key_sr3=P0^2;
  19. sbit key_gnd_dr=P0^4;
  20. sbit led_dr=P3^5;  
  21. sbit dig_hc595_sh_dr=P2^0;     
  22. sbit dig_hc595_st_dr=P2^1;  
  23. sbit dig_hc595_ds_dr=P2^2;  
  24. sbit hc595_sh_dr=P2^3;   
  25. sbit hc595_st_dr=P2^4;  
  26. sbit hc595_ds_dr=P2^5;  
  27. unsigned char GucKeySec=0;   
  28. unsigned char GucKey3Sr=1;
  29. unsigned int  GuiVoiceCnt=0;
  30. unsigned char GucVoiceStart=0;
  31. unsigned char GucDigShow8;
  32. unsigned char GucDigShow7;  
  33. unsigned char GucDigShow6;  
  34. unsigned char GucDigShow5;
  35. unsigned char GucDigShow4;
  36. unsigned char GucDigShow3;
  37. unsigned char GucDigShow2;
  38. unsigned char GucDigShow1;
  39. unsigned char GucDisplayUpdate=1;
  40. unsigned char GucWd=0;
  41. unsigned int GuiWdData0=0;
  42. unsigned int GuiWdData1=0;
  43. unsigned int GuiWdData2=0;
  44. unsigned int GuiWdData3=0;
  45. unsigned int GuiWdData4=0;
  46. unsigned int GuiWdData5=0;
  47. unsigned int GuiWdData6=0;
  48. unsigned int GuiWdData7=0;
  49. unsigned int GuiWdData8=0;
  50. unsigned int GuiWdData9=0;
  51. code unsigned char dig_table[]=
  52. {
  53.   0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x00,0x40,
  54. };



  55. void main() //主程序
  56. {
  57. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  58.        

  59.        
  60.   GuiWdData0=10;   //把10这个数值放到窗口变量0里面显示
  61.   GuiWdData1=11;   //把11这个数值放到窗口变量1里面显示
  62.   GuiWdData2=12;   //把12这个数值放到窗口变量2里面显示
  63.   GuiWdData3=13;   //把13这个数值放到窗口变量3里面显示
  64.   GuiWdData4=14;   //把14这个数值放到窗口变量4里面显示
  65.   GuiWdData5=15;   //把15这个数值放到窗口变量5里面显示
  66.   GuiWdData6=16;   //把16这个数值放到窗口变量6里面显示
  67.   GuiWdData7=17;   //把17这个数值放到窗口变量7里面显示
  68.   GuiWdData8=18;   //把18这个数值放到窗口变量8里面显示
  69.   GuiWdData9=19;   //把19这个数值放到窗口变量9里面显示
  70.        
  71. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  72.    while(1)  
  73.    {
  74.                   initial();
  75.       key_service();
  76.       display_service();
  77.    }

  78. }


  79. void display_service(void)
  80. {
  81.     static unsigned char SucLedStatus16_09=0;  
  82.     static unsigned char SucLedStatus08_01=0;   
  83.     static unsigned int  SinWdDataTemp=0;

  84.     if(1==GucDisplayUpdate)
  85.     {
  86.         GucDisplayUpdate=0;
  87.                
  88.                           switch(GucWd)
  89.                           {
  90.            case 0:
  91.                 GucDigShow8=0;
  92.                                               SinWdDataTemp=GuiWdData0;
  93.                                               break;
  94.            case 1:
  95.                 GucDigShow8=1;
  96.                                               SinWdDataTemp=GuiWdData1;
  97.                                               break;
  98.            case 2:
  99.                 GucDigShow8=2;
  100.                                               SinWdDataTemp=GuiWdData2;
  101.                                               break;
  102.            case 3:
  103.                 GucDigShow8=3;  
  104.                                               SinWdDataTemp=GuiWdData3;
  105.                                               break;
  106.            case 4:
  107.                 GucDigShow8=4;
  108.                                               SinWdDataTemp=GuiWdData4;
  109.                                               break;
  110.            case 5:
  111.                 GucDigShow8=5;
  112.                                               SinWdDataTemp=GuiWdData5;
  113.                                               break;
  114.            case 6:
  115.                 GucDigShow8=6;
  116.                                               SinWdDataTemp=GuiWdData6;
  117.                                               break;
  118.            case 7:
  119.                 GucDigShow8=7;
  120.                                               SinWdDataTemp=GuiWdData7;
  121.                                               break;
  122.            case 8:
  123.                 GucDigShow8=8;
  124.                                               SinWdDataTemp=GuiWdData8;
  125.                                               break;
  126.            case 9:
  127.                 GucDigShow8=9;
  128.                                               SinWdDataTemp=GuiWdData9;
  129.                                               break;                 
  130.         }
  131.                                
  132.         GucDigShow7=17;
  133.         GucDigShow6=16;         
  134.                                
  135.                                 if(1==GucKey3Sr)
  136.                                 {
  137.                                    if(SinWdDataTemp>=10000)
  138.                              {
  139.               GucDigShow5=SinWdDataTemp/10000;
  140.            }
  141.                              else
  142.                                    {
  143.               GucDigShow5=16;
  144.            }
  145.                                
  146.                                    if(SinWdDataTemp>=1000)
  147.                                    {
  148.               GucDigShow4=SinWdDataTemp%10000/1000;
  149.            }
  150.                                     else
  151.                                    {
  152.               GucDigShow4=16;
  153.            }
  154.                                
  155.                                    if(SinWdDataTemp>=100)
  156.                                    {
  157.               GucDigShow3=SinWdDataTemp%1000/100;
  158.            }
  159.                                     else
  160.                                    {
  161.               GucDigShow3=16;
  162.            }       
  163.                                
  164.                                    if(SinWdDataTemp>=10)
  165.                                    {
  166.               GucDigShow2=SinWdDataTemp%100/10;
  167.            }
  168.                                     else
  169.                                    {
  170.               GucDigShow2=16;
  171.            }       
  172.                                
  173.            GucDigShow1=SinWdDataTemp%10;
  174.                           }
  175.                                 else
  176.                                 {
  177.                                          GucDigShow5=16;
  178.                                
  179.                                    if(SinWdDataTemp>=0x1000)
  180.                                    {
  181.               GucDigShow4=SinWdDataTemp/0x1000;
  182.            }
  183.                                     else
  184.                                    {
  185.               GucDigShow4=16;
  186.            }
  187.                                
  188.                                    if(SinWdDataTemp>=0x0100)
  189.                                    {
  190.               GucDigShow3=SinWdDataTemp%0x1000/0x0100;
  191.            }
  192.                                     else
  193.                                    {
  194.               GucDigShow3=16;
  195.            }       
  196.                                
  197.                                    if(SinWdDataTemp>=0x0010)
  198.                                    {
  199.               GucDigShow2=SinWdDataTemp%0x0100/0x0010;
  200.            }
  201.                                     else
  202.                                    {
  203.               GucDigShow2=16;
  204.            }       
  205.                                
  206.            GucDigShow1=SinWdDataTemp%0x0010;
  207.         }
  208.                                
  209.                                
  210.                                 SucLedStatus16_09=SinWdDataTemp>>8;  
  211.         SucLedStatus08_01=SinWdDataTemp;
  212.         hc595_drive(SucLedStatus16_09,SucLedStatus08_01);
  213.     }

  214. }


  215. void key_scan(void)
  216. {  
  217.        
  218.   static unsigned int  SuiKeyTimeCnt1=0;
  219.   static unsigned char SucKeyLock1=0;

  220.   static unsigned int  SuiKeyTimeCnt2=0;
  221.   static unsigned char SucKeyLock2=0;

  222.   static unsigned int  SuiKey3Cnt1=0;
  223.   static unsigned int  SuiKey3Cnt2=0;
  224.        
  225.        
  226.   if(1==key_sr1)
  227.   {
  228.      SucKeyLock1=0;
  229.      SuiKeyTimeCnt1=0;
  230.   }
  231.   else if(0==SucKeyLock1)
  232.   {
  233.      SuiKeyTimeCnt1++;
  234.      if(SuiKeyTimeCnt1>const_key_time1)
  235.      {
  236.         SuiKeyTimeCnt1=0;
  237.         SucKeyLock1=1;  
  238.         GucKeySec=1;   
  239.      }
  240.   }

  241.   if(1==key_sr2)
  242.   {
  243.      SucKeyLock2=0;
  244.      SuiKeyTimeCnt2=0;
  245.   }
  246.   else if(0==SucKeyLock2)
  247.   {
  248.      SuiKeyTimeCnt2++;
  249.      if(SuiKeyTimeCnt2>const_key_time2)
  250.      {
  251.         SuiKeyTimeCnt2=0;
  252.         SucKeyLock2=1;  
  253.         GucKeySec=2;  
  254.      }
  255.   }

  256.        
  257.   if(1==key_sr3)
  258.   {
  259.        SuiKey3Cnt1=0;
  260.        SuiKey3Cnt2++;
  261.        if(SuiKey3Cnt2>const_key_time3)
  262.        {
  263.            SuiKey3Cnt2=0;
  264.            GucKey3Sr=1;  
  265.        }
  266.    }
  267.    else   
  268.    {
  269.        SuiKey3Cnt2=0;
  270.        SuiKey3Cnt1++;
  271.        if(SuiKey3Cnt1>const_key_time3)
  272.        {
  273.           SuiKey3Cnt1=0;
  274.           GucKey3Sr=0;
  275.        }
  276.    }


  277. }


  278. void key_service(void)
  279. {
  280.         static unsigned char SucKey3SrRecord=1;
  281.        
  282.   if(GucKey3Sr!=SucKey3SrRecord)
  283.   {
  284.      SucKey3SrRecord=GucKey3Sr;
  285.                  GucDisplayUpdate=1;
  286.                
  287.   }
  288.        
  289.   switch(GucKeySec)
  290.   {
  291.     case 1:
  292.           GucWd++;
  293.                       if(GucWd>9)
  294.                                         {
  295.              GucWd=9;
  296.           }
  297.                       GucDisplayUpdate=1;
  298.                                        
  299.                                        
  300.           GuiVoiceCnt=const_voice_short;
  301.           GucVoiceStart=1;
  302.           GucKeySec=0;  
  303.           break;   
  304.    
  305.     case 2:
  306.           GucWd--;
  307.                       if(GucWd>9)
  308.                                         {
  309.              GucWd=0;
  310.           }
  311.                       GucDisplayUpdate=1;
  312.                
  313.           GuiVoiceCnt=const_voice_short;
  314.           GucVoiceStart=1;
  315.           GucKeySec=0;  
  316.           break;  
  317.   }     



  318.        
  319. }

  320. void display_drive()  
  321. {
  322.    static unsigned char SucDigShowTemp=0;
  323.    static unsigned char SucDisplayDriveStep=1;
  324.        
  325.    switch(SucDisplayDriveStep)
  326.    {
  327.       case 1:
  328.            SucDigShowTemp=dig_table[GucDigShow1];
  329.            dig_hc595_drive(SucDigShowTemp,0xfe);
  330.            break;
  331.       case 2:  
  332.            SucDigShowTemp=dig_table[GucDigShow2];
  333.            dig_hc595_drive(SucDigShowTemp,0xfd);
  334.            break;
  335.       case 3:
  336.            SucDigShowTemp=dig_table[GucDigShow3];
  337.            dig_hc595_drive(SucDigShowTemp,0xfb);
  338.            break;
  339.       case 4:  
  340.            SucDigShowTemp=dig_table[GucDigShow4];
  341.            dig_hc595_drive(SucDigShowTemp,0xf7);
  342.            break;
  343.       case 5:
  344.            SucDigShowTemp=dig_table[GucDigShow5];
  345.            dig_hc595_drive(SucDigShowTemp,0xef);
  346.            break;
  347.       case 6:  
  348.            SucDigShowTemp=dig_table[GucDigShow6];
  349.            dig_hc595_drive(SucDigShowTemp,0xdf);
  350.            break;
  351.       case 7:  
  352.            SucDigShowTemp=dig_table[GucDigShow7];
  353.            dig_hc595_drive(SucDigShowTemp,0xbf);
  354.            break;
  355.       case 8:
  356.            SucDigShowTemp=dig_table[GucDigShow8];
  357.            dig_hc595_drive(SucDigShowTemp,0x7f);
  358.            break;
  359.    }

  360.    SucDisplayDriveStep++;
  361.    if(SucDisplayDriveStep>8)  
  362.    {
  363.      SucDisplayDriveStep=1;
  364.    }



  365. }



  366. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  367. {
  368.    unsigned char i;
  369.    unsigned char ucTempData;
  370.    dig_hc595_sh_dr=0;
  371.    dig_hc595_st_dr=0;

  372.    ucTempData=ucDigStatusTemp16_09;  
  373.    for(i=0;i<8;i++)
  374.    {
  375.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  376.          else dig_hc595_ds_dr=0;

  377.          dig_hc595_sh_dr=0;   
  378.          delay_short(1);
  379.          dig_hc595_sh_dr=1;
  380.          delay_short(1);

  381.          ucTempData=ucTempData<<1;
  382.    }

  383.    ucTempData=ucDigStatusTemp08_01;  
  384.    for(i=0;i<8;i++)
  385.    {
  386.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  387.          else dig_hc595_ds_dr=0;

  388.          dig_hc595_sh_dr=0;     
  389.          delay_short(1);
  390.          dig_hc595_sh_dr=1;
  391.          delay_short(1);

  392.          ucTempData=ucTempData<<1;
  393.    }

  394.    dig_hc595_st_dr=0;
  395.    delay_short(1);
  396.    dig_hc595_st_dr=1;
  397.    delay_short(1);

  398.    dig_hc595_sh_dr=0;
  399.    dig_hc595_st_dr=0;
  400.    dig_hc595_ds_dr=0;

  401. }

  402. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  403. {
  404.    unsigned char i;
  405.    unsigned char ucTempData;
  406.    hc595_sh_dr=0;
  407.    hc595_st_dr=0;

  408.    ucTempData=ucLedStatusTemp16_09;  
  409.    for(i=0;i<8;i++)
  410.    {
  411.          if(ucTempData>=0x80)hc595_ds_dr=1;
  412.          else hc595_ds_dr=0;

  413.          hc595_sh_dr=0;   
  414.          delay_short(1);
  415.          hc595_sh_dr=1;
  416.          delay_short(1);

  417.          ucTempData=ucTempData<<1;
  418.    }

  419.    ucTempData=ucLedStatusTemp08_01;  
  420.    for(i=0;i<8;i++)
  421.    {
  422.          if(ucTempData>=0x80)hc595_ds_dr=1;
  423.          else hc595_ds_dr=0;

  424.          hc595_sh_dr=0;     
  425.          delay_short(1);
  426.          hc595_sh_dr=1;
  427.          delay_short(1);

  428.          ucTempData=ucTempData<<1;
  429.    }

  430.    hc595_st_dr=0;  
  431.    delay_short(1);
  432.    hc595_st_dr=1;
  433.    delay_short(1);

  434.    hc595_sh_dr=0;   
  435.    hc595_st_dr=0;
  436.    hc595_ds_dr=0;

  437. }


  438. void T0_time(void) interrupt 1
  439. {
  440.   TF0=0;
  441.   TR0=0;

  442.   if(1==GucVoiceStart)
  443.         {

  444.                  if(GuiVoiceCnt!=0)
  445.                  {
  446.                           GuiVoiceCnt--;
  447.                           beep_dr=0;
  448.      }
  449.                  else
  450.                  {
  451.                           beep_dr=1;
  452.                           GucVoiceStart=0;
  453.      }
  454.   }

  455.   key_scan();
  456.   display_drive();  


  457.   TH0=0xfe;  
  458.   TL0=0x0b;
  459.   TR0=1;
  460. }


  461. void delay_short(unsigned int uiDelayShort)
  462. {
  463.    static unsigned int i;  
  464.    for(i=0;i<uiDelayShort;i++);
  465. }



  466. void initial(void)
  467. {
  468.          static unsigned char SucInitialLock=0;
  469.        
  470.          if(0==SucInitialLock)
  471.          {
  472.        SucInitialLock=1;
  473.                  
  474.              key_gnd_dr=0;
  475.        led_dr=0;  
  476.        beep_dr=1;
  477.        TMOD=0x01;
  478.        TH0=0xfe;  
  479.        TL0=0x0b;          
  480.                  
  481.        EA=1;     
  482.        ET0=1;   
  483.        TR0=1;   

  484.    }


  485. }

复制代码

下节预告:三种类型变量的定义与赋值语句。
(未完待续)

作者: 西北狼    时间: 2015-4-2 21:58
如果能附上坚鸿51学习板相关电路图,对照学习,会更好
作者: jianhong_wu    时间: 2015-4-3 15:20
西北狼 发表于 2015-4-2 21:58
如果能附上坚鸿51学习板相关电路图,对照学习,会更好

你这个建议很好。我马上附上原理图。
作者: fkddzm    时间: 2015-4-3 22:16
这个帖子的一楼就有坚鸿51学习板原理图(第三版)。
作者: fkddzm    时间: 2015-4-3 22:18
非常精彩,期待后续。。。
作者: jianhong_wu    时间: 2015-4-7 09:31
本帖最后由 jianhong_wu 于 2015-6-3 18:23 编辑

第十一节:变量的定义与赋值语句。
      写程序到底是写什么?我用七个字概括是:对象之间的行为。假设以下a,b,c,d,e.这些都是对象,那么程序往往是对象之间的以下这些行为:
    (1)把某个数值赋值给对象a
    (2)把对象b赋值给对象a
    (3)把对象b与对象c运算的结果赋值给对象a
    (4)如果对象d等于某个数值,则把某个数值赋值给对象a
    (5)如果对象d等于某个数值,则把对象b赋值给对象a
    (6)如果对象d等于某个数值,则把对象b与对象c运算的结果赋值给对象a
    (7)如果对象d等于对象e,则把某个数值赋值给对象a
    (8)如果对象d等于对象e,则把对象b赋值给对象a
    (9)如果对象d等于对象e,则把对象b与对象c运算的结果赋值给对象a
    (10)...等等,不一一列举。
      从上述可以看出,程序的两个要素是:对象和行为。如果把对象看作是单片机的RAM数据存储器,那么行为就是单片机的ROM程序存储器。如果把对象看作是变量,那么行为就是指令语句。本节标题“变量的定义与赋值语句”,其中“变量的定义”就是对象,“赋值语句”就是行为。
      变量的定义。一个程序最大允许有多少个对象,是由数据存储器RAM的字节数决定的(字节是一种单位,后面章节会讲到)stc89c52rc这个单片机有几百个字节的RAM,但是并不意味着程序就一定要全部占用这些RAM。程序需要占用多少RAM,完全是根据程序的实际情况来决定,需要多少就申请多少。这里的“对象”就是变量。这里的“申请”就是变量的定义。
       定义变量的关键字。常用有3种容量的变量,每种变量的取值范围不一样。第一种是”unsigned char”变量,取值范围从0255,占用RAM一个字节,比喻成一房一厅。第二种是”unsigned int”变量,取值范围从065535,占用RAM两个字节,比喻成两房一厅。第三种是“unsigned long”变量,取值范围从04294967295,占用RAM三个字节,比喻成三房一厅。unsigned char,unsigned intunsigned long都是定义变量的关键字。
       定义变量的语法格式。定义变量的语法格式由3部分组成:关键字,变量名,分号。比如:
       unsigned char a;
      其中unsigned char就是关键字,a就是变量名,分号”;”就是一条语句的结束符号。
      变量名的命名规则。变量名的第一个字符不能是数字,必须是字母或者下划线,字母或者下划线后面可以带数字,一个变量名之间的字符不能带空格。变量名不能跟编译器的关键字重名,不能跟函数名重名。比如:
      unsigned char 3a; //不合法,第一个字符不能是数字。
      unsigned char char; //不合法,char是编译器的关键字。
      unsigned char a b; //不合法,ab是一个变量名,ab的中间不能有空格。
      unsigned char a; //合法。
      unsigned char abc; //合法。
      unsigned char _ab; //合法。
      unsigned char _3ab; //合法。
      unsigned char a123; //合法。
      unsigned char a12ced; //合法。
      定义变量与RAM的内在关系。当我们定义一个变量时,相当于向单片机申请了一个RAM空间。C编译器会自动为这个变量名分配一个RAM空间,每个字节的RAM空间都有一个固定的地址。把每个字节的RAM空间比喻成 房间,这个地址就是房号。地址是纯数字编号,不利于我们记忆,C语言编译器为了降低我们的工作难度,不用我们记每个变量的地址,只需要记住这个变量的名称就可以了。操作某个变量名,就相当于操作到对应地址的RAM空间。变量名与对应地址RAM空间的映射关系是C编译器暗中帮我们做好了。比如:
      unsigned char a;  //a占用一个字节的RAM空间,这个空间的地址由C编译自动分配。
      unsigned char b;  //b占用一个字节的RAM空间,这个空间的地址由C编译自动分配。
      unsigned char c;  //c占用一个字节的RAM空间,这个空间的地址由C编译自动分配。  
      上述a,b,c三个变量名占用一个字节的RAM空间,同时被C编译器分配了3个不同的RAM空间地址。
      赋值语句的含义。赋值语句是行为。把右边对象的内容复制一份给左边对象。 赋值语句有一个很重要的特性,就是覆盖性,左边对象原来的内容会被右边对象复制过来的新内容所覆盖。比如,左边对象是变量a,原来a里面存的数据是3,右边对象是立即数6,执行赋值语句后,把6赋值给了对象a,那么a原来的数据3就被覆盖丢失了,变成了6.
赋值语句的格式。赋值语句的语法格式由4部分组成:左边对象,关键字,右边对象,分号。比如:
      a=b;
      其中a就是左边对象。
      其中“=”就是关键字。写法跟我们平时用的等于号是一样,但是在C语言里不是等于的意思,而是代表赋值的意思。跟等于号是两码事。
      其中b就是右边对象。
      其中分号“;”代表一条语句的结束符。
      赋值语句与ROM的内在关系。赋值语句是行为,凡是程序的行为指令都存储在单片机的ROM区。C编译器会把一条赋值语句翻译成对应的一条或者几条机器码,机器码指令也是以字节为单位的。下载程序的时候,这些机器码就会被下载进单片机的ROM区。比如以下这行赋值语句:
      a=b;
      经过C编译器编译后会生成以字节为单位的机器码。这些机器码记录着这些信息:变量aRAM地址,变量bRAM地址,以及把b变量RAM地址里面的内容赋值到a变量地址里面的RAM空间。
      变量定义的初始化。讲了赋值语句之后,再回过头来讲变量定义的初始化。变量定义之后,等于被C编译器分配了一个RAM空间,那么这个空间里面存储的数据是什么?如果没有刻意给它初始化,那么RAM空间里面存储的数据是不太确定的,是默认的。有些场合,需要在给变量分配RAM空间时就给它一个固定的初始值,这就是变量定义的初始化。变量初始化的语法格式由3部分组成:关键字,变量名赋值,分号。比如:
      unsigned char a=9;
     其中unsigned char就是关键字。
     其中a=9就是变量名赋值。a从被C编译器分配RAM空间那一刻起,就默认是存了9这个数据。
     分号”;”就是一条语句的结束符号。
     接下来练习一个程序实例。直接复制前面章节中第十节的模板程序,只需要在main函数里编写练习代码,编译后,把程序下载进坚鸿51学习板,通过按S1或者S5按键即可在数码管上观察不同的变量数值。其它部分的模板程序代码就不贴出来了,详细的main函数源代码讲解如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;   //定义一个变量a,并且分配了一个字节的RAM空间,里面保存的数据是默认值0.
  6.   unsigned char b;   //定义一个变量b,并且分配了一个字节的RAM空间,里面保存的数据是默认值0.
  7.   unsigned char c;   //定义一个变量c,并且分配了一个字节的RAM空间,里面保存的数据是默认值0.  
  8.   unsigned char d=9; //定义一个变量d,并且分配了一个字节的RAM空间,里面保存的数据被初始化成9.

  9.   b=3;  //把3赋值给变量b,b原来的默认数据是0被覆盖了,此时变量b保存的数值是3
  10.   c=b;  //把右边变量b的内容复制一份赋值给左边的变量c,c原来的默认数据0被覆盖了,此时,c保存的数值跟b的数值一样,都是3.

  11.         
  12.         
  13.   GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  14.   GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  15.   GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  16.   GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示

  17.         
  18. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  19.    while(1)  
  20.    {
  21.                   initial();
  22.       key_service();
  23.       display_service();
  24.    }

  25. }
复制代码

       上坚鸿51学习板观察程序执行的结果:
       变量a的数值是0
       变量b的数值是3
       变量c的数值是3
       变量d的数值是9



       下节预告:两个变量的数据交换。
(未完待续)




作者: f晨星    时间: 2015-4-7 17:53
jianhong_wu 发表于 2015-4-2 13:10
第十节:一个用来学习C语言的模板程序。目前,几乎所有的初学者在学习和上机练习C语言的时候,都是在电脑上 ...

没有注释啊,
作者: jianhong_wu    时间: 2015-4-10 12:09
第十二节:两个变量的数据交换。
为了加深理解赋值语句的一个重要特性“覆盖性”,本节利用赋值语句“=”做一个实验。要求把变量a与b的两个数据进行交换,假设a原来的数据是1,b原来的数据是5,交换数据后,a的数据应该变为5,b的数据应该变为1。
很多初学者刚看到这么简单的题目,会想当然的根据我们日常生活的思路,你把你的东西给我,我把我的东西给你,就两个步骤,so easy!请直接复制第十节的模板程序,仅修改main函数后,main函数源代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=1;   //定义一个变量a,并且分配了一个字节的RAM空间,里面保存的数据被初始化成1.
  6.   unsigned char b=5;   //定义一个变量b,并且分配了一个字节的RAM空间,里面保存的数据被初始化成5.

  7.         b=a; //第一步:为了交换,先把a的数赋值给b。
  8.         a=b; //第二步:为了交换,再把b的数赋值给a。

  9.   GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  10.   GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示

  11.         
  12. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  13.    while(1)  
  14.    {
  15.       initial();
  16.       key_service();
  17.       display_service();
  18.    }

  19. }
复制代码

       上坚鸿51学习板观察程序执行的结果:
       变量a的数值是1。
       变量b的数值是1。
     上述实验结果并没有达到交换数据的目的,为什么?因为赋值语句有一个重要的特性,就是覆盖性。分析如下:
         b=a; //第一步
     分析点评:执行第一步后,此时虽然b得到了a的数据1,但是b原来自己的数据5已经被覆盖丢失了!
         a=b; //第二步
     分析点评:由于b的数据在执行第一步后变成了1,执行第二步后,此时相当于把1赋值给a,并没有5!所以a和b的数据都是1,不能达到交换后“a为5,b为1”的目的。
     上述交换数据的程序宣告失败!怎么办?既然赋值语句具有覆盖性,那么两变量想交换数据,就必须借助第三方寄存,此时只需要多定义一个第三方变量t。main函数源代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=1;   //定义一个变量a,并且分配了一个字节的RAM空间,里面保存的数据被初始化成1.
  6.   unsigned char b=5;   //定义一个变量b,并且分配了一个字节的RAM空间,里面保存的数据被初始化成5.
  7.   unsigned char t;     //定义一个变量t,并且分配了一个字节的RAM空间,里面默认是什么数据不重要。
  8.        
  9.         t=b; //第一步:为了避免b的数据在执行第二步后被覆盖丢失,先把b的数据寄存在第三方变量t那里。
  10.         b=a; //第二步:把a的数赋值给b,b原来的数据虽然被覆盖丢失,但是b在t变量那里有备份,再也不用担心了。
  11.         a=t; //第三步:由于此时b已经获得了a的数据,如果想交换,此时只能把b在t变量里的备份赋值给a,而不能用b。

  12.   GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  13.   GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示

  14.         
  15. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  16.    while(1)  
  17.    {
  18.       initial();
  19.       key_service();
  20.       display_service();
  21.    }

  22. }
复制代码

       上坚鸿51学习板观察程序执行的结果:
       变量a的数值是5。
       变量b的数值是1。
      交换成功!
     
       下节预告:二进制与字节单位。
(未完待续)


作者: jianhong_wu    时间: 2015-4-16 19:49
本帖最后由 jianhong_wu 于 2015-6-3 18:24 编辑

第十三节:二进制与字节单位,以及各种定义变量的取值范围。
      为什么是二进制?人类日常生活明明是十进制的,为何数字电子领域偏要选择二进制?这是由数字硬件电路决定的。人有十个手指头,人可以发出十种不同声音来命名0,1,2,3...9这些数字,人可以肉眼识别十种不同状态的信息,但是数字电路要直接处理十进制却很难,相对来说,二进制就轻松多了。一颗LED灯的亮与灭,一根IO口的输出是高电平和低电平,读取某一个点的电压是高于2V还是低于0.8V,只需要用三极管等元器件就可把处理电路搭建起来,二进制广泛应用在数字电路的存储,通讯和运算等领域,想学好单片机就必须掌握它。
     二进制如何表示成千上万的数值?现在用LED灯的亮和灭来跟大家讲解。
  (11LED灯:
灭   第0种状态
亮   第1种状态
合计:共2种状态。
  (22LED灯挨着:
灭灭   第0种状态
灭亮   第1种状态
亮灭   第2种状态
亮亮   第3种状态
合计:共4种状态。
  (33LED灯挨着:
灭灭灭   第0种状态
灭灭亮   第1种状态
灭亮灭   第2种状态
灭亮亮   第3种状态
亮灭灭   第4种状态
亮灭亮   第5种状态
亮亮灭   第6种状态
亮亮亮   第7种状态
合计:共8种状态。
  (48LED灯挨着:
灭灭灭灭灭灭灭灭   第0种状态
灭灭灭灭灭灭灭亮   第1种状态
......
亮亮亮亮亮亮亮灭   第254种状态
亮亮亮亮亮亮亮亮   第255种状态
合计:共256种状态。
  (516LED灯挨着:
灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭   第0种状态
灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭亮   第1种状态
......
亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮灭   第65534种状态
亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮   第65535种状态
合计:共65536种状态。
  (632LED灯挨着:
灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭   
0种状态
灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭亮   
1种状态
......
亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮灭   
4294967294种状态
亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮   
4294967295种状态
合计:共4294967296种状态。

       什么是位?以上一个LED灯就代表一位,8LED灯就代表8位。一个变量的位数越大就意味着这个变量的取值范围越大。一个单片机的位数越多大,就说明这个单片机一次处理的数据范围就越大,意味着运算和处理速度就越快。我们日常所说的8位单片机,32位单片机,就是这个位的概念。为什么32位的单片机比8位单片机的处理和运算能力强,就是这个原因。位的英文名是用bit来表示。
什么是字节?字节是计算机很重要的一个基本单位,一个字节有8位。8LED灯挨着能代表多少种状态,就意味着一个字节的数据范围有多大。从上面举的例子中,我们知道8LED灯挨着,能表示从0255种状态,所以一个字节的取值范围就是从0255
       各种定义变量的取值范围。前面第十一节讲了常用变量的定义有3种,unsigned charunsigned int ,unsigned long。但是没有讲到它们的取值范围,现在讲到二进制和字节了,可以回过头来跟大家讲讲这3种变量的取值范围,而且很重要。
unsigned char的变量占用1个字节RAM,共8位,根据前面LED灯的例子,取值范围是从0255
Unsigned int的变量占用2个字节RAM,共16位,根据前面LED灯的例子,取值范围是从065535
Unsigned long的变量占用4个字节RAM,共32位,根据前面LED灯的例子,取值范围是从04294967295
       现在我们编写一个程序来验证unsigned charunsigned int的取值范围。定义两个unsigned char变量aba赋值255b赋值256255256恰好处于unsigned char的取值边界。另外再定义两个unsigned int变量cdc赋值65535d赋值655366553565536恰好处于unsigned int的取值边界。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;   //定义一个变量a,并且分配了1个字节的RAM空间。
  6.   unsigned char b;   //定义一个变量b,并且分配了1个字节的RAM空间。
  7.   unsigned int c;    //定义一个变量c,并且分配了2个字节的RAM空间。
  8.   unsigned int d;    //定义一个变量d,并且分配了2个字节的RAM空间。

  9.         a=255;//把255赋值给变量a,a此时会是什么数?会超范围溢出吗?
  10.         b=256;//把256赋值给变量b,b此时会是什么数?会超范围溢出吗?
  11.         c=65535;//把65535赋值给变量c,c此时会是什么数?会超范围溢出吗?
  12.         d=65536;//把65536赋值给变量d,d此时会是什么数?会超范围溢出吗?
  13.         
  14.         
  15.   GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  16.   GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  17.   GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  18.   GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示

  19.         
  20. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  21.    while(1)  
  22.    {
  23.       initial();
  24.       key_service();
  25.       display_service();
  26.    }

  27. }
复制代码

    上坚鸿51学习板观察程序执行的结果如下:
    unsigned char变量a的数值是255。
     unsigned char变量b的数值是0。
    unsigned int  变量c的数值是65535。
     unsigned int  变量d的数值是0。
     通过以上现象分析,我们知道unsigned char变量最大能取值到255,如果非要赋值256就会超出范围溢出后变成了0。而unsigned int变量最大能取值到65535,如果非要赋值65536就会超出范围溢出后变成了0。
    多说一句,至于unsigned long的取值范围,大家暂时不用尝试,因为我现在给大家用的模板程序能观察的最大变量是16位的unsigned int类型,暂时不支持32位的unsigned long类型。
       下节预告:二进制与十六进制。
(未完待续)




作者: 大耳怪    时间: 2015-4-18 13:49
鸿哥基础篇,写程序模块化教学可以吗。谢谢
作者: 西北狼    时间: 2015-4-18 19:47
继续学习   
作者: jianhong_wu    时间: 2015-4-19 01:39
大耳怪 发表于 2015-4-18 13:49
鸿哥基础篇,写程序模块化教学可以吗。谢谢

暂时不会讲这些。以后再看情况。
作者: jianhong_wu    时间: 2015-4-19 11:28
本帖最后由 jianhong_wu 于 2015-6-3 18:26 编辑

第十四节:二进制与十六进制。
       C51编译器并不支持二进制的书写格式,即使添加某个头文件后能支持二进制的书写格式,二进制的书写还是有个弊端,就是数字太多太长了,写起来非常费劲不方便,怎么办?解决办法就是用十六进制。十六进制是二进制的缩写,之所以称它为二进制的缩写,是因为它们的转换关系非常简单直观,不需要借助计算器即可相互转换。
      何谓十六进制?欲搞清楚这个问题,还得先从十进制说起。所谓十进制,就是用一位字符可以表示从09这十个数字。所谓二进制,就是用一位字符可以表示从01这二个数字。所谓十六进制,当然也就是用一位字符可以表示从015这十六个数字。但是马上就会面临一个问题,十六进制的10156个数其实是有两位字符组成的,并不是一位呀?于是C语言用一个字符A,B,C,D,E,F分别替代10,11,12,13,14,156个数,10前面的09还是跟十进制的字符一致。A,B,C,D,E,F也可以用小写a,b,c,d,e,f来替代,不区分大小写。
     前面提到了十六进制是二进制的缩写,它们的转换关系非常简单直观,每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,可以在左边补上20变成00101001,然后把每4位字符转成1个十六进制的字符。左边高40010对应十六进制的2,右边低41001对应十六进制的9,所以合起来最终的十六进制是29
        十六进制的标准书写格式。刚才提到的十六进制29,在C语言里不能直接写29,否则就跟十进制的写法混淆了。为了把十六进制和十进制的书写格式进行区分,C语言规定凡是十六进制必须加一个数字0和一个字母x作为前缀,也就是十六进制必须以0x作为前缀,刚才的十六进制29就应该写成0x29。凡是不加前缀的就默认为十进制。
       现在我们编写一个程序来观察十六进制和二进制的关系,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:


  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;    //定义一个变量a,并且分配了1个字节的RAM空间。
  6.   unsigned char b;    //定义一个变量b,并且分配了1个字节的RAM空间。
  7.   unsigned char c;    //定义一个变量c,并且分配了1个字节的RAM空间。
  8.   unsigned char d;    //定义一个变量d,并且分配了1个字节的RAM空间。

  9.   a=0x06;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。
  10.   b=0x0A;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。
  11.   c=0x0e;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。
  12.   d=0x2C;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。
  13.         
  14.         
  15.   GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  16.   GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  17.   GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  18.   GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示

  19.         
  20. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  21.    while(1)  
  22.    {
  23.       initial();
  24.       key_service();
  25.       display_service();
  26.    }

  27. }
复制代码


        如何在坚鸿51学习板上观察十六进制和二进制?S1S5按键是切换窗口按键。按住S9按键不松手,就可以观察当前窗口数据的十六进制格式了。松开S9按键就是当前窗口的十进制数据格式。而坚鸿51学习板右上角的16LED灯就代表了当前窗口的二进制,亮的代表1,灭的代表0
       上坚鸿51学习板观察程序执行的结果如下:
                                   十六进制         二进制         十进制     
unsigned char变量a      6                   0000 0110       6
unsigned char变量b      A                   0000 1010       10
unsigned char变量c      E                   0000 1110       14
unsigned char变量d      2C                 0010 1100       44

        多说一句,在程序里,可以用十六进制,也可以用十进制,比如:
d=0x2Cd=44的含义是一样的。十六进制的0x2C和十进制的44最终都会被C51编译器翻译成二进制00101100
       下节预告:十进制与十六进制。
(未完待续)






作者: jianhong_wu    时间: 2015-4-23 23:10
本帖最后由 jianhong_wu 于 2015-6-3 18:27 编辑

第十五节:十进制与十六进制。
       十六进制是二进制的缩写形式,而C语言程序里只用了十进制和十六进制这两种书写格式。它们各有什么应用特点?十六进制方便人理解机器,通常应用在配置寄存器,底层通讯驱动,底层IO口驱动,以及数据的移位,转换和合并等场合。而十进制方便人理解值的大小,在应用层经常用总之,进制数据的表现形式而已。
    十进制与十六进制如何相互转换?其实很多教科书上有介绍它们之间如何通过手工计算进行转换的方法。但是实际应用中,我从来没有用过这种手工计算方法,我用的方法是最简单直接的,就是借助电脑自带的计算器进行转换即可。现在把这种方法介绍给大家。
    第一步:点击电脑左下角“开始”菜单,在下拉菜单中把鼠标移动到“所有程序”,在下拉菜单中把鼠标移动到“附件”,在下拉菜单中点击“计算器”,此时会弹出“计算器” 的窗口。
    第二步:点击计算器窗口上面的“查看”菜单,在下拉菜单中点击“科学型”,此时“计算器” 的窗口会变长。按键上方出现“十六进制”,“十进制”,“八进制”,“二进制”等单选项。
    第三步:在按键上方“十六进制”,“十进制”,“八进制”,“二进制”等单选项中,单击所要切换到的进制,然后按数字按键输入数据。输完数据后,再单击切换到所要转换的进制中,即可完成各进制的数据切换。注意,在切换到“十六进制”的时候,在右边“四字”,“双字”,“单字”,“字节”中选中“四字”。
    第四步:把十进制转换到十六进制的方法如下:单击切换到“十进制”,然后按数字按键输入数据。输完数据后,再单击切换到“十六进制”,即可完成进制的转换。比如输入十进制的“230”,切换到十六进制就变成了“E6”。
    第五步:把十六进制转换到十进制的方法如下:单击切换到“十六进制”,然后按数字按键输入数据。输完数据后,再单击切换到“十进制”,即可完成进制的转换。比如输入十六进制的“AC”,切换到十进制就变成了“172”。
现在我们编写一个程序来观察十进制和十六进制的关系,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;    //定义一个变量a,并且分配了1个字节的RAM空间。
  6.   unsigned char b;    //定义一个变量b,并且分配了1个字节的RAM空间。

  7.   a=230;    //把十进制的230赋值给变量a,在坚鸿51学习板上观察一下它的十六进制是不是E6。
  8.   b=0xAC;   //把十六进制的AC赋值给变量b,在坚鸿51学习板上观察一下它的十进制是不是172。
  9.          
  10.   GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  11.   GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示

  12.         
  13. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  14.    while(1)  
  15.    {
  16.       initial();
  17.       key_service();
  18.       display_service();
  19.    }

  20. }
复制代码


如何在坚鸿51学习板上观察十进制和十六进制?S1S5按键是切换窗口按键。按住S9按键不松手,就可以观察当前窗口数据的十六进制格式了。松开S9按键就是当前窗口的十进制数据格式。而坚鸿51学习板右上角的16LED灯就代表了当前窗口的二进制,亮的代表1,灭的代表0
上坚鸿51学习板观察程序执行的结果如下:
                      十六进制        十进制     
unsigned char变量a      E6            230
unsigned char变量b      AC            172

     下节预告:判断语句“if”和等于关系符“==”。
(未完待续)






作者: jianhong_wu    时间: 2015-5-2 10:42
本帖最后由 jianhong_wu 于 2015-6-3 18:28 编辑

第十六节:加法运算的5种常用格式。
       根据上一节的预告,本来这节应该讲判断语句的,但是考虑到后续章节的连贯性,决定先讲运算语法。
       在讲运算语法之前,先讲一个我在前面忘了讲的知识点,那就是注释语句。何谓注释语句?在我前面一些章节的main函数中,经观察,发现一个规律,凡是中文解说的文字,要么前面有符号”//”,要么就是被包含在“/*”和”*/”之间。符号“//”和“/*  */”都是注释语句。注释语句是用来添加文字备忘,方便程序员阅读记忆的。在注释语句里的文字是不会被编译器翻译成机器码的,也就是说即使注释里面的文字再多,也不会增加单片机的程序容量,它是被编译器过滤忽略的,仅仅方便程序员做备注文字而已。
符号“//”和“/*  */”都是注释语句,但应用方面有点小差异。符号“//”是用来注释一行文字。而“/*  */”往往是用来注释一段文字,当然“/*  */”也可以注释一行文字。但是符号“//”仅仅能注释一行文字,却不能注释一段文字。
       讲完注释语句,继续回到本节正题。单片机本身具备了简单的加减乘除运算能力,我们只需要通过C语言调用相关的运算语法,即可指示单片机按我们的要求进行简单的运算。至于内部具体的运算细节我们可以不管,除非是涉及到大数据的运算才需要我们额外编写算法。请先看以下的加法语法格式:
       “保存变量”=“加数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变量里面的数值是10y变量里面的数值是6,执行上述语句后,保存变量c变成了16。而变量xy则保持不变,x还是10y还是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是保存变量,xy都是变量。这类语句有一个特点,具备了自加功能,可以更改自己本身自己的数值。比如原来保存变量e的数值是2,执行“e=e+x;”语句后,e变成了12,接着再执行完“e=e+y;”语句后,e最后变成了18

       现在编写一个程序来练习上述5种格式的加法语句,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:


  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;       //定义一个变量a,并且分配了1个字节的RAM空间。
  6.   unsigned char b;       //定义一个变量b,并且分配了1个字节的RAM空间。
  7.   unsigned char c;       //定义一个变量c,并且分配了1个字节的RAM空间。
  8.   unsigned char d=2;       //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为2.
  9.   unsigned char e=2;       //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为2.
  10.         
  11.   unsigned char x=10;    //定义一个变量x,并且分配了1个字节的RAM空间。初始化默认为10.
  12.   unsigned char y=6;     //定义一个变量y,并且分配了1个字节的RAM空间。初始化默认为6.        

  13.   //第1种:“加数1”是常量,“加数2”是常量。
  14.         a=3+15;
  15.         
  16.         
  17.         //第2种:“加数1”是变量,“加数2”是常量。
  18.         b=x+15;
  19.         
  20.         
  21.         //第3种:“加数1”是变量,“加数2”是变量。
  22.   c=x+y;
  23.         
  24.         
  25.   //第4种:“加数1”是保存变量本身,“加数2”是常量。
  26.   d=d+18;
  27.   d=d+7;
  28.         
  29.         
  30.         //第5种:“加数1”是保存变量本身,“加数2”是变量。
  31.         e=e+x;
  32.   e=e+y;


  33.   GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  34.   GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  35.   GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  36.   GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示
  37.   GuiWdData4=e;   //把变量e这个数值放到窗口变量4里面显示

  38.         
  39. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  40.    while(1)  
  41.    {
  42.       initial();
  43.       key_service();
  44.       display_service();
  45.    }

  46. }
复制代码

        如何在坚鸿51学习板上观察a,b,c,d,e5个变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。上坚鸿51学习板观察程序执行的结果如下:
变量a为18
变量b为25
变量c为16
变量d为27
变量e为18

     下节预告:加法的连写和自加运算的简写。
(未完待续)




作者: 西北狼    时间: 2015-5-4 13:02
学习了,赞一个
作者: jianhong_wu    时间: 2015-5-10 07:53
本帖最后由 jianhong_wu 于 2015-6-3 18:29 编辑

第十七节:连加以及自加运算的简写。
       上一节我列举的加法例子中,右边的加数个数都是两个。实际上,C语言规则没有限制加数的个数,它的通用格式如下:
       “保存变量”=“加数1+“加数2+...+“加数N”;
       当右边的加数个数超过两个的时候,这种情况就是我所说的“连加”,每个加数的属性没有限定,可以是常量,也可以是变量。比如:
a=1+69+102;     //加数全部是常量。
b=q+x+y+k+r;    //加数全部是变量。
c=3+x+y+5+k;   //加数有的是常量,有的是变量。
        连加的运行顺序是,赋值符号“=”右边的加数挨个相加,把每一次的运算结果放在一个临时的隐蔽变量里,这个隐蔽的变量我们看不到,是单片机系统内部参与运算时的专用寄存器,等右边所有的加数连加的计算结果出来后,再把这个隐蔽变量所保存的计算结果赋值给左边的“保存变量”。
        讲完了连加的格式,接着讲自加的简写。何谓自加?当右边的加数只要其中有一个是“保存变量”本身时,这种情况就是我所说的“自加”。比如:
“保存变量”=“保存变量”+“加数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;
        这些例子都是很常规的自加简写,再跟大家讲一种很常用的特殊简写。当右边只有2个加数,当一个加数是“保存变量”,另一个是常数1时,格式如下:
“保存变量”=“保存变量”+1
        这时候,可以把上述格式简写成如下两种格式:
“保存变量”++;
++“保存变量”;
        这两种格式也是俗称的“自加1”操作。比如:
g++;  //相当于g=g+1或者g+=1;
++h;  //相当于h=h+1或者h+=1;
        也就是说自加1符号“++”可以在变量的左边,也可以在变量的右边,它们在这里本质是一样的,没有差别。当然,如果是在循环条件语句中,这时自加1符号“++”在左边还是在右边是有一点点微弱的差别,这方面的内容以后再讲。

        现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:


  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;       //定义一个变量a,并且分配了1个字节的RAM空间。
  6.   unsigned char b;       //定义一个变量b,并且分配了1个字节的RAM空间。
  7.   unsigned char c;       //定义一个变量c,并且分配了1个字节的RAM空间。
  8.   unsigned char d=5;       //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为5.
  9.   unsigned char e=5;       //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为5.
  10.   unsigned char f=5;       //定义一个变量f,并且分配了1个字节的RAM空间。初始化默认为5.
  11.         unsigned char g=5;       //定义一个变量g,并且分配了1个字节的RAM空间。初始化默认为5.        
  12.   unsigned char h=5;       //定义一个变量h,并且分配了1个字节的RAM空间。初始化默认为5.

  13.         
  14.   unsigned char q=1;    //定义一个变量q,并且分配了1个字节的RAM空间。初始化默认为1.
  15.   unsigned char x=3;    //定义一个变量x,并且分配了1个字节的RAM空间。初始化默认为3.
  16.   unsigned char y=6;     //定义一个变量y,并且分配了1个字节的RAM空间。初始化默认为6.        
  17.   unsigned char k=2;     //定义一个变量k,并且分配了1个字节的RAM空间。初始化默认为2.
  18.   unsigned char r=8;     //定义一个变量r,并且分配了1个字节的RAM空间。初始化默认为8.
  19.         
  20.         
  21.          //第1个知识点:连加。
  22.    a=1+69+102;     //加数全部是常量。a的结果为:172。
  23.    b=q+x+y+k+r;    //加数全部是变量。b的结果为:20。
  24.    c=3+x+y+5+k;   //加数有的是常量,有的是变量。c的结果为:19。

  25.          //第2个知识点:自加的常规格式。
  26.    d+=6;  //相当于d=d+6;  d的结果为:11。
  27.    e+=x;  //相当于e=e+x;  e的结果为:8。
  28.    f+=18+y+k; //相当于f=f+18+y+k;  f的结果为:31。
  29.          
  30.          
  31.          //第3个知识点:自加的特殊格式。
  32.    g++;  //相当于g=g+1或者g+=1;  g的结果为:6。
  33.    ++h;  //相当于h=h+1或者h+=1;  h的结果为:6。




  34.    GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  35.    GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  36.    GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  37.    GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示
  38.    GuiWdData4=e;   //把变量e这个数值放到窗口变量4里面显示
  39.    GuiWdData5=f;   //把变量f这个数值放到窗口变量5里面显示
  40.    GuiWdData6=g;   //把变量g这个数值放到窗口变量6里面显示
  41.    GuiWdData7=h;   //把变量h这个数值放到窗口变量7里面显示



  42.         
  43. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  44.    while(1)  
  45.    {
  46.       initial();
  47.       key_service();
  48.       display_service();
  49.    }

  50. }
复制代码

        如何在坚鸿51学习板上观察a,b,c,d,e,f,g,h8个变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。上坚鸿51学习板观察程序执行的结果如下:

变量a为172
变量b为20
变量c为19
变量d为11
变量e为8
变量f为31
变量g为6
变量h为6
     下节预告:加法的溢出和优先级
(未完待续)





作者: jianhong_wu    时间: 2015-5-16 23:41
本帖最后由 jianhong_wu 于 2015-6-3 18:29 编辑

第十八节:加法运算的溢出。
       我前面介绍的三种数据类型unsigned char ,unsigned int ,unsigned long,都是有最大范围限制的,它们最大范围分别是255,65535,4294967295,如果加法运算的结果超过了参与运算的变量本身,会出现什么结果,有什么规律,这就是本节要讲解的溢出问题。
    (1)何谓溢出?比如以下例子:
unsigned char a;
a=0x8536;
分析:
因为aunsigned char变量,位数是8位,也就是1个字节,而0x853616位,2个字节,这种情况下,把0x8536赋值给单字节变量a,变量a只能接收到最低位的一个字节0x36,而高位字节的0x85就被丢失了,这个就是本节所说的“溢出”了。
    (2)再看一个例子如下:
unsigned char b=0xff;
b=b+1;
分析:
b默认值是0xff,再加1后,变成了0x0100保存在一个隐藏的中间变量,然后再把这个中间变量赋值给单字节变量bb只能接收到低位字节0x00,所以运算后b的数值由于溢出变成了0x00
    (3)再看一个例子如下:
unsigned char c=0xff;
c=c+2;
分析:
c默认值是0xff,再加2后,变成了0x0101保存在一个隐藏中间变量,然后再把这个中间变量赋值给单字节变量cc只能接收到低位字节0x01,所以运算后c的数值由于溢出变成了0x01
    (4)再看一个例子如下:
Unsigned int d=0xfffe;
d=d+5;
分析:
d默认值是0xfffe,再加5后,变成了0x10003保存在一个隐藏中间变量,由于这个隐藏的中间变量是unsigned int类型,只能保存2个字节的数据,所以在中间变量这个环节就溢出了,实际上隐藏的中间变量只保存了0x0003,然后再把这个中间变量赋值给双字节变量dd理所当然也是0x0003
    (5)再看一个例子如下:
unsigned long e=0xfffffffe;
e=e+5;
分析:
e默认值是0xfffffffe,再加5后,变成了0x100000003保存在一个隐藏中间变量,由于这个隐藏的中间变量是unsigned long类型,只能保存4个字节的数据,所以在中间变量这个环节就溢出了,实际上隐藏的中间变量只保存了0x00000003,然后再把这个中间变量赋值给4字节变量ee理所当然也是0x00000003
       现在编写一个程序来练习上述前面4个例子,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.    unsigned char a;
  6.          unsigned char b=0xff;
  7.          unsigned char c=0xff;
  8.           unsigned int  d=0xfffe;

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




  14.    GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  15.    GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  16.    GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  17.    GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示





  18.         
  19. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  20.    while(1)  
  21.    {
  22.       initial();
  23.       key_service();
  24.       display_service();
  25.    }

  26. }
复制代码

        如何在坚鸿51学习板上观察a,b,c,d4个变量的十六进制?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量,只要按住S9按键不放,此时显示的就是该变量的十六进制。上坚鸿51学习板观察程序执行的结果如下:

变量a为0x36。
变量b为0x00。
变量c为0x01。
变量d为0x0003。

        这一节提到了一个“隐藏中间变量”的概念,这个神秘的“隐藏中间变量”到底是unsigned int类型还是unsigned long类型?有什么规律?如果运算中存在多种不同变量类型该怎么办,实际应用中有解决的办法吗?预知详情,请看一节内容。
       下节预告:加法运算中,神秘中间变量的类型以及解决“掺杂多种变量类型”的办法。
(未完待续)





作者: jianhong_wu    时间: 2015-5-22 05:45
本帖最后由 jianhong_wu 于 2015-5-22 05:48 编辑

第十九节:加法运算中,神秘中间变量的类型以及解决“掺杂多种变量类型”的办法。
       在开始本节内容之前,先告诉大家前面第十一节内容有一处笔误,unsigned long的数据长度应该是4个字节,而不是3个字节。
上一节提到了一个“隐藏中间变量”的概念,两个加数相加,其结果先保存在一个“隐藏中间变量”里,然后再把这个“隐藏中间变量”赋值给左边的“保存变量”。这里的“隐藏中间变量”到底是unsigned int类型还是unsigned long类型?为了研究它的规律,在keil自带的C51编译环境下,我专门编写了好几个测试程序来观察实际运行的结果。
       “保存变量”=“加数1+“加数2;
       我测试的程序如下:
    (1)“保存变量”为 unsigned int类型,“加数1”为unsigned char类型,“加数2”为unsigned char 类型。
  1. unsigned int a;
  2. unsigned char x=0x12;
  3. unsigned char y=0xfe;
  4. a=x+y;
复制代码

运行结果:a等于0x0110
分析过程:两个char类型的数相加,当运算结果大于char本身时,并没有发生溢出现象,int型的“保存变量”a最终得到了完整的结果。
初步结论:这种情况,“隐藏中间变量”应该为unsigned int 类型。
    (2)“保存变量”为 unsigned long类型,“加数1”为unsigned int类型,“加数2”为unsigned char 类型。
  1. unsigned long a;
  2. unsigned int x=0xfffe;
  3. unsigned char y=0x12;
  4. a=x+y;
复制代码

运行结果:a等于十六进制的0x0010
分析过程:一个int类型的数与一个char类型的数相加,当运算结果大于其中最大加数int类型本身时,本来以为运算结果应该是long类型的0x00010010,结果是int类型的0x0010,发生了溢出现象。
初步结论:这种情况,“隐藏中间变量”应该为unsigned int 类型。
     (3)“保存变量”为 unsigned long类型,“加数1”与“加数2”都为常量。
  1. unsigned long a;
  2. a=50000+50000;
复制代码

运行结果:a等于100000
分析过程:int的最大数据范围是65535,而两个常量相加,其结果超过了65535还能完整保存下来。
初步结论:这种情况,“隐藏中间变量”等于左边的“保存变量”类型。
     (4)“保存变量”为 unsigned long类型,“加数1”为unsigned int类型,“加数2”为常量。
  1. unsigned long a;
  2. unsigned long b;
  3. unsigned  int x=50000;
  4. a=x+30000;
  5. b=x+50000;
复制代码


运行结果:a等于14464,b等于100000
分析过程:本来以为a应该等于80000的,结果是14464发生了溢出。而b100000没有发生溢出。
初步结论:这是一种很怪异的现象,为什么同样的类型,因为常量的不同,一个发生了溢出,另外一个没有发生溢出?这时的“隐藏中间变量”到底是int类型还是long类型我无法下结论。
       经过上述简单的测试,我发现规律是模糊的,模糊的规律就不能成为规律。如果真要按这种思路研究下去,那真是没完没了,因为还有很多情况要研究,当超过3个以上加数相加,同时存在long,int,char,常量这4种类型时又是什么规律?在不同的C编译器里又会是什么现象?即使把所有情况的规律摸清楚了又能怎么样,因为那么繁杂很容易忘记导致出错。有什么解决的办法吗?现在跟大家分享一种很简单的解决办法。
       当遇到有争议的问题时,还有一种解决思路是:与其参与争议越陷越深,还不如想办法及时抽身绕开争议。在上述运算中,只要经过简单的变换,让它们遵循“所有参与运算的变量,左边的变量类型必须跟右边的保存变量类型一致”这个原则,那么就不会存在这些争议了。
     (5)比如上述第(4)个例子,其转换方法如下:
  1. unsigned long a;
  2. unsigned long b;
  3. unsigned  int x=50000;
  4. Unsigned  long t;  //多增加一个long类型的变量,用来变换类型
  5. t=0;  //把变量的高位和低位全部清零。
  6. t=x;   //把x的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
  7. a=t+30000;
  8. b=t+50000;
复制代码

运行结果:a等于80000,b等于100000。都没有发生溢出。
      (6)比如上述第(2)个例子,其转换方法如下:
  1. unsigned long a;
  2. unsigned int x=0xfffe;
  3. unsigned char y=0x12;
  4. unsigned  long t;  //多增加一个long类型的变量,用来变换类型。
  5. unsigned  long r;  //多增加一个long类型的变量,用来变换类型。
  6. t=0;//把变量的高位和低位全部清零。
  7. t=x;   //把x的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
  8. r=0;  //把变量的高位和低位全部清零。
  9. r=y   //把y的数值先放到一个long类型的变量里,让”加数”跟”保存变量”类型一致。
  10. a=t+r;
复制代码

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

        下节预告:减法运算的常见格式。
(未完待续)




作者: 西北狼    时间: 2015-5-23 19:20
                  
作者: fkddzm    时间: 2015-5-26 20:57
顶一下。。。。
作者: jianhong_wu    时间: 2015-5-31 07:44
第二十节:减法运算的5种常见格式。
      请先看以下的减法语法格式:
       “保存变量”=“减数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。
       现在编写一个程序来练习上述5种格式的减法语句,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;       //定义一个变量a,并且分配了1个字节的RAM空间。
  6.   unsigned char b;       //定义一个变量b,并且分配了1个字节的RAM空间。
  7.   unsigned char c;       //定义一个变量c,并且分配了1个字节的RAM空间。
  8.   unsigned char d=18;       //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为18.
  9.   unsigned char e=28;       //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为28.
  10.         
  11.   unsigned char x=15;    //定义一个变量x,并且分配了1个字节的RAM空间。初始化默认为15.
  12.   unsigned char y=6;     //定义一个变量y,并且分配了1个字节的RAM空间。初始化默认为6.        

  13.   //第1种:“减数1”是常量,“减数2”是常量。
  14.   a=15-3;
  15.         
  16.         
  17.   //第2种:“减数1”是变量,“减数2”是常量。
  18.   b=x-10;
  19.         
  20.         
  21.   //第3种:“减数1”是变量,“减数2”是变量。
  22.   c=x-y;
  23.         
  24.         
  25.   //第4种:“减数1”是保存变量本身,“减数2”是常量。
  26.   d=d-2;
  27.   d=d-7;
  28.         
  29.         
  30.   //第5种:“减数1”是保存变量本身,“减数2”是变量。
  31.   e=e-x;
  32.   e=e-y;


  33.   GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  34.   GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  35.   GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  36.   GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示
  37.   GuiWdData4=e;   //把变量e这个数值放到窗口变量4里面显示

  38.         
  39. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  40.    while(1)  
  41.    {
  42.       initial();
  43.       key_service();
  44.       display_service();
  45.    }

  46. }
复制代码

        如何在坚鸿51学习板上观察a,b,c,d,e这5个变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。上坚鸿51学习板观察程序执行的结果如下:
变量a为12。
变量b为5。
变量c为9。
变量d为9。
变量e为7。
     下节预告:减法的连写和自减运算的简写。
(未完待续)


作者: jianhong_wu    时间: 2015-6-8 07:48
第二十一节:减法的连写和自减运算的简写。
     连减。上一节我列举的减法例子中,右边的减数只有一个。实际上,C语言规则没有限制减数的个数,它的通用格式如下:
      “保存变量”=“被减数”-“减数1”-“减数2”-...-“减数N”;
      被减数与减数的属性。当右边的减数个数总共超过1个的时候,就是我所说的“连减”。被减数和减数的属性没有限定,可以是常量,也可以是变量。比如:
     a=68-3-15;     //被减数和减数全部是常量。
     b=q-x-y-k;    //被减数和减数全部是变量。
     c=63-x-5-k;   //被减数和减数,有的是常量,有的是变量。
       连减的运行顺序。赋值符号“=”右边的被减数挨个与减数相减,每一次的运算结果都放在一个临时的隐蔽变量里,这个隐蔽的变量我们看不到,是单片机系统内部参与运算时的专用寄存器,当与所有减数相减的计算结果出来后,再把隐蔽变量所保存的计算结果赋值给左边的“保存变量”。
       自减。当被减数是“保存变量”本身时,这种情况就是我所说的“自减”。比如:
“保存变量”=“保存变量”-“减数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符号“--”在左边还是在右边是有一点点微弱的差别,这方面的内容以后再讲。
        上机练习。现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;       //定义一个变量a,并且分配了1个字节的RAM空间。
  6.   unsigned char b;       //定义一个变量b,并且分配了1个字节的RAM空间。
  7.   unsigned char c;       //定义一个变量c,并且分配了1个字节的RAM空间。
  8.   unsigned char d=65;       //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为65.
  9.   unsigned char e=38;       //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为38.
  10.   unsigned char f=29;       //定义一个变量f,并且分配了1个字节的RAM空间。初始化默认为29.
  11.   unsigned char g=5;       //定义一个变量g,并且分配了1个字节的RAM空间。初始化默认为5.        
  12.   unsigned char h=5;       //定义一个变量h,并且分配了1个字节的RAM空间。初始化默认为5.

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

  18.   //第1个知识点:连减。
  19.   a=68-3-15;//被减数和减数全部是常量。a的结果为:50。
  20.   b=q-x-y-k;//被减数和减数全部是变量。b的结果为:39。
  21.   c=63-x-5-k;//被减数和减数,有的是常量,有的是变量。c的结果为:53。
  22.        
  23.        
  24.   //第2个知识点:自减的常规格式。   
  25.   d-=6;//相当于d=d-6;  d的结果为:59。
  26.   e-=x;//相当于e=e-x;  e的结果为:35。
  27.   f-=18-y-k;//相当于f=f-(18-y-k);  f的结果为:19。
  28.        
  29.        
  30.   //第3个知识点:自减的特殊格式。
  31.   g--;//相当于g=g-1或者g-=1;  g的结果为:4。
  32.   --h;//相当于h=h-1或者h-=1;  d的结果为:4。
  33.          

  34.    GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  35.    GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  36.    GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  37.    GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示
  38.    GuiWdData4=e;   //把变量e这个数值放到窗口变量4里面显示
  39.    GuiWdData5=f;   //把变量f这个数值放到窗口变量5里面显示
  40.    GuiWdData6=g;   //把变量g这个数值放到窗口变量6里面显示
  41.    GuiWdData7=h;   //把变量h这个数值放到窗口变量7里面显示



  42.         
  43. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  44.    while(1)  
  45.    {
  46.       initial();
  47.       key_service();
  48.       display_service();
  49.    }

  50. }
复制代码

      查看运算结果的方法。如何在坚鸿51学习板上观察a,b,c,d,e,f,g,h这8个变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。上坚鸿51学习板观察程序执行的结果如下:
      变量a为50。
      变量b为39。
      变量c为53。
      变量d为59。
      变量e为35。
      变量f为19。
      变量g为4。
      变量h为4。
       下节预告:减法运算的溢出。
(未完待续)


作者: jianhong_wu    时间: 2015-6-17 21:27
本帖最后由 jianhong_wu 于 2015-6-17 21:33 编辑

第二十二节:减法运算的溢出。
       在开始本章节之前,先纠正一下前面第17节内容的一个小bug。我原文中写道:
       “保存变量”+=“加数1”+“加数2”+...+“加数N”;
相当于:
       “保存变量”=“保存变量”+“加数1”+“加数2”+...+“加数N”;
        当时我没有考虑到优先级,漏了一个括号,修改后,
相当于:
       “保存变量”=“保存变量”+(“加数1”+“加数2”+...+“加数);
         这样才算比较准确。同理,我后面所举的例子:
         f+=18+y+k; //相当于f=f+18+y+k;
在注释中也漏了一个括号,应该是:
         f+=18+y+k; //相当于f=f+(18+y+k);
         上述多一个括号或者少一个括号虽然看似不影响运算结果,但是运算顺序是有点不一样的。


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

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

分析:
左边的“保存变量”c的数据长度是2个字节16位,c=0-1可以看成是十六进制的c=0x0000-0x0001。由于0x00000x0001小,所以假想一下需要向高位借位,借位后成了c=0x10000-0x0001。所以c的最终结果是0xffff(十进制是65535)。根据”假想借位”这个规律,如果是d也是unsigned  int 类型,那么d=2-5自然就相当于b=0x10002-0x0005,运算结果b等于0xfffd(十进制是65533)
          为了验证上述抛出的”假想借位”,现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:


  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         

  5.         
  6.         
  7.   unsigned char a;       //定义一个变量a,并且分配了1个字节的RAM空间。
  8.   unsigned char b;       //定义一个变量b,并且分配了1个字节的RAM空间。
  9.   unsigned int c;        //定义一个变量c,并且分配了2个字节的RAM空间。
  10.   unsigned int d;        //定义一个变量d,并且分配了2个字节的RAM空间。

  11.    //第一个例子,针对a与b都是unsigned char类型数据。     
  12.    a=0-1;  
  13.    b=2-5;
  14.         
  15.         
  16.         //第二个例子,针对c与d都是unsigned int类型的数据。
  17.    c=0-1;
  18.    d=2-5;        


  19.    GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  20.    GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  21.    GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  22.    GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示
  23.         
  24. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  25.    while(1)  
  26.    {
  27.                   initial();
  28.       key_service();
  29.       display_service();
  30.    }

  31. }
复制代码


          查看运算结果的方法。如何在坚鸿51学习板上观察a,b,c,d这4个变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。上坚鸿51学习板观察程序执行的结果如下:

          变量a为0xff(十进制是255)
          变量b为0xfd(十进制是253)
          变量c为0xffff(十进制是65535)
          变量d为0xfffd(十进制是65533)

          下节预告:建议减法运算前先把所有变量转换成同一数据类型再参与运算。
(未完待续)


作者: jianhong_wu    时间: 2015-6-21 08:24
本帖最后由 jianhong_wu 于 2015-6-21 08:26 编辑

第二十三节:建议把所有参与减法运算的变量都转换成unsigned long数据类型。
    不管是以前讲的加法,现在讲的减法,还是未来讲的乘法和除法,我都会强烈建议“请把所有参与运算的变量都转成unsigned long类型”。unsigned long变量是三种数据类型中取值范围最大的数,取值范围可达04294967295之间,用了此类型变量的运算,不会轻易导致运算溢出的问题。有细心读者会问,万一数据超过了4294967295怎么办?答:可用BCD码的数组方式进行运算。这种数组运算的方法我在《从业将近十年,手把手教你单片机程序框架》里用了好几个章节跟大家介绍过,初学者暂时不用深入学习它。
变量转换的方法是引入中间变量,有多少个需要转换的变量就引入多少个中间变量,请看下面这个例子。
转换之前:
  1. unsigned int  a;
  2. unsigned char x=195;
  3. unsigned long y=101;
  4. a=x-y;
复制代码

分析:上述公式用到3个变量,其中ax都不是unsigned long变量,因此需要为它们分别引入中间变量ts
转换之后:
  1. unsigned int  a;
  2. unsigned char x=195;
  3. unsigned long y=101;

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

  6. s=0;  //s在接收x原数据之前先把高位和低位全部清零。因为s和x的数据宽度不一。
  7. s=x;  //接收x原数据,相当于把x转换成unsigned long中间变量。

  8. t=s-y;  //此处的t就默认代表了变量a。
复制代码

本章虽短,但是此方法在实际项目中很重要,大家不可大意。
     下节预告:乘法运算的5种常见格式。
(未完待续)

作者: jianhong_wu    时间: 2015-6-28 10:21
第二十四节:乘法运算的5种常见格式。
      请先看以下的乘法语法格式:
       “保存变量”=“被乘数1”*“乘数2”*... *“乘数N”;
        含义是:右边的“被乘数”与各“乘数”相乘,并且把最终的运算结果赋值给左边的“保存变量”。注意,这里的符号“=”不是等于号的意思,而是赋值的意思。左边的“保存变量”必须是变量,不能是常量,否则编译时会报错。右边的“被乘数”和“乘数”既可以是变量,也可以是常量,也可以是“保存变量”本身自己。多说一句,何谓变量和常量?变量是可以在程序中被更改的,是被分配的一个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。
       现在编写一个程序来练习上述5种格式的乘法语句,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;       //定义一个变量a,并且分配了1个字节的RAM空间。
  6.   unsigned char b;       //定义一个变量b,并且分配了1个字节的RAM空间。
  7.   unsigned char c;       //定义一个变量c,并且分配了1个字节的RAM空间。
  8.   unsigned char d=18;       //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为18.
  9.   unsigned char e=2;       //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为2.
  10.         
  11.   unsigned char x=15;    //定义一个变量x,并且分配了1个字节的RAM空间。初始化默认为15.
  12.   unsigned char y=6;     //定义一个变量y,并且分配了1个字节的RAM空间。初始化默认为6.        

  13.   //第1种:“被乘数1”是常量,“乘数2”是常量。
  14.   a=15*3;
  15.         
  16.         
  17.   //第2种:“被乘数1”是变量,“乘数2”是常量。
  18.   b=x*10;
  19.         
  20.   //第3种:“被乘数1”是变量,“乘数2”是变量。
  21.   c=x*y;
  22.         
  23.         
  24.   //第4种:“被乘数1”是保存变量本身,“乘数2”是常量。
  25.   d=d*2;
  26.   d=d*7;
  27.         
  28.         
  29.   //第5种:“被乘数1”是保存变量本身,“乘数2”是变量。
  30.   e=e*x;
  31.   e=e*y;


  32.   GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  33.   GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  34.   GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  35.   GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示
  36.   GuiWdData4=e;   //把变量e这个数值放到窗口变量4里面显示

  37.         
  38. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  39.    while(1)  
  40.    {
  41.       initial();
  42.       key_service();
  43.       display_service();
  44.    }

  45. }
复制代码

        如何在坚鸿51学习板上观察a,b,c,d,e这5个变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。上坚鸿51学习板观察程序执行的结果如下:
变量a为45。
变量b为150。
变量c为90。
变量d为252。
变量e为180。
     下节预告:乘法连写的简写。
(未完待续)


作者: jianhong_wu    时间: 2015-7-6 10:00
本帖最后由 jianhong_wu 于 2015-7-6 10:09 编辑

第二十五节:连乘以及自乘运算的简写。
       上一节我列举的乘法例子中,右边的参与运算的数据都是两个。实际上,C语言规则没有限制数据个数,它的通用格式如下:
       “保存变量”=“被乘数1”*“乘数2”...*“乘数N”;
       当右边的乘数个数超过两个的时候,就是我所说的“连乘”,每个乘数的属性没有限定,可以是常量,也可以是变量。比如:
  1.   a=2*5*3;  //被乘数和乘数全部是常量。a的结果为30。
  2.   b=k*x*y;  //被乘数和乘数全部是变量。b的结果为36。
  3.   c=x*5*y;  //被乘数和乘数,有的是常量,有的是变量。c的结果为90。
复制代码

        连乘的运行顺序是,赋值符号“=”右边的乘数挨个相乘,把每一次的运算结果放在一个临时的隐蔽变量里,这个隐蔽的变量我们看不到,是单片机系统内部参与运算时的专用寄存器,等右边所有的乘数连乘的计算结果出来后,再把这个隐蔽变量所保存的计算结果赋值给左边的“保存变量”。
        讲完了连乘的格式,接着讲自乘的简写。何谓自乘?当右边的被乘数是“保存变量”本身时,这种情况就是我所说的“自乘”。比如:
“保存变量”=“保存变量”*“乘数1”;
“保存变量”=“保存变量”*“乘数1”*“乘数2”...*“乘数N”;
        上述自加计算式可以简写成如下格式:
“保存变量”*=“乘数1”;
“保存变量”*=“乘数1”*“乘数2”...*“乘数N”;
        这种格式就是我所说的自乘简写。现在举几个例子如下:
  1.   d*=6;     //相当于d=d*6;最后d的结果为30。
  2.   e*=x;     //相当于e=e*x;最后e的结果为15。
  3.   f*=2*y*k; //相当于f=f*(2*y*k);最后f的结果为120。
复制代码

     我之前在讲加法的自加和减法的自减运算时,还给大家介绍了它们另外一种特殊的简写方式。比如减法运算,当右边只有2减数,当一个减数是“保存变量”,另一个是常数1时,格式如下:
“保存变量”=“保存变量”-1;
        这时候,可以把上述格式简写成如下两种格式:
“保存变量”--;
--“保存变量”;
        这两种格式也是俗称的“自减1”操作。比如:
  1. g--;  //相当于g=g-1或者g-=1;
  2. --h;  //相当于h=h-1或者h-=1;
复制代码

      那么,本节所讲的自乘运算,有没有这种特殊写法“g**”或者“**h”?答案很明显,没有。因为任何一个数“自乘1”还是等于它本身,所以研究这种特殊写法就没有任何意义。
      现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:



  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;       //定义一个变量a,并且分配了1个字节的RAM空间。
  6.   unsigned char b;       //定义一个变量b,并且分配了1个字节的RAM空间。
  7.   unsigned char c;       //定义一个变量c,并且分配了1个字节的RAM空间。
  8.   unsigned char d=5;       //定义一个变量d,并且分配了1个字节的RAM空间。初始化默认为5.
  9.   unsigned char e=5;       //定义一个变量e,并且分配了1个字节的RAM空间。初始化默认为5.
  10.   unsigned char f=5;       //定义一个变量f,并且分配了1个字节的RAM空间。初始化默认为5.
  11.    
  12.   unsigned char x=3;    //定义一个变量x,并且分配了1个字节的RAM空间。初始化默认为3.
  13.   unsigned char y=6;     //定义一个变量y,并且分配了1个字节的RAM空间。初始化默认为6.        
  14.   unsigned char k=2;     //定义一个变量k,并且分配了1个字节的RAM空间。初始化默认为2.
  15.         
  16.         
  17.             //第1个知识点:连乘。
  18.   a=2*5*3;  //被乘数和乘数全部是常量。a的结果为30。
  19.   b=k*x*y;  //被乘数和乘数全部是变量。b的结果为36。
  20.   c=x*5*y;  //被乘数和乘数,有的是常量,有的是变量。c的结果为90。

  21.             //第2个知识点:自乘的简写。
  22.   d*=6;     //相当于d=d*6;最后d的结果为30。
  23.   e*=x;     //相当于e=e*x;最后e的结果为15。
  24.   f*=2*y*k; //相当于f=f*(2*y*k);最后f的结果为120。
  25.          
  26.          


  27.    GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示
  28.    GuiWdData1=b;   //把变量b这个数值放到窗口变量1里面显示
  29.    GuiWdData2=c;   //把变量c这个数值放到窗口变量2里面显示
  30.    GuiWdData3=d;   //把变量d这个数值放到窗口变量3里面显示
  31.    GuiWdData4=e;   //把变量e这个数值放到窗口变量4里面显示
  32.    GuiWdData5=f;   //把变量f这个数值放到窗口变量5里面显示


  33.         
  34. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  35.    while(1)  
  36.    {
  37.       initial();
  38.       key_service();
  39.       display_service();
  40.    }

  41. }
复制代码

      如何在坚鸿51学习板上观察a,b,c,d,e,f这6个变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。上坚鸿51学习板观察程序执行的结果如下:
变量a为30
变量b为36。
变量c为90。
变量d为30。
变量e为15。
变量f为120。
     下节预告:乘法运算的溢出。
(未完待续)




作者: jianhong_wu    时间: 2015-7-12 07:03
本帖最后由 jianhong_wu 于 2015-7-12 07:32 编辑

第二十六节:乘法运算的溢出。
     乘法的溢出规律跟加法的溢出规律是一样的。举一个例子如下:
  1.    unsigned char k=30;
  2.    unsigned char n=10;
  3.    unsigned char a;
  4.    a=k*n;  
复制代码


分析:
kn相乘,相当于30乘以10,运算结果是300(十六进制是0x012c保存在一个隐藏中间变量,根据前面加法运算的规律,我猜测这个隐藏中间变量可能是unsigned int类型,然后再把这个中间变量赋值给单字节变量a,a只能接收十六进制的低8位字节0x2c,所以运算后a的数值由于溢出变成了十六进制的0x2c(十进制是44)。
由于乘法的溢出规律跟加法的溢出规律是一样的,所以不再多举例子。在实际项目中,为了避免一不小心就溢出的问题,我强烈建议,不管加减乘除,凡是参与运算的变量全部都要转化成unsigned long变量,转化的方法也跟加减运算的转换方法一致,不再详细解决这方面的内容。
现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始-----------------------------------------------------------------

  4. ----------*/
  5.       
  6.    unsigned char k=30;
  7.    unsigned char n=10;
  8.    unsigned char a;

  9.    a=k*n;  

  10.    GuiWdData0=a;   //把变量a这个数值放到窗口变量0里面显示



  11.         
  12. /*---C语言学习区域的结束-----------------------------------------------------------------

  13. ----------*/
  14.    while(1)  
  15.    {
  16.       initial();
  17.       key_service();
  18.       display_service();
  19.    }

  20. }
复制代码

    查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。上坚鸿51学习板观察程序执行的结果如下:
       变量a为0x2c(十进制是44)。

     下节预告:除法运算的常见格式。
(未完待续)


作者: jianhong_wu    时间: 2015-7-18 07:01
第二十七节:整除求商的运算。
C语言中,乘法符号不是“×”而是“*”,除法求商的符号不是“÷”而是“/”。乘除法符号跟我们日常用的数学符号有点不一样,我个人猜测C语言这样规定的原因是因为“×”容易跟大写字母“X”搞混,而“÷”这个符号在电脑键盘上不方便直接输入,故分别用“*”和“/”替代。
何谓“整除”?请看以下两个对比例子:
10除以4,商等于2.5------(带小数点)
10除以4,商等于2,余数是2------(这就叫做整除)
什么时候带小数点,什么时候是整除?取决于参与运算的变量类型。标准的C语言中,其实远远不止我前面所说的unsigned char ,unsigned int ,unsigned long这三种类型,还有一种叫浮点数的float类型,当参与运算的变量涉及float类型时,就可能存在小数点。这是题外话,大家大概知道有这么一回事即可,暂时不用深入研究float等其它类型的数据,因为在单片机项目中,只要用我所述的三种常用类型就绝对够用了,单片机不用涉及float类型,如果项目涉及小数点的显示和处理,我们完全可以用那三种类型去处理它,这些处理方法我后续会讲到,暂时不用管。而unsigned char ,unsigned int ,unsigned long这三种类型的除法都是整除,我后续所讲的所有章节内容也都是整除。
整除的通用格式:
“保存变量”=“被除数” /  “除数1/  “除数2... /  “除数N;
跟之前讲的加减运算一样,左边的“保存变量”必须是变量,右边的可以是变量和常量的任意组合。如果右边只有两个参与运算的数据,就是整除的常见格式。
整除的常见格式:
“保存变量”=“被除数” /  “除数” ;
现在从整除常见格式的6个方面来分析它的规律。
(1)当“除数”等于0时。我们都知道,数学运算除数是不允许等于0的,如果在单片机中非要让除数为0,商会出现什么结果?我试过,发现有一个规律:如果“除数”是变量的0,那么商等于十进制的255(十六进制是0xff)。如果“除数”是常量的0,那么商等于十进制的1。比如:
  1. a=23 /y;  //假设除数变量y里面是0,那么a的结果是255(十六进制的0xff)。
  2. b=23 /0;  //除数是常量0,那么b的结果是1。
复制代码

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

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

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

(5)自除运算的简写。跟前面加减法一样,当“被除数”是“保存变量”时,存在自除运算的简写。
“保存变量”=“保存变量” /  “除数” ;
上述自除运算的简写如下:
“保存变量” / =“除数” ;
比如:
  1. g/=5;  //相当于g=g/5;
复制代码

加减法有自加1++g”和自减1g--”的特殊写法,但是除法不存在这种自除1的特殊写法,因为一个数除以1还是等于它本身,所以自除1没有任何意义,因此C语言语法中没有这种写法。
   (6)除法的溢出规律跟加法的溢出规律是一样的,所以不再多举例子。在实际项目中,为了避免一不小心就溢出的问题,我强烈建议,不管加减乘除,凡是参与运算的变量全部都要转化成unsigned long变量,转化的方法也跟加减运算的转换方法一致,不再详细讲解这方面的内容。
现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;
  6.   unsigned char b;
  7.   unsigned char c;
  8.   unsigned char d;
  9.   unsigned char e;
  10.   unsigned char f;
  11.         unsigned char g=10;  //初始化为10
  12.         unsigned char y=0; //除数变量初始化为0。
  13.         a=23/y;
  14.         b=23/0;
  15.         c=7/10;
  16.         d=10/10;
  17.         e=10/4;
  18.         f=10/3;
  19.         g/=5;  //相当于g=g/5;
  20.        
  21.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  22.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  23.   GuiWdData2=c;   //把c这个变量放到窗口变量2里面显示
  24.   GuiWdData3=d;   //把d这个变量放到窗口变量3里面显示
  25.   GuiWdData4=e;   //把e这个变量放到窗口变量4里面显示
  26.   GuiWdData5=f;   //把f这个变量放到窗口变量5里面显示
  27.   GuiWdData6=g;   //把g这个变量放到窗口变量5里面显示
  28.         
  29. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  30.    while(1)  
  31.    {
  32.                   initial();
  33.       key_service();
  34.       display_service();
  35.    }

  36. }
复制代码

    查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。上坚鸿51学习板观察程序执行的结果如下:
     变量a为255(十六进制是0xff)。
     变量b为1。
     变量c为0。
     变量d为1。
    变量e为2。
    变量f为3。
    变量g为2。
     下节预告:整除求余的运算。
(未完待续)


作者: jianhong_wu    时间: 2015-7-27 08:24
第二十八节:整除求余的运算。
    求余跟上一节讲的求商都是属于整除运算,区别是:求余返回余数,求商返回商。整除求余的余数有一个很明显的规律:余数永远小于除数(除数不为0的情况下)。比如,除数是10,那么不管被除数有多大,余数必然是从0到9的数,不可能是10以上的数。上一节提到除法求商的运算符号是“/”,而除法求余的运算符号是“%”,外形跟百分号一致,只是在C语言中用来做除法求余的运算符而已。
整除求余的通用格式:
“保存变量”=“被除数”% “除数1% “除数2...%  “除数N;
跟之前讲的加减运算一样,左边的“保存变量”必须是变量,右边的可以是变量和常量的任意组合。如果右边只有两个参与运算的数据,就是整除求余的常见格式。
整除求余的常见格式:
“保存变量”=“被除数” % “除数” ;       
现在从整除求余常见格式的6个方面来分析它的规律。
(1)当“除数”等于0时。我们都知道,数学运算除数是不允许等于0的,如果在单片机中非要让除数为0,余数会出现什么结果?我在keilC51编译环境试过,发现有一个规律:如果“除数”是变量的0,那么余数等于被除数。如果“除数”是常量的0,那么余数等于1。还有一种特殊的情况是编译不通过的,就是“被除数”是变量,而“除数”是常量的0。其实大家都知道“除数”不能为0,为什么我非要做“除数”为0时的实验呢?意义何在?我虽然知道除数为0时会出错,但是我不知道这个错到底严不严重,会不会程序崩溃,当我做了这个实验后,我心中的石头才放下了,万一除数为0,只是运算出错,至少程序不会崩溃,这样我心里就有了一个底,当哪天我某个程序崩溃时,我至少可以排除了这种情况,方便我找bug。这就是本实验的意义所在。
    比如:
  1. a=23%y;  //假设除数变量y里面是0,那么a的结果是23。
  2. b=23%0;  //除数是常量0,那么b的结果是1。
  3. b=g%0;  //这种特殊情况编译不通过:“被除数”是变量,而“除数”是常量的0。
复制代码

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

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

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

(5)自除求余运算的简写。跟前面加减法一样,当“被除数”是“保存变量”时,存在自除求余运算的简写。
“保存变量”=“保存变量” % “除数” ;
上述自除运算的简写如下:
“保存变量” % =“除数” ;
比如:
  1. g%=5;  //相当于g=g%5;
复制代码

加减法有自加1++g”和自减1g--”的特殊写法,但是除法不存在这种自除1的特殊写法,因为一个数除以1还是等于它本身,所以自除1没有任何意义,因此C语言语法中没有这种写法。
   (6)除法的溢出规律跟加法的溢出规律是一样的,所以不再多举例子。在实际项目中,为了避免一不小心就溢出的问题,我强烈建议,不管加减乘除,凡是参与运算的变量全部都要转化成unsigned long变量,转化的方法也跟加减运算的转换方法一致,不再详细讲解这方面的内容。
现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;
  6.   unsigned char b;
  7.   unsigned char c;
  8.   unsigned char d;
  9.   unsigned char e;
  10.   unsigned char f;
  11.         unsigned char g=10;  //初始化为10
  12.         unsigned char y=0; //除数变量初始化为0。
  13.         a=23%y;
  14.         b=23%0;
  15.         //b=g%0;  //这种特殊情况编译不通过:“被除数”是变量,而“除数”是常量的0。
  16.         c=7%10;
  17.         d=10%10;
  18.         e=10%4;
  19.         f=10%3;
  20.         g%=5;  //相当于g=g%5;
  21.        
  22.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  23.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  24.   GuiWdData2=c;   //把c这个变量放到窗口变量2里面显示
  25.   GuiWdData3=d;   //把d这个变量放到窗口变量3里面显示
  26.   GuiWdData4=e;   //把e这个变量放到窗口变量4里面显示
  27.   GuiWdData5=f;   //把f这个变量放到窗口变量5里面显示
  28.   GuiWdData6=g;   //把g这个变量放到窗口变量5里面显示
  29.         
  30. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  31.    while(1)  
  32.    {
  33.                   initial();
  34.       key_service();
  35.       display_service();
  36.    }

  37. }
复制代码

    查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。上坚鸿51学习板观察程序执行的结果如下:
     变量a为23。
     变量b为1。
     变量c为7。
     变量d为0。
    变量e为2。
    变量f为1。
    变量g为0。
     下节预告:利用“整除求商求余”来提取一个数的个十百千位。
(未完待续)


作者: jianhong_wu    时间: 2015-8-1 15:13
第二十九节:利用“整除求商求余”来拆分提取一个数的个十百千位。
上两节讲了整除的求商求余运算符,这两个运算不仅能用在数学运算中,还可以用来拆分提取一个数的个十百千位。提取这些位有什么用呢?因为在以后的单片机显示程序中,不管是液晶屏还是数码管,必须用到这种提取算法,先把一个数的个十百千位一个个拆分提取出来,然后再送到显示屏上显示,所以这种算法很常见和实用。上述提到的“个,十,百,千”位只是一个虚数,具体是多少应该根据实际项目而定,也有可能是“个,十,百,千,万,十万,百万...”等位,但是处理的思路和方法都是一致的。
拆分提取的思路。比如8562这个数,千位是8,百位是5,十位是6,个位是2。可以依次看成是:
  1. 8=8562/1000;
  2. 5=562/100;
  3. 6=62/10;
  4. 2=2/1;
复制代码

上述用到了整除求商,但是562,62,2又是如何通过8562分解得到的呢?需要用到整除求余:
  1. 562=8562%1000;
  2. 62=8562%100;
  3. 2=8562%10;
复制代码

最后综合在一起,连在一起写:
  1. 8=8562/1000;
  2. 5=8562%1000/100;
  3. 6=8562%100/10;
  4. 2=8562%10/1;
复制代码

因为我们预先知道了这个数最大位是千位,所以千位直接整除1000求商就可以了。实际项目中,我们只是用某个变量,而这个变量的大小我们并不知道具体是什么,它的最大位可能并不止千位,也有可能是万位,所以需要把上述最高位的千位也做一下整除10000求余数,然后在整除1000求商,最后整理如下:
  1. 8=8562%10000/1000;
  2. 5=8562%1000/100;
  3. 6=8562%100/10;
  4. 2=8562%10/1;
复制代码

大家仔细观察和品味一下,很容易发现规律和原因,如果求万,十万,百万,也是用一样的方法。多提醒一句,根据我的经验,有一些单片机的C编译器可能不支持long类型数据的求余求商连写在一起,那么就要用一个中间变量分两步走,先求余,再求商,分开来操作。比如:
  1. 8=8562%10000/1000;
复制代码

分成两步走:
  1. a=8562%10000;
  2. a=a/1000;
复制代码

上述的变量a就是引入的中间变量。
现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;
  6.   unsigned char b;
  7.   unsigned char c;
  8.   unsigned char d;
  9.         unsigned int  x=8562;  //初始化为8562,注意必须是int类型以上,不能是char类型,char最大范围是255。

  10.   a=x%10000/1000;  //拆分提取千位
  11.   b=x%1000/100;    //拆分提取百位
  12.   c=x%100/10;      //拆分提取十位
  13.   d=x%10/1;        //拆分提取个位


  14.        
  15.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  16.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  17.   GuiWdData2=c;   //把c这个变量放到窗口变量2里面显示
  18.   GuiWdData3=d;   //把d这个变量放到窗口变量3里面显示

  19.         
  20. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  21.    while(1)  
  22.    {
  23.       initial();
  24.       key_service();
  25.       display_service();
  26.    }

  27. }
复制代码

    查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。上坚鸿51学习板观察程序执行的结果如下:
     变量a为8。
     变量b为5。
     变量c为6。
     变量d为2。
     下节预告:逻辑运算符的“与”运算。
(未完待续)


作者: jianhong_wu    时间: 2015-8-8 11:19
本帖最后由 jianhong_wu 于 2015-8-8 11:44 编辑

第三十节:逻辑运算符的“与”运算。
       单片机任何数字的底层运算都是以二进制的形式进行,前面讲的加减乘除也不例外。只不过加减乘除是生活常用的运算,任意给两个十进制的数据让单片机运算,我们都可以凭借既有的生活经验通过口算或者笔算来计算出结果,不需要刻意模拟单片机底层的二进制运算。但是本节讲的“与”运算却不行,它是为二进制而生的,若想使用它,必先把参与运算的数据双双转换成二进制格式的数据,你才能分析“与”运算的含义和规律。“与”运算是以位来进行运算的,位就是代表二进制中的每一位,每一个位只能是0或者1。两个数的“与”运算就是两个数被展开成二进制后的“与”运算。
       “与”运算的运算符号是“&”。运算规律是:两个位进行“与”运算,只有两个位都同时是1运算结果才能等于1,,否则,只要其中有一位是0,运算结果都是0.比如:
0&0等于0。
0&1等于0。
1&0等于0。
1&1等于1。
注意,上述的0和1都是指二进制的0和1。
       现在举一个完整的例子来分析“与”运算的规律。有两个unsigned char类型的十进制数分别是12和9,求12&9的结果是多少?分析步骤如下:
       第一步:先把参与运算的两个数以二进制的格式展开。十进制转二进制的方法请参考前面第13,14,15节的内容。
十进制12的二进制格式是:00001100。
十进制9的二进制格式是:00001001。
       第二步:二进制数右对齐,按每一位进行“与”运算。
            00001100
          &00001001
结果是 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)。

       现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;
  6.   unsigned char b=85;  //十六进制是0x55,二进制是01010101。

  7.   a=12&9;
  8.   b=b&0xfe;   


  9.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  10.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示

  11.         
  12. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  13.    while(1)  
  14.    {
  15.       initial();
  16.       key_service();
  17.       display_service();
  18.    }

  19. }
复制代码


        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0上坚鸿51学习板观察程序执行的结果如下:
        变量a为8(十六进制是0x08,二进制是00001000)。
        变量b为84(十六进制是0x54,二进制是01010100)。
        下节预告:逻辑运算符的“或”运算。
(未完待续)


作者: jianhong_wu    时间: 2015-8-15 08:40
本帖最后由 jianhong_wu 于 2015-8-15 08:42 编辑

第三十一节:逻辑运算符的“或”运算。
       “或”运算是以位为单位进行运算的。位是指二进制中的某一位,位只能是0或者1。两个数的“或”运算就是转换成二进制后每一位的“或”运算。
       “或”运算的符号是“|”。运算规律是:两个位的“或”运算,如果两个位都是0,那么运算结果才是0,否则只要其中有一位是1,那么运算结果必定是1。比如:
0|0等于0。
0|1等于1。
1|0等于1。
1|1等于1。
      现在举一个完整的例子来分析“|”运算的规律。有两个unsigned char类型的十进制数分别是12和9,求12|9的结果是多少?分析步骤如下:
      第一步:先把参与运算的两个数都转换成二进制的格式。十进制转二进制的方法请参考前面第13,14,15节的内容。
十进制12的二进制格式是:00001100。
十进制9的二进制格式是:00001001。
      第二步:二进制数右对齐,按每一位进行“或”运算。
           00001100
          |00001001
结果是 00001101。
      第三步:把二进制的00001101转换成十六进制是:0x0D。转换成十进制是13。所以12&9的结果是13。
      “或”运算最常见的用途是可以指定一个变量的某位置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)。

       现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;
  6.   unsigned char b=84;  //十六进制是0x54,二进制是01010100。

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


  9.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  10.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示

  11.         
  12. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  13.    while(1)  
  14.    {
  15.       initial();
  16.       key_service();
  17.       display_service();
  18.    }

  19. }
复制代码


       查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0上坚鸿51学习板观察程序执行的结果如下:
      变量a为13(十六进制是0x0D,二进制是00001101)。
      变量b为85(十六进制是0x55,二进制是01010101)。
      下节预告:逻辑运算符的“异或”运算。
(未完待续)


作者: jianhong_wu    时间: 2015-8-23 16:21
本帖最后由 jianhong_wu 于 2015-8-23 16:25 编辑

第三十二节:逻辑运算符的“异或”运算。
       “异或”运算是以位为单位进行运算的。位是指二进制中的某一位,位只能是0或者1。两个数的“异或”运算就是转换成二进制后每一位的“异或”运算。
       “异或”运算的符号是“^”。运算规律是:两个位的“异或”运算,如果两个位都相同,那么运算结果就是0;如果两个位不同(相异),则运算结果是1。比如:
0^0等于0。(两个位相同)
0^1等于1。(两个位相异)
1^0等于1。(两个位相异)
1^1等于0。(两个位相同)
       现在举一个完整的例子来分析“^”运算的规律。有两个unsigned char类型的十进制数分别是12和9,求12^9的结果是多少?分析步骤如下:
       第一步:先把参与运算的两个数都转换成二进制的格式。十进制转二进制的方法请参考前面第13,14,15节的内容。
十进制12的二进制格式是:00001100。
十进制9的二进制格式是:00001001。
       第二步:二进制数右对齐,按每一位进行“异或”运算。
            00001100
         ^00001001
结果是 00000101。
        第三步:把二进制的 00000101转换成十六进制是:0x05。转换成十进制是5。所以12^9的结果是5。
       “异或”在哪些项目上经常应用?以我个人的项目经验,平时很少用“异或”,我唯一用过一次“异或”,是在制定串口通讯协议时,通过“异或”算法,增加一个校验字节,此校验字节是一串数据依次相“异或”的总结果,目的是为了增加数据传送时的抗干扰能力。

       现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a;


  6.   a=12^9;


  7.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示

  8.         
  9. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  10.    while(1)  
  11.    {
  12.       initial();
  13.       key_service();
  14.       display_service();
  15.    }

  16. }
复制代码


         查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0上坚鸿51学习板观察程序执行的结果如下:
        变量a为5(十六进制是0x05,二进制是00000101)。

        下节预告:逻辑运算符的“按位取反”运算。
(未完待续)


作者: jianhong_wu    时间: 2015-8-30 07:05
本帖最后由 jianhong_wu 于 2015-8-30 10:46 编辑

第三十三节:逻辑运算符的“按位取反”和“非”。
       “按位取反”运算的符号是波浪符号“~”。运算规律是:针对某个数的“按位取反”,先将其展开成二进制的格式,然后每个位取反,所谓取反就是1的变成0,0的变成1。
         现在举一个完整的例子来分析“按位取反”运算的规律。有一个unsigned char类型的十进制数分别是5和0,求~5和~0的结果是多少?分析步骤如下:
         第一步:先把参与运算的数转换成二进制的格式。十进制转二进制的方法请参考前面第13,14,15节的内容。
十进制5的二进制格式是:00000101。
十进制0的二进制格式是:00000000。
         第二步:
(1)将5的二进制数每一位取反,1的变成0,0的变成1。
        ~00000101
结果是11111010。
把二进制的11111010转换成十六进制是:0xFA。转换成十进制是250。所以~5的结果是250。
(2)将0的二进制数每一位取反,1的变成0,0的变成1。
        ~00000000
结果是11111111。
把二进制的11111111转换成十六进制是:0xFF。转换成十进制是255。所以~0的结果是255。
       讲完“按位取反”,现在接着讲“非”。 “非”跟“按位取反”有点相似,但是区别也明显。“按位取反”针对的是一个数的某一位,侧重在局部。而“非”是针对一个数的整体,侧重在全局。“非”只有两种状态“假”和“真”。0代表假,大于0的数值代表真,也可以说“非”假即真,“非”真即假。不是假的就是真的,不是真的就是假的。强调的是两种状态的切换。在数值表示上,用0代表假的状态,用1代表真的状态。
      “非”运算的符号是感叹号“!”。运算规律是:针对某个数的“非”,不管此数有多大,只要它大于0,那么被“非”后就一定是0。也不管此数是什么变量类型,只要它数值等于0,那么被“非”后就一定是1。
        现在举一个完整的例子来分析“非”运算的规律。有一个unsigned char类型的十进制数分别是5和0,求!5和!0的结果是多少?分析步骤如下:
        第一步:5大于0,是一个整体,被“非”后为0.
        第二步:0就是0,是一个整体,被“非”后为1.

        现在编写一个程序来练习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         

  5.   unsigned char a=5;
  6.   unsigned char b=5;
  7.   unsigned char c=0;
  8.   unsigned char d=0;

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


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

  13.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  14.   GuiWdData1=b;   //把a这个变量放到窗口变量1里面显示
  15.   GuiWdData2=c;   //把a这个变量放到窗口变量2里面显示
  16.   GuiWdData3=d;   //把a这个变量放到窗口变量3里面显示   


  17. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  18.    while(1)  
  19.    {
  20.                   initial();
  21.       key_service();
  22.       display_service();
  23.    }

  24. }
复制代码


       查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
       变量a为250(十六进制是0xFA,二进制是11111010)。
       变量b为0 (十六进制是0x00,二进制是00000000)。
       变量c为255(十六进制是0xFF,二进制是11111111)。
       变量d为1 (十六进制是0x01,二进制是00000001)。
       下节预告:移位运算的左移。
(未完待续)


作者: jianhong_wu    时间: 2015-9-5 13:07
本帖最后由 jianhong_wu 于 2015-9-6 06:55 编辑

第三十四节:移位运算的左移。
        前面讲的“与,或,异或”运算,漏讲了它们的简写格式的内容,在本节开始之前先把这部分内容补上。在“与,或,异或”这些运算中,当赋值语句左边的“保存变量”是参与运算的变量本身时,也存在简写的语法格式,比如:
      a&=0x01;  //相当于a=a&0x01;
      a|=0x01;  //相当于a=a|0x01;
      a^=0x01;  //相当于a=a^0x01;

     现在正式开始本节内容“移位运算的左移”。左移的运算符号是“<<”,语法格式如下:
     保存变量=被移数<<n;
     其中n代表“被移数”需要左移的位数。
     整句语法的含义是:“被移数”的二进制格式数据被整体往左边移动了n位,原来高n位数据被直接覆盖,新空出的低n位数据填入0。最后把移位结果存入“保存变量”。
     现在举一个完整的例子来分析“左移”运算的规律。有2个unsigned char类型的变量a和b,a的初始值是十进制数5,a=a<<1的结果是多少?b的初始值也是十进制数5,b=b<<2的结果是多少?
     分析步骤如下:
     第一步:先把参与运算的数转换成二进制的格式。十进制转二进制的方法请参考前面第13,14,15节的内容。
十进制5的二进制格式是:00000101。
     第二步:
(1) 将5的二进制数整体往左边移动1位:
                                   原来是:00000101
        整体往左移动1位后变成:00001010
        把二进制的00001010转换成十六进制是:0x0A。转换成十进制是10。所以a初始值是5,左移1位后的结果是10.
(2) 将5的二进制数整体往左边移动2位:
                                  原来是:00000101
        整体往左移动2位后变成:00010100
        把二进制的00010100转换成十六进制是:0x14。转换成十进制是20。所以b初始值是5,左移2位后的结果是20.
        仔细观察上述两个例子,发现了一个重要的规律:某数左移1位相当于此数乘以2,左移多少位相当于乘以多少个2.比如上述例子中5左移1位相当于5乘以2,结果等于10。而5左移2位相当于5乘以2再乘以2,5*2*2的结果等于20。既然左移1位相当于某个数乘以2,那么为什么不直接用乘法来替代左移呢?原因是一条左移语句的运算速度比一条乘法语句的运算速度要快很多倍。
        左移是在单片机项目中很常用的语法,也经常应用在一些数据类型之间的合并中。比如有两个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。
       再多讲一下知识点,左移也存在简写格式,比如:
d<<=1; //就相当于d=d<<1;
e<<=2; //就相当于e=e<<2;
       现在编写一个程序来练习刚才讲到的主要内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:



  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/


  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. a=a<<1; //a左移1位,从原来的5变成了10.
  10. b=b<<2; //b左移2位,从原来的5变成了20.


  11. c=H; //c的低8位被H覆盖,也就是此时c的低8位得到了H的各位值。
  12. c=c<<8; //及时把c的低8位移动到高8位,同时c原来的低8位被填入0
  13. c=c+L; //此时c再加L,c的低8位就L的值。此时c得到了H和L合并而来的值。


  14. GuiWdData0=a; //把a这个变量放到窗口变量0里面显示
  15. GuiWdData1=b; //把b这个变量放到窗口变量1里面显示
  16. GuiWdData2=c; //把c这个变量放到窗口变量2里面显示



  17. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  18. while(1)
  19. {
  20. initial();
  21. key_service();
  22. display_service();
  23. }

  24. }

复制代码

        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
        变量a为10 (十六进制是0x0A,二进制是00001010)。
        变量b为20(十六进制是0x14,二进制是00010100)。
        变量c为4660(十六进制是0x1234,二进制是0001 0010 0011 0100)。
        下节预告:移位运算的右移。
(未完待续)




作者: jianhong_wu    时间: 2015-9-6 06:56
本帖最后由 jianhong_wu 于 2015-9-6 06:59 编辑

第三十五节:移位运算的右移。
        右移的运算符号是“>>”,语法格式如下:
        保存变量=被移数>>n;
        其中n代表“被移数”需要右移的位数。
        整句语法的含义是:“被移数”的二进制格式数据被整体往右边移动了n位,原来低n位数据被直接覆盖,新空出的高n位数据填入0。最后把移位结果存入“保存变量”。
        现在举一个完整的例子来分析“右移”运算的规律。有2个unsigned char类型的变量a和b,a的初始值是十进制数5,a=a>>1的结果是多少?b的初始值也是十进制数5,b=b>>2的结果是多少?
分析步骤如下:
        第一步:先把参与运算的数转换成二进制的格式。十进制转二进制的方法请参考前面第13,14,15节的内容。
十进制5的二进制格式是:00000101。
       第二步:
(1) 将5的二进制数整体往右边移动1位:
                                   原来是:00000101
        整体往右移动1位后变成:00000010
        把二进制的00000010转换成十六进制是:0x02。转换成十进制是2。所以a初始值是5, 右移1位后的结果是2.
(2) 将5的二进制数整体往右边移动2位:
                                   原来是:00000101
        整体往右移动2位后变成:00000001
        把二进制的00000001转换成十六进制是:0x01。转换成十进制是1。所以b初始值是5, 右移2位后的结果是1。
       上一节讲的“左移”1位有乘以2的规律,相反,这节讲的“右移”也存在整除的规律:某数右移1位相当于此数整除2,右移多少位相当于整除多少个2.比如上述例子中5右移1位相当于5整除2,结果等于2。而5右移2位相当于5整除2再整除2,5/2/2的结果等于1。既然右移1位相当于某个数整除2,那么为什么不直接用整除来替代右移呢?原因是一条右移语句的运算速度比一条整除语句的运算速度要快很多倍。
        右移是在单片机项目中很常用的语法,也经常应用在一些数据类型之间的拆分中。比如有一个双字节unsigned int类型的变量c,它的初始值是0x1234,要把它拆分成两个unsigned char单字节的类型数据H和L,其中H是高8位字节,L是低八位字节,拆分后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的值是多少?答案是0x1234,因为只要它没有赋值给它自己,执行完语句后就不会改变它自己本身。
       再多讲一下知识点,右移也存在简写格式,比如:
e>>=1; //就相当于e=e>>1;
f>>=2; //就相当于f=f>>2;
       现在编写一个程序来练习刚才讲到的主要内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:


  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/

  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 int d;

  10. a=a>>1;
  11. b=b>>2;

  12. L=c; //c的低8位直接赋值给单字节的L
  13. H=c>>8; //c先把高8位右移到低8位,然后再把这8位数据赋值给H

  14. //执行上述语句后,此时的c变量的数值是多少呢?
  15. //答案是0x1234,因为只要没有赋值给它自己,就不会改变它自己.

  16. d=c; //此时d就等于c,是十六进制的0x1234.十进制是4660

  17. GuiWdData0=a; //把a这个变量放到窗口变量0里面显示
  18. GuiWdData1=b; //把b这个变量放到窗口变量1里面显示
  19. GuiWdData2=H; //把H这个变量放到窗口变量2里面显示
  20. GuiWdData3=L; //把L这个变量放到窗口变量3里面显示
  21. GuiWdData4=d; //把d这个变量放到窗口变量4里面显示

  22. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  23. while(1)
  24. {
  25. initial();
  26. key_service();
  27. display_service();
  28. }

  29. }

复制代码

        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
       变量a为2 (十六进制是0x02,二进制是00000010)。
       变量b为1(十六进制是0x01,二进制是00000001)。
       变量H为18(十六进制是0x12,二进制是00010010)。
       变量L为52(十六进制是0x34,二进制是00110100)。
       变量d为4660(十六进制是0x1234,二进制是0001 0010 0011 0100)。

     下节预告:括号改变优先级。
(未完待续)




作者: jianhong_wu    时间: 2015-9-13 08:47
本帖最后由 jianhong_wu 于 2015-9-13 08:48 编辑

第三十六节:括号改变优先级。
        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);
      加括号后,优先级顺序一目了然。

      现在编写一个程序来练习刚才讲到的主要内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=0x01;
  6.   unsigned char b=0x01;

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


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

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

  13.         
  14.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  15.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  16.   GuiWdData2=c;   //把c这个变量放到窗口变量2里面显示
  17.   GuiWdData3=d;   //把d这个变量放到窗口变量3里面显示

  18.         
  19. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  20.    while(1)  
  21.    {
  22.       initial();
  23.       key_service();
  24.       display_service();
  25.    }

  26. }
复制代码


        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
       变量a为9。
       变量b为128。
       变量c为8。
       变量d为7。
      下节预告:if判断语句和等于关系符“==”。
     (未完待续)


作者: jianhong_wu    时间: 2015-9-20 06:06
本帖最后由 jianhong_wu 于 2015-9-20 06:11 编辑

第三十七节:if判断语句以及常量变量真假的判断。
        “if”是C语言的判断语句关键词,意思是如果if小括号里面的条件满足,就执行条件后面大括号里的语句;如果条件不满足,则直接跳过条件后面大括号里的语句。“if”语句的常见格式如下:
if(条件)
{
    语句1;
    语句2;
    语句3;
……
}
        还有一种省略大括号的书写格式,但是要注意,当if条件语句后面省略了大括号时,如果if小括号里面的条件满足,仅仅执行条件后面第一条语句,如果条件不满足,则跳过条件后面第一条语句。比如:
if(条件)
    语句1;
    语句2;
    语句3;
……
       上述格式省略了大括号,实际上默认相当于:
if(条件)
{
    语句1;
}
    语句2;
    语句3;
……
       上述语句分析:当条件满足时,就执行语句1,如果不满足,就跳过语句1,直接从语句2处开始往后执行。在实际项目中,为了阅读清晰,建议大家不要省略大括号。
        接着讲另一个新的知识点,对于if(条件),if语句的条件包含两种,一种是常量或者变量真假的判断,另一种是关系判断。本节内容先举例讲常量或变量的判断。比如:
if(常量或者变量)
{
    语句1;
    语句2;
}
    语句3;
    语句4;
……
        当小括号里面的常量或者变量大于0时,就代表小括号里面的条件满足;当小括号里面的常量或者变量等于0时,就代表小括号里面的条件不满足。还有一种专业的说法,条件满足称之为“真”,条件不满足称之为“假”。在这里,常量或者变量大于0称之为“真”,等于0称之为“假”。还可以换一种思路来记忆,常量或者变量不等于0称之为“真”,等于0称之为“假”。比如刚才的例子:
if(常量或者变量)
{
    语句1;
    语句2;
}
    语句3;
    语句4;
……
        若条件为真,则从语句1处开始执行,若条件为假,则跳过语句1和语句2,直接从语句3开始执行。
        现在编写一个程序,有5条if判断语句,如果条件为真,累加统计变量就会自动加1,最后看看条件为真的语句有几条。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char x=2;
  6.   unsigned char y=0;
  7.   unsigned char a=0;  //此变量统计有多少条语句是真的

  8.   if(1)      //常量大于0,因此为真
  9.   {
  10.      a=a+1;  //a由0自加1后变成1。
  11.   }


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


  16.   if(15)     //常量大于0,因此为真
  17.   {
  18.      a=a+1;  //a由1自加1后变成2。
  19.   }



  20.   if(x)     //变量x为2,大于0,因此为真
  21.   {
  22.      a=a+1;  //a由,2自加1后变成3。
  23.   }


  24.   if(y)     //变量y为0,等于0,因此为假
  25.   {
  26.      a=a+1;  //由于条件为假,这条语句没有被执行,因此此时a仍然是3
  27.   }
  28.         
  29.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示

  30.         
  31. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  32.    while(1)  
  33.    {
  34.       initial();
  35.       key_service();
  36.       display_service();
  37.    }

  38. }
复制代码


         查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
         变量a为3。
         下节预告:等于关系符“==”和不等于关系符“!=”。
(未完待续)


作者: jianhong_wu    时间: 2015-9-26 09:47
本帖最后由 jianhong_wu 于 2015-9-26 09:49 编辑

第三十八节:等于关系符“==”和不等于关系符“!=”。
       上一节讲了if(条件)语句中当条件是纯常量或者变量的情况,这节开始讲if语句的关系判断。要进行关系判断,就涉及到关系符语句。本节先讲等于关系符“==”和不等于关系符“!=”。
        一. 等于关系符“==”语句。
      (1)等于关系符“==”语句的常见格式如下:
if(常量或变量==常量或变量)
{
    语句1;
    语句2;
}
语句3;
……
语句N;
        上述格式的含义是:如果等于号”==”左边的数确实等于右边的数,就执行大括号里的语句1和语句2;如果左右的数不相等,直接跳过大括号里的语句1和语句2,从语句3开始继续往下执行。
if语句省略大括号时的执行顺序上一节已经讲过了,本节和以后的章节就不再重复讲这方面的内容。
        (2)被判断的左右两个数中,如果有一个数是常量,另外一个是变量,针对这种情况建议大家尽量把常量放在等于号“==”的左边,原因是:万一程序员不小心把等于号“==”误写成赋值符号“=”时,编译器在编译时,能及时报错,因为常量在左边是无法赋值的,编译器能及时发现错误。但是如果变量在左边,因为变量是允许赋值的,所以有一些C语言编译器未必会报错,就会留下不易察觉的程序隐患。比如:
    if(a==5)
{
    语句1;
}
建议改成
    if(5==a)
{
    语句1;
}
         二. 不等于关系符“!=”语句。
        (1)不等于关系符“!=”语句的常见格式如下:
if(常量或变量!=常量或变量)
{
    语句1;
    语句2;
}
语句3;
……
语句N;
        上述格式的含义是:如果不等于号”!=”左边的数确实不等于右边的数,就执行大括号里的语句1和语句2;如果左右的数恰好相等,就直接跳过大括号里的语句1和语句2,从语句3开始继续往下执行。
       (2)被判断的左右两个数中,如果有一个数是常量,另外一个是变量,针对这种情况建议大家尽量把常量放在不等于号“!=”的左边,原因是:万一程序员不小心把不等于号“!=”误写成赋值符号“=”时,编译器在编译时,能及时报错,因为常量在左边是无法赋值的,编译器能及时发现错误。但是如果变量在左边,因为变量是允许赋值的,所以有一些C语言编译器未必会报错,就会留下不易察觉的程序隐患。比如:
    if(a!=5)
{
    语句1;
}
建议改成
    if(5!=a)
{
    语句1;
}

        现在编写一个实验程序,一共有8个给定的数,要统计其中数值等于85的数有几个,统计其中数值不等于75的数有几个。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.   unsigned char x1=90; //给定的第1个数     
  5.   unsigned char x2=65; //给定的第2个数   
  6.   unsigned char x3=85; //给定的第3个数   
  7.   unsigned char x4=79; //给定的第4个数   
  8.   unsigned char x5=95; //给定的第5个数   
  9.   unsigned char x6=65; //给定的第6个数   
  10.   unsigned char x7=75; //给定的第7个数   
  11.   unsigned char x8=85; //给定的第8个数  

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

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

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


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


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


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


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


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


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



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


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



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



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




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


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



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



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

  78.       
  79.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  80.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  81.   
  82.         
  83. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  84.    while(1)  
  85.    {
  86.                   initial();
  87.       key_service();
  88.       display_service();
  89.    }

  90. }
复制代码


         查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
         变量a为2。(等于85的有x3,x8这2个)
         变量b为7。(不等于75的有x1,x2,x3,x4,x5,x6,x8这7个)
         下节预告:大于关系符“>”和大于等于关系符“>=”。
(未完待续)


作者: jianhong_wu    时间: 2015-10-4 09:15
本帖最后由 jianhong_wu 于 2015-10-4 10:18 编辑

第三十九节:大于关系符“>”和大于等于关系符“>=”。
        一. 再复习一遍if语句的通用格式:
if(条件)
{
    语句1;
    语句2;
}
语句3;
……
语句N;
       上述格式的含义是:如果if语句里的条件满足(为真),就执行大括号里的语句1和语句2;如果条件不满足(为假),直接跳过大括号里的语句1和语句2,从语句3开始继续往下执行。
if语句省略大括号时的执行顺序前面章节已经讲过了,本节和以后的章节就不再重复讲这方面的内容。
        二. 大于关系符“>”语句。
if(常量或变量>常量或变量)
       上述if条件的真假判断规则是:如果左边的数大于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。
        三. 大于等于关系符“>=”语句。
if(常量或变量>=常量或变量)
       上述if条件的真假判断规则是:如果左边的数大于或者等于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。
        现在编写一个实验程序,一共有8个给定的数,要统计其中数值大于79的数有几个,统计其中数值大于等于79的数有几个。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.   unsigned char x1=90; //给定的第1个数     
  5.   unsigned char x2=65; //给定的第2个数   
  6.   unsigned char x3=85; //给定的第3个数   
  7.   unsigned char x4=79; //给定的第4个数   
  8.   unsigned char x5=95; //给定的第5个数   
  9.   unsigned char x6=65; //给定的第6个数   
  10.   unsigned char x7=75; //给定的第7个数   
  11.   unsigned char x8=85; //给定的第8个数  

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



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


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


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

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


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


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


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

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


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


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


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

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


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


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


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

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

  80.       
  81.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  82.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  83.   
  84.         
  85. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  86.    while(1)  
  87.    {
  88.                   initial();
  89.       key_service();
  90.       display_service();
  91.    }

  92. }
复制代码


         查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
        变量a为4。(大于79的有x1,x3, x5,x8这4个)
        变量b为5。(大于等于79的有x1,x3, x4, x5,x8这5个)
        下节预告:小于关系符“<”和小于等于关系符“<=”。
(未完待续)


作者: jianhong_wu    时间: 2015-10-4 10:34
第四十节:小于关系符“<”和小于等于关系符“<=”。
        一. 小于关系符“<”语句。
if(常量或变量<常量或变量)
       上述if条件的真假判断规则是:如果左边的数小于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。
        二. 小于等于关系符“<=”语句。
if(常量或变量<=常量或变量)
       上述if条件的真假判断规则是:如果左边的数小于或者等于右边的数,此条件为真(条件满足)。否则,为假(条件不满足)。
        现在编写一个实验程序,一共有8个给定的数,要统计其中数值小于79的数有几个,统计其中数值小于等于79的数有几个。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.   unsigned char x1=90; //给定的第1个数     
  5.   unsigned char x2=65; //给定的第2个数   
  6.   unsigned char x3=85; //给定的第3个数   
  7.   unsigned char x4=79; //给定的第4个数   
  8.   unsigned char x5=95; //给定的第5个数   
  9.   unsigned char x6=65; //给定的第6个数   
  10.   unsigned char x7=75; //给定的第7个数   
  11.   unsigned char x8=85; //给定的第8个数  

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


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

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

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

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

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

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

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

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

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


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


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

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

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


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


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

  75.   
  76.   if(x8<=79)  //如果条件为真,则执行下面大括号里面的语句。
  77.   {
  78.      b++;   //相当于b=b+1,用来统计小于等于79的总数
  79.   }
  80.       
  81.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  82.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  83.   
  84.         
  85. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  86.    while(1)  
  87.    {
  88.                   initial();
  89.       key_service();
  90.       display_service();
  91.    }

  92. }
复制代码


         查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
         变量a为3。(小于79的有x2,x6, x7这3个)
         变量b为4。(小于等于79的有x2, x4,x6, x7这4个)

         下节预告:与“&&”,或“||”的关系符。
(未完待续)


作者: jianhong_wu    时间: 2015-10-11 06:59
第四十一节:与“&&”,或“||”的关系符。
       一.前面第30,31节内容讲了运算符的与或“&”“|”,它们与本节关系符的与或“&&”“||”有相似之处,同时又有什么区别呢?
     (1)一个叫运算符,一个叫关系符。
     (2)运算符是在一个变量二进制的位与位之间进行1和0的运算(1为真,0为假),而关系符是强调两个以上条件判断的整体与整体之间进行真和假的判断运算。
     (3)运算符的书写符号是“&”“|”, 关系符的书写符号是“&&”“||”。
      二.与“&&”语句。它的中文表达含义是:假如有两个条件判断,既满足第1个条件判断,又满足第2个条件判断,则此整体判断裁定为真(条件满足)。否则,只要一个条件判断不满足,此整体判断就裁定为假(条件不满足)。比如:
if(第1个条件判断&&第2个条件判断…&&第N个条件判断)
      在上述if括号的条件中,如果所有的关系判断都为真,则此整体判断为真(条件满足),否则,只要有一个关系判断为假,则此整体判断为假(条件不满足)。
      比如要取从70到80之间的所有数据,那么既要大于等于70,同时又要小于等于80,因此可以这样书写:
if(a>=70&&a<=80)
{
   语句1;
语句2;
……
语句N;
}
      三.或“||”语句。它的中文表达含义是:假如有两个条件判断,只要有一个条件判断为真,则此整体判断裁定为真(条件满足)。否则,必须所有的条件判断都不满足,此整体判断才会裁定为假(条件不满足)。比如:
if(第1个条件判断||第2个条件判断…||第N个条件判断)
      在上述if括号的条件中,只要有1个条件判断为真,此整体判断就裁定为真。否则,必须所有的条件判断为假,此整体判断才裁定为假。
      比如要取除了7080之间以外的所有数据,也就是要么小于70,或者要么大于80,可以这样写:
if(a<70||a>80)
{
   语句1;
语句2;
……
语句N;
}

       现在编写一个实验程序,一共有8个给定的数,要统计其中数值从70到80之间的数有几个,统计其中取除了70到80之间以外的数有几个。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.   unsigned char x1=90; //给定的第1个数     
  5.   unsigned char x2=65; //给定的第2个数   
  6.   unsigned char x3=85; //给定的第3个数   
  7.   unsigned char x4=79; //给定的第4个数   
  8.   unsigned char x5=95; //给定的第5个数   
  9.   unsigned char x6=65; //给定的第6个数   
  10.   unsigned char x7=75; //给定的第7个数   
  11.   unsigned char x8=85; //给定的第8个数  

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


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


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


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

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

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


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


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

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

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

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

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

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

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

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

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

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

  78.       
  79.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  80.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  81.   
  82.         
  83. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  84.    while(1)  
  85.    {
  86.                   initial();
  87.       key_service();
  88.       display_service();
  89.    }

  90. }
复制代码


        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
        变量a为2。(数值从70到80之间的有x4, x7这2个)
        变量b为6。(除了70到80之间以外的有x1, x2,x3,x5, x6, x8这6个)
        下节预告:括号改变判断的优先级。
(未完待续)


作者: jianhong_wu    时间: 2015-10-18 05:36
本帖最后由 jianhong_wu 于 2015-10-18 05:38 编辑

第四十二节:小括号改变判断的优先级。
       小括号如何改变判断优先级? C语言有规定,凡是在判断语句里插入了小括号,程序就会优先执行最里面小括号的判断语句,之后才会根据判断符的优先级执行其它相关语句。
       小括号的意义何在?C语言中的判断符号众多,非常不利于程序员对各符号优先级的记忆,小括号却解决了这个问题。小括号因为可以改变判断的优先级,所以为实际项目带来了两个好处:一个是明确判断顺序,一个是改变判断顺序。
       比如在上一节提到的两个判断语:
if(a>=70&&a<=80)和if(a<70||a>80)
       有一些朋友喜欢插入两个小括号变成:
if((a>=70)&&(a<=80))和if((a<70)||(a>80))
       上述的修改,在不知道 “>,>=,<,<=” 这类语句跟 “&&,||” 这类语句哪个优先级更高的前提下,插入了小括号,可以更加明确判断的顺序,这种做法也挺好的,值得肯定。我个人平时在面对“>,>=,<,<=”这类语句跟 “&&,||” 这类语句时,也就是针对上述那种情况,由于我比较肯定的清楚“>,>=,<,<=”这类语句比“&&,||” 这类语句的优先级高,所以我不需要在此插入小括号来明确判断顺序。但是在下面将要提到的这种情况,我是会百分百插入小括号来明确和改变判断的顺序。什么情况呢?如下:
       if(判断条件1  ||  判断条件2  &&  判断条件3)
       到底是先“判断条件1” 跟“判断条件2”相或,最后再跟“判断条件3”相与?还是先“判断条件2” 跟“判断条件3”相与,最后再跟“判断条件1”相或?此时应该插入小括号明确它们判断的先后顺序。
       要么第一种顺序:
if((判断条件1  ||  判断条件2)  &&  判断条件3)
       要么第二种顺序:
if(判断条件1  ||  (判断条件2  &&  判断条件3))
       具体选择哪种一种判断顺序要根据项目的需要来决定。同样的3个判断条件,如果判断的顺序不一样,那么结果也可能出现不一样,比如,上述判断条件:
假设“判断条件1”为真,
假设“判断条件2”为真,
假设“判断条件3”为假,
        第一种顺序:
if((真||  真)  &&  假)
分析结论:先”真”和”真”相或结果为”真”,然后再把第一步判断结果的”真”和”假”相与,最后结果是”假”。
        第二种顺序:
if(真||  (真  &&  假))
分析结论:先”真”和” 假”相与结果为” 假”,然后再把第一步判断结果的” 假”和”真”相或,最后结果是”真”。
        现在编写一个实验程序验证上述两种判断顺序,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   //x,y这三个变量作为条件判断的变量
  6.   unsigned char x=5;
  7.   unsigned char y=6;
  8.   
  9.   //a,b这两个变量作为输出判断结果的真假,0代表假,1代表真。
  10.   unsigned char a=0;  //默认为0,也就是默认为假
  11.   unsigned char b=0;  //默认为0,也就是默认为假
  12.   
  13.   if((x<y||y>x)&&x==y) //里面的条件是((真||真)&&假),最终结果判断是假
  14.   {
  15.      a=1;
  16.   }

  17.   if(x<y||(y>x&&x==y)) //里面的条件是(真||(真&&假)),最终结果判断是真
  18.   {
  19.      b=1;
  20.   }
  21.   
  22.         
  23.   GuiWdData0=a;   //把a这个变量结果放到窗口变量0里面显示
  24.   GuiWdData1=b;   //把b这个变量结果放到窗口变量1里面显示

  25.         
  26. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  27.    while(1)  
  28.    {
  29.       initial();
  30.       key_service();
  31.       display_service();
  32.    }

  33. }
复制代码


         查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
         变量a为0。(0代表此条件判断结果为假)
         变量b为1。(1代表此条件判断结果为真)
         下节预告:if,elseif,else的5种组合判断语句。
(未完待续)


作者: jianhong_wu    时间: 2015-10-25 00:45
本帖最后由 jianhong_wu 于 2015-10-25 00:47 编辑

第四十三节:if,else if,else的5种组合判断语句。
        if,else if,else可以组成5种组合判断语句,这类组合语句可以这样通俗的解读:在众多条件判断中,先从第一个条件开始判断,如果第一个条件是真,那么不管后面的条件是否为真,都不再判断,直接执行条件1大括号后面的语句,组合语句中其它剩下的条件不再判断直接跳过,否则,就挨个条件往下判断,只要其中一个条件满足,就不再判断剩余的条件,也就是我们日常所说的多选一,甚至某些组合语句如果所有条件都不满足,那么什么也不选。总之,在如此众多的条件中,最多只能执行一个条件后面大括号的语句。组合语句还有一个规律:if语句只能出现在第一个条件判断,而且只能出现一次;else只能出现在最后一个条件判断,而且也只能出现一次;而else if语句总是出现在中间,绝对不能出现在第一个条件判断,如果没有else,也可以出现在最后的条件判断。接下来挨个仔细分析这5种组合语句的特点。
        第一种:
if(条件1)   //if只能出现第一个条件,并且只能出现一次
{
    语句1;
}
else    //else只能出现最后,并且也只能出现一次。
{
    语句2;
}
       分析:如果“条件1”为真,就直接执行“条件1”后面大括号的“语句1”,不再执行else后面的“语句2”。否则,如果“条件1”为假,那么就不执行“条件1”后面大括号的“语句1”,而是直接执行else后面的大括号“语句2”。简单概括就是两个必选一个,如果第一个条件1为假,就直接执行else后面大括号里的语句,也就是本例子中的“语句2”。
       第二种:
if(条件1)   //if只能出现第一个条件,并且只能出现一次
{
    语句1;
}
else  if(条件2)  //else if只能出现中间,可以出现多次
{
    语句2;
}
       分析:如果“条件1”为真,就直接执行“条件1”后面大括号的“语句1”,这时不管else if后面的“条件2”是否为真,都不再判断的“条件2”,这种情况当然也不会执行到“语句2”。否则,如果“条件1”为假,那么就不执行“条件1”后面大括号的“语句1”,而是继续判断else if后面的“条件2”,如果“条件2”为真,就会执行后面大括号的“语句2”,否则如果“条件2”也为假,那么“语句2”也不会执行。简单概括就是在两个条件中最多只能选一个,如果两个条件都为假,那么都不选。
       第三种:
if(条件1)       //if只能出现第一个条件,并且只能出现一次
{
    语句1;
}
else  if(条件2)   //else if只能出现中间,可以出现多次
{
    语句2;
}
else      //else只能出现最后,并且也只能出现一次。
{
    语句3;
}
       分析:如果“条件1”为真,就直接执行“条件1”后面大括号的“语句1”,这时不管else if后面的“条件2”是否为真,都不再判断的“条件2”,这种情况当然也不会执行到后面的“语句2”,更加不会执行到else 后面的“语句3”。否则,如果“条件1”为假,那么就不执行“条件1”后面大括号的“语句1”,而是继续判断else if后面的“条件2”,如果“条件2”为真,就会执行后面大括号的“语句2”,而else后面的“语句3”就不会被执行到,否则如果“条件2”也为假,那么“语句2”也不会执行,此时就直接执行else后面的“语句3”。简单概括就是在三个条件中必须选一个,如果前面两个条件都为假,那么就直接执行else后面大括号里的语句,也就是本例子中的“语句3”。
       第四种:
if(条件1)       //if只能出现第一个条件,并且只能出现一次
{
    语句1;
}
else  if(条件2)   //else if只能出现中间,可以出现多次
{
    语句2;
}
……
else  if(条件N)   //else if只能出现中间,可以出现多次
{
    语句N;
}

        分析:从“条件1”开始往下判断,只要有一个条件满足了,就执行当前条件后面大括号的语句,剩余的条件不再判断直接跳过,如果所有的条件都不满足,那么里面的语句都不执行。简单概括就是在N个条件中最多只能选一个,如果所有的条件都为假,那么都不选。
        第五种:
if(条件1)       //if只能出现第一个条件,并且只能出现一次
{
    语句1;
}
else  if(条件2)   //else if只能出现中间,可以出现多次
{
    语句2;
}
……
else  if(条件N)   //else if只能出现中间,可以出现多次
{
    语句N;
}
else   
{
    语句N+1;
}

       分析:从“条件1”开始往下判断,只要有一个条件满足了,就执行当前条件后面大括号的语句,剩余的条件不再判断直接跳过,如果所有的条件都不满足,那么就直接执行else后面大括号里的语句,也就是本例子中的“语句N+1”。简单概括就是在N+1个条件中必须选一个,如果前面所有N个条件都为假,最后就直接执行else后面大括号的语句,也就是本例子中的“语句N+1”。
       现在编写一个程序来实验上述5种组合语句。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   //x这个变量作为条件判断的变量
  6.   unsigned char x=5;


  7.   
  8.   //a,b,c,d,e这5个变量作为输出判断结果,0代表什么语句都没执行,1代表执行了语句1,
  9.   //2代表执行语句2,3代表执行语句3,4代表执行语句4,5代表执行语句5。
  10.   unsigned char a=0;  
  11.   unsigned char b=0;
  12.   unsigned char c=0;  
  13.   unsigned char d=0;
  14.   unsigned char e=0;  

  15.   //第一种
  16.   if(x>6)  //x默认是5
  17.   {
  18.      a=1;   //语句1
  19.   }
  20.   else
  21.   {
  22.      a=2;  //语句2
  23.   }


  24.   //第二种
  25.   if(x>6)  //x默认是5
  26.   {
  27.      b=1;   //语句1
  28.   }
  29.   else if(x==7)  //x默认是5
  30.   {
  31.      b=2;  //语句2
  32.   }


  33.   //第三种
  34.   if(x>6)  //x默认是5
  35.   {
  36.      c=1;   //语句1
  37.   }
  38.   else if(x==7)  //x默认是5
  39.   {
  40.      c=2;  //语句2
  41.   }
  42.   else
  43.   {
  44.      c=3;  //语句3
  45.   }

  46.   //第四种
  47.   if(x>6)  //x默认是5
  48.   {
  49.      d=1;   //语句1
  50.   }
  51.   else if(x==7)  //x默认是5
  52.   {
  53.      d=2;  //语句2
  54.   }
  55.   else if(x==5)  //x默认是5
  56.   {
  57.      d=3;  //语句3
  58.   }
  59.   else if(x==4)  //x默认是5
  60.   {
  61.      d=4;  //语句4
  62.   }

  63.   //第五种
  64.   if(x>6)  //x默认是5
  65.   {
  66.      e=1;   //语句1
  67.   }
  68.   else if(x==7)  //x默认是5
  69.   {
  70.      e=2;  //语句2
  71.   }
  72.   else if(x==5)  //x默认是5
  73.   {
  74.      e=3;  //语句3
  75.   }
  76.   else if(x==4)  //x默认是5
  77.   {
  78.      e=4;  //语句4
  79.   }
  80.   else
  81.   {
  82.      e=5;  //语句5
  83.   }

  84.         
  85.   GuiWdData0=a;   //把a这个变量结果放到窗口变量0里面显示
  86.   GuiWdData1=b;   //把b这个变量结果放到窗口变量1里面显示
  87.   GuiWdData2=c;   //把c这个变量结果放到窗口变量2里面显示
  88.   GuiWdData3=d;   //把d这个变量结果放到窗口变量3里面显示
  89.   GuiWdData4=e;   //把e这个变量结果放到窗口变量4里面显示

  90.         
  91. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  92.    while(1)  
  93.    {
  94.       initial();
  95.       key_service();
  96.       display_service();
  97.    }

  98. }
复制代码


         查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
        变量a为2。(2代表执行了语句2)
        变量b为0。(0代表什么语句都没执行)
        变量c为3。(3代表执行了语句3)
        变量d为3。(3代表执行了语句3)
        变量e为3。(3代表执行了语句3)

        下节预告:一维数组。
(未完待续)


作者: szdzjs    时间: 2015-10-25 13:35
鸿哥的大作又有更新了
作者: jianhong_wu    时间: 2015-10-31 02:26
本帖最后由 jianhong_wu 于 2015-10-31 02:36 编辑

第四十四节:一维数组能批量定义变量的特点。
       一维数组有两个特点:
       第一个:批量定义变量。我们之前的章节用unsigned  char, unsigned  int, unsigned long定义变量时,往往一条语句只习惯定义一个变量,而用数组可以一条语句定义N个变量,只要不超过单片机的RAM范围。
       第二个:数组的每个元素变量的地址都是挨个相临的。第一个元素变量的地址是该数组的首地址,后面的下标是以此首地址为原点的偏移地址。这个特点跟以后学到的循环语句或者指针配合起来,只需简单几行代码就可以实现很多复杂实用的算法。
       上述第二个特点的内容等以后学到循环语句和指针时再深入讲解,本节重点讲解一维数组的书写格式和第一个特点。
       一维数组未带初始化时的通用定义格式如下:
类型 数组名[数组元素个数N];
比如:
unsigned char  x[3];  //此处的3不是下标,而是元素个数
       分析:此数组一行代码定义了三个变量,分别是x[0], x[1], x[2],此时中括号里的0,1,2称为数组的下标,注意,数组的下标是从0开始的,从N-1结束,此时的N代表数组元素个数。因此,上述数组不存在x[3]这个元素变量,只有x[0], x[1], x[2]这三个变量,如果非要使用x[3]这个元素变量,那就会导致数组越界出现异常或者编译不通过。
      一维数组带初始化时的通用定义格式如下:
类型 数组名[数组元素个数N]={ 元素0, 元素1,…元素N-1};
比如:
unsigned char  y[3]={10,11,12};
       分析:此数组一行代码定义了三个变量,分别是y[0], y[1], y[2]。而y[0]初始化为10,y[1]初始化为11,y[2]初始化为12。
在程序中,调用数组某个变量元素时,下标可以是常量,比如y[0],此时的0就是常量;下标也可以是变量,比如y,此时的i就是变量。再强调一次,下标常量或者变量i的数值必须小于y数组定义时的元素个数,否则就会导致数组越界出现异常或者编译不通过。
       现在编写一个程序来熟悉一下一维数组的书写和使用格式。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char  x[3];  //此处的3不是下标,而是元素个数,里面的3个变量没有初始化
  6.   unsigned char  y[3]={10,11,12}; //里面三个元素变量y[0],y[1],y[2]分别初始化为10,11,12

  7.   unsigned char  i=0; //定义和初始化一个变量。用来做x数组的下标。

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


  14.         
  15.   GuiWdData0=x[0];   //把x[0]这个元素变量放到窗口变量0里面显示
  16.   GuiWdData1=x[1];   //把x[1]这个元素变量放到窗口变量1里面显示
  17.   GuiWdData2=x[2];   //把x[2]这个元素变量放到窗口变量2里面显示


  18.   GuiWdData3=y[0];   //把y[0]这个元素变量放到窗口变量3里面显示
  19.   GuiWdData4=y[1];   //把y[1]这个元素变量放到窗口变量4里面显示
  20.   GuiWdData5=y[2];   //把y[2]这个元素变量放到窗口变量5里面显示

  21.         
  22. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  23.    while(1)  
  24.    {
  25.                   initial();
  26.       key_service();
  27.       display_service();
  28.    }

  29. }
复制代码

       查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
        变量元素x[0]为25。
        变量元素x[1]为26。
        变量元素x[2]为28。
        变量元素y[0]为10。
       变量元素y[1]为11。
       变量元素y[2]为12。
       下节预告:二维数组能批量定义变量的特点。
(未完待续)

作者: jianhong_wu    时间: 2015-11-9 00:27
本帖最后由 jianhong_wu 于 2015-11-9 00:31 编辑

第四十五节:二维数组。
       上一节讲一维数组时漏讲了一个知识点,一维数组在定义时,如果预先给它填写若干个初始化的数据,也可以省略中括号[N]里面的元素个数N,这样编译器在编译时会根据初始化的个数来自动识别和定义此一维数组实际元素个数。比如:
unsigned char  y[3]={10,11,12};
        跟
unsigned char  y[]={10,11,12};
       的意义是一样的。注意,省略元素个数时必须要有初始化的数据,否则编译器不知道此数组的长度可能导致编译出错。
       继续回到本节的内容,二维数组。一维数组只有一个下标,像由很多点连成的一条直线,而二维数组有两个下标,布下了一行行的点像一张矩形的网,它的两个下标分别代表了行和列,行和列又像我们所学的y轴和x轴坐标,通过y轴和x轴坐标就可以找到所需的点,也就是数组的某个元素。
       上述是对二维数组的感性描述,二维数组是由一维数组发展而来,所以具备了很多一维数组的特点。二维数组的所有”网点”元素的地址都是挨个相临的,先第0行,再第1行,再第2行…再第N行,上一行尾元素跟下一行头元素的地址是相临连续的。
        二维数组未带初始化时的通用定义格式如下:
类型 数组名[行数Y][列数X];
比如:
unsigned char  a[2][3]; //此处的2代表有2行,3代表有3列。
分析:此二维数组定义了6个变量,跟一维数组一样,下标都是从0开始,到(N-1)时结束,此处的N代表行数或者列数。所以a[2][3]数组的元素挨个分别是a[0][0],a[0][1], a[0][2], a[1][0], a[1][1], a[1][2]这6个变量。
         二维数组有两种常用初始化格式,一种是逐行初始化,一种是整体初始化。
         第一种逐行初始化:
unsigned char   a[2][3]=
{
   {0,1,2},
   {3,4,5}
};
        在逐行初始化定义二维数组时,只要有初始化的数据,也可以省略行下标,但是列下标不能省略,比如:
unsigned char   a[][3]=
{
   {0,1,2},
   {3,4,5}
};
此时编译器会根据元素的个数来确定行数是多少。

        第二种整体初始化,跟一维数组一样,内部数据元素不需要额外增加大括号来分行。
unsigned char   a[2][3]=
{
   0,1,2,3,4,5
};
或者
unsigned char   a[2][3]=
{
   0,1,2,
   3,4,5
};
都行。
       C语言是很自由很丰富的语言,比如二维数组还允许不完全初始化的一些情况,我就不再深入讲解,我讲解的都是挑选一些针对以后单片机项目中可能会经常用到的语法。
       二维数组我在很多项目上还是经常用到的,比如用在一些需要把所得的信息进行查表判断的项目,在每一行里放一条关键词字符串信息,利用循环语句进行逐行查找匹配。至于二维数组如何存放字符串的知识点以后再讲。这节的重点是让大家对二维数组有个初步的认识。
       现在编写一个程序来熟悉一下二维数组的书写和使用格式。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         

  5.    unsigned char  a[2][3]=  //定义和初始化一个二维数组
  6.    {
  7.       {0,1,2},
  8.       {3,4,5}
  9.    };

  10.         
  11.   GuiWdData0=a[0][0];   //把a[0][0]这个元素变量放到窗口变量0里面显示
  12.   GuiWdData1=a[0][1];   //把a[0][1]这个元素变量放到窗口变量1里面显示
  13.   GuiWdData2=a[0][2];   //把a[0][2]这个元素变量放到窗口变量2里面显示
  14.   GuiWdData3=a[1][0];   //把a[1][0]这个元素变量放到窗口变量3里面显示
  15.   GuiWdData4=a[1][1];   //把a[1][1]这个元素变量放到窗口变量4里面显示
  16.   GuiWdData5=a[1][2];   //把a[1][2]这个元素变量放到窗口变量5里面显示
  17.         
  18. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  19.    while(1)  
  20.    {
  21.       initial();
  22.       key_service();
  23.       display_service();
  24.    }

  25. }
复制代码


    查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
    变量元素a[0][0]为0。
    变量元素a[0][1]为1。
    变量元素a[0][2]为2。
    变量元素a[1][0]为3。
    变量元素a[1][1]为4。
    变量元素a[1][2]为5。
    下节预告:while循环语句。
(未完待续)


作者: jianhong_wu    时间: 2015-11-14 02:22
本帖最后由 jianhong_wu 于 2015-11-14 02:25 编辑

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

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

       46.2   while循环的常见格式
while(条件)
{
    语句1;   
    语句2;
     ……   
    语句N;
}

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

       46.3   while省略花括号,后面第一条语句前没带分号
while(条件)
         语句1;   
        语句2;
        ……   
        语句N;

        分析:
        跟if语句一样,此时循环体默认只包含“语句1”,相当于:  
while(条件)
{
         语句1;   
}
        语句2;
        ……   
         语句N;


         46.4   while省略花括号,后面第一条语句前带分号
        while(条件);
         语句1;   
        语句2;
        ……   
        语句N;

        分析:
        注意,此时循环体默认不包含“语句1”,而是相当于:   
while(条件)
          ;   //一个分号代表一条空语句,语法上要求加上分号
         语句1;
         语句2;
         ……   
         语句N;
         此时如果条件一直为真,单片机就一直在空循环耗着,执行不到语句1的语句。直到条件为假才罢休。


          46.5   编写程序
          编写一个程序来熟悉一下while语句的书写和使用格式。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=0;  //观察这个数最后的变化
  6.   unsigned char b=0;  //观察这个数最后的变化

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


  8.   i=3;   
  9.   while(i)  //i不断变为0时才跳出此循环体
  10.   {
  11.      a=a+1;
  12.      i=i-1; //循环的条件不断发生变化,不断减小
  13.   }


  14.   i=0;
  15.   while(i<3)  //i大于等于3时才跳出此循环体
  16.   {
  17.      b=b+2;
  18.      i=i+1;   //循环的条件不断发生变化,不断增加
  19.   }

  20.      
  21.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  22.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示

  23.         
  24. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  25.    while(1)  
  26.    {
  27.       initial();
  28.       key_service();
  29.       display_service();
  30.    }

  31. }
复制代码


           查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
           变量a为3。
            变量b为6。


            下节预告:while循环语句的嵌套与break语句。
(未完待续)


作者: jianhong_wu    时间: 2015-11-22 08:40
本帖最后由 jianhong_wu 于 2015-11-22 08:45 编辑

第四十七节:循环语句do while和for。
       47.1   do while语句的常见格式
do{
       语句1;   
       语句2;
       ……   
       语句N;
} while(条件);
      分析:
      单片机从上往下执行语句,先从do那里无条件进来,从语句1开始往下执行,一直执行到语句N,然后开始判断while(条件)的条件是否为真,如果为真继续返回到do的入口处,继续从语句1开始往下执行,依次循环。大家留意到了吗,do while和while语句有什么差别?差别是,dowhile是先无条件进来执行一次循环体(花括号里所有的程序代码),执行到循环体最底部才判断while(条件)的条件是否为真来决定是否继续循环,先上车再买票。而while语句是先判断条件是否为真再决定是否需要进入循环体,先买票再上车。


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


      47.3   for语句的自加格式。
for(变量的初始化语句; 变量的条件判断;变量在执行一次循环体后自加的步进变化)
{
       语句1;
       语句2;
       ……
       语句N;
}
在把上述变成具体一点如下:
for(i=0; i<3;i++)
{
      语句1;
      语句2;
      ……
      语句N;
}
       分析:
       单片机从上往下,在进入循环体前,先把变量i初始化赋值0,然后判断i是否小于3这个条件,如果此条件为真,就开始从语句1往下执行到语句N,执行完一次循环体后,i因为“i++”语句就自加1,此时i从原来初始化的0变成了1,接着再返回来到for语句的第二句条件判断”i<3”那里,判断i是否继续小于3这个条件,如果此条件为真就继续往下执行,否则就跳过循环体。因此上述for语句的功能如果用while语句来写,相当于以下代码:

i=0;  //进入循环体之前先初始化给予初值
while(i<3)
{
     语句1;
     语句2;
     ……
     语句N;
     i++;   //执行一次循环体之后此变量自加发生变化
}
上述的循环语句只执行了3次循环体。


      47.4   for语句的自减格式
       刚才讲的for(i=0; i<3;i++)格式,它的变量i是不断自加的,还有一种比较常见的格式是i不断自减的。如下:
for(i=3; i>0;i--)
{
     语句1;
     语句2;
     ……
     语句N;
}
      上述自减的for语句功能如果用while语句来写,相当于以下代码:

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

     47.5   for省略花括号,后面第一条语句前没带分号
for(i=0; i<3;i++)
         语句1;  
         语句2;
         ……   
         语句N;

       分析:
       跟if语句一样,此时循环体默认只包含“语句1”,相当于:  
for(i=0; i<3;i++)
{
         语句1;  
}
         语句2;
         ……   
          语句N;

       47.6   for省略花括号,后面第一条语句前带分号
for(i=0; i<3;i++);
         语句1;  
         语句2;
         ……   
         语句N;

    分析:
    注意,此时循环体默认不包含“语句1”,而是相当于:   
for(i=0; i<3;i++)
           ;  //一个分号代表一条空语句,语法上要求加上分号
       语句1;
       语句2;
       ……   
       语句N;
       此时循环体内先循环执行三次空语句,然后才会结束for循环,接着从语句1开始往下执行。

       47.7   编写程序
       编写一个程序来熟悉一下do while和for语句的使用。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=0;  //观察这个数最后的变化
  6.   unsigned char b=0;  //观察这个数最后的变化
  7.   unsigned char c=0;  //观察这个数最后的变化


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

  9.   i=3;   
  10.   do
  11.   {
  12.      a=a+1;  //每执行一次循环体a就增加1,此行代码被循环执行了3次
  13.          i=i-1;  //i不断变小
  14.   }while(i); //i不断变小,当i变为0时才跳出此循环体



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


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



  23.      
  24.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  25.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  26.   GuiWdData2=c;   //把c这个变量放到窗口变量1里面显示
  27.         
  28. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  29.    while(1)  
  30.    {
  31.       initial();
  32.       key_service();
  33.       display_service();
  34.    }

  35. }
复制代码


        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
        变量a为3。
        变量b为6。
        变量c为9。
         下节预告:循环体内的continue和break语句。
(未完待续)


作者: jianhong_wu    时间: 2015-11-30 09:34
本帖最后由 jianhong_wu 于 2015-11-30 09:39 编辑

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

48.1   continue语句
        通常情况下,单片机在循环体里从上往下执行,一直执行到最底部的花括号才会返回到循环体入口处准备第二次循环,但是,如果在循环体中途遇到continue语句,单片机虽然没有执行到最底部的花括号,也会马上返回到循环体入口处准备第二次循环,在continue语句之后至最底部花括号之间的语句没有被执行到。比如:

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

    ……   
    语句N;
}    //循环体结束

       分析:
       上述语句中,单片机从语句1开始执行到continue语句,然后马上就返回到循环体的条件判断入口处,也就是语句3至语句N是不可能被执行到的,上述语句简化后相当于:

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

       既然可以如此简化,还要continue语句有什么用呢?我们一般应用continue的时候不会像上面那样单独使用,而是配合这if条件判断语句来用,这样continue才有它存在价值,比如:

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

    ……   
    语句N;
}    //循环体结束


48.2    break语句
         continue语句是提前结束当前这一次的循环,准备进入下一次的新循环,是某次结束的概念。而break语句是直接跳出当前循环体,结束当前循环体,是整体结束的概念。
while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
    break;
    语句3;

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

      分析:
      上述语句中,单片机从语句1开始执行到break语句,无需判断while或者for的条件,直接马上跳出循环体,执行到循环体之外的语句(N+1)。在实际项目中,break往往也是会配合if条件判断语句一起使用,比如:

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

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


48.3    break语句能跳多远
         break语句能跳多远?预知答案请先分析以下这个例子:

while(…)
{  
      语句1;   
      语句2;
        while(…)
      {   
           语句3;   
           break;
           语句4;
     }   
     语句5;
}   
语句6;

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


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


48.5   编写程序
         编写一个程序来熟悉一下continue和break语句的使用。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=0;  //观察这个数最后的变化
  6.   unsigned char b=0;  //观察这个数最后的变化
  7.   unsigned char c=0;  //观察这个数最后的变化
  8.   unsigned char d=0;  //观察这个数最后的变化

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


  10. //i<6的条件判断是在进入循环体之前判断,而i的自加1是在执行完一次循环体之后才自加的。
  11.   for(i=0;i<6;i++)  
  12.   {
  13.      a=a+1;    //被执行了6次,分别是第0,1,2,3,4,5次
  14.      if(i>=3)
  15.      {
  16.         continue;  //提前结束本次循环,准备进入下一次循环
  17.      }
  18.      b=b+1;  //被执行了3次,分别是第0,1,2次
  19.   }


  20. //i<6的条件判断是在进入循环体之前判断,而i的自加1是在执行完一次循环体之后才自加的。
  21.   for(i=0;i<6;i++)  
  22.   {
  23.      c=c+1;   //被执行了4次,分别是第0,1,2,3次
  24.      if(i>=3)
  25.      {
  26.         break; //马上跳出当前循环体
  27.      }
  28.      d=d+1;   //被执行了3次,分别是第0,1,2次
  29.   }

  30.      
  31.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  32.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  33.   GuiWdData2=c;   //把c这个变量放到窗口变量2里面显示
  34.   GuiWdData3=d;   //把d这个变量放到窗口变量3里面显示
  35.         
  36. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  37.    while(1)  
  38.    {
  39.       initial();
  40.       key_service();
  41.       display_service();
  42.    }

  43. }
复制代码


       查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
       变量a为6。
       变量b为3。
       变量c为4。     
       变量d为3。
       下节预告:for和while的循环嵌套。
(未完待续)


作者: jianhong_wu    时间: 2015-12-5 23:32
本帖最后由 jianhong_wu 于 2015-12-6 11:15 编辑

第四十九节:for和while循环体的嵌套。

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


        49.2   循环嵌套的常见用途---二维数组的应用
         前面章节讲到了二维数组a[2][3],它有6个变量,在没有学for语句之前,要依次把每个元素单独赋值清零真不容易,要写6次赋值语句如下:
a[0][0]=0;
a[0][1]=0;
a[0][2]=0;
a[1][0]=0;
a[1][1]=0;
a[1][2]=0;
         自从懂了for嵌套语句之后,直接写代码如下,简洁了许多:
  1. for(i=0;i<2;i++)  //大循环
  2. {
  3.      for(k=0;k<3;k++) //内嵌的小循环
  4.      {
  5.           a[i][k]=0;
  6.       }
  7. }
复制代码


        49.3     循环嵌套的常见用途---大延时
        以后接触单片机项目后,会经常会用到delay这个延时函数,大部分都是利用for循环来实现,小延时函数往往不用嵌套,直接如下编写:
for(k=0;k<N;k++);  
      上述的N是控制循环次数,每次循环都要消耗单片机一点时间,如果N越大需要消耗的时间就越多,起到延时的作用。但是N所能取的最大值受它所定义的类型所限制,比如是unsigned char类型时最大范围是255,是unsigned int类型时最大范围是65535,是unsigned long类型时最大范围是4294967295。如果要实现更大的延时怎么办?就可以用for的循环嵌套,利用循环嵌套次数的乘法翻倍特性,很容易编写大的延时函数。比如:
for(i=0;i<M;i++)  //大循环
{
      for(k=0;k<N;k++); //内嵌的小循环
}
      此时循环的次数是N乘以M的乘积。
      现在编写一个循环嵌套的练习程序,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=0;  //观察这个数最后的变化
  6.   unsigned char b=0;  //观察这个数最后的变化
  7.   unsigned char c=0;  //观察这个数最后的变化


  8.   unsigned char i;  //控制大循环体的条件判断变量
  9.   unsigned char k;  //控制内嵌小循环体的条件判断变量

  10.   for(i=0;i<2;i++)  //大循环
  11.   {
  12.      a=a+1;    //被执行了2次
  13.      for(k=0;k<3;k++)  //内嵌小循环
  14.      {  
  15.           b=b+1;  //被执行了6次,也就是i乘以k,2乘以3等于6.
  16.      }
  17.      c=c+1;    //被执行了2次
  18.   }

  19.      
  20.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  21.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  22.   GuiWdData2=c;   //把c这个变量放到窗口变量2里面显示

  23.         
  24. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  25.    while(1)  
  26.    {
  27.       initial();
  28.       key_service();
  29.       display_service();
  30.    }

  31. }
复制代码


          查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
         变量a为2。
          变量b为6。
          变量c为2。
          下节预告:switch语句。
(未完待续)



作者: jianhong_wu    时间: 2015-12-14 23:11
本帖最后由 jianhong_wu 于 2015-12-14 23:16 编辑

第五十节:switch语句。
         switch是非常重要的语句,我几乎所有的单片机项目都是用switch搭建程序主框架。如果说while和for是一对孪生兄弟,那么if-elseif和switch也是一对孪生兄弟,凡是用switch语句能实现的功能都可以用if-elseif实现。switch有条件分支的功能,在条件超过3个以上的条件分支功能里,switch书写会比if-else if更加直观清晰。
         50.1      switch语句的语法
         以下是switch语句常见的格式:

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

        执行顺序分析:
        单片机从switch(变量)进来,根据变量的数值大小,进入对应的case入口里。假如变量等于1,就直接进入到case 1入口,执行“语句1”后,遇到break语句就跳出整个switch程序体。假如变量等于3,单片机从switch(变量)进来,因为没有发现case 3,所以直接跳出switch程序体没有执行任何语句。多补充一句,在case 2选项中,语句2最后的break可以省略,因为case 2是最后一个case,即使没有遇到break也会遇到最下面的花括号而跳出switch程序体。上述程序功能如果用if-elseif语句来实现如下:

if(变量==0)
{
    语句0;
}
else if(变量==1)
{
    语句1;
}
else if(变量==2)
{
    语句2;
}


        50.2      switch语句的break
        大家从50.1的例子中看到了三个关键字分别是:switch,case,break。其实并不是每个case都必须要break配套,break只是起到一个出口的功能。假如没有遇到break,程序会一直往下执行,直到遇到break或者switch最下面的花括号为止。比如:

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

        执行顺序分析:
        假如此时switch(变量)的变量等于1,就直接进入到case 1入口,执行“语句1”后,没有遇到break语句,继续执行“语句2”,紧接着遇到break语句,才跳出整个switch程序体。本例子中case 1没有break语句,就继续往下执行下面case2里面的语句,直到遇到break或者最下面的花括号为止。

        50.3      switch语句的case有规定顺序吗?必须连贯吗?
switch程序体内部可以写很多case入口,这些case入口是不是必须按从小到大的顺序?是不是规定必须case数字连贯?答:没有规定顺序,也没有规定case数字连贯,case的数值只是代表入口。比如以下两种写法都是合法的:

        50.3.1    case不按从小到大的顺序:

switch(变量)   
{
     case 2:
         语句2;
         break;
     case 0:  
         语句0;
         break;
     case 1:  
         语句1;
         break;
}   

         50.3.2    case的数字不连贯
switch(变量)   
{
    case 0:  
         语句0;
         break;
     case 3:
         语句3;
         break;
     case 9:  
         语句9;
         break;
}   


         50.4      switch语句的default。
         default和break一样,也不是必须的语句,应根据程序需要来选择。default相当于if-else if-else 语句中的else,也就是当switch(变量)的变量没有匹配的case入口时,就会默认进入default入口,就像if-else if-else 语句中当前面所有的条件不满足时,就进入else语句的程序体,比如:

switch(变量)    //根据变量的数值大小从对应的case入口进来
{
     case 0:   //入口0
         语句0;
         break; //switch程序体的出口之一
     case 1:  //入口1
         语句1;
         break; //switch程序体的出口之一
     case 2: //入口2
         语句2;
         break; //switch程序体的出口之一
     default:  //当所有的case不满足,就进入default入口
         语句3;
         break;
}    //最下面的花括号也是一个switch程序体的出口之一

        执行顺序分析:
        假如switch(变量)的变量等于35,因为没有找到case 35,所以就直接从默认的default入口进来执行” 语句3”,然后遇到break语句就跳出switch程序体。上述程序功能如果用if-elseif-else语句来实现如下:

if(变量==0)
{
    语句0;
}
else if(变量==1)
{
    语句1;
}
else if(变量==2)
{
    语句2;
}
else   //相当于switch中的default
{
    语句3;
}


         50.5      switch语句中内嵌switch
         if语句也可以内嵌if语句,while语句也可以内嵌while语句,switch语句当然也可以内嵌switch。比如

switch(a)
{
   case 1:
        switch(b)
        {
             case 1:
                  Break;
             case 2:
                  Break;
        }
        Break;
   case 2:
        Break;
}

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

        50.6      switch练习程序
       现在编写一个switch的练习程序,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:


  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/



  4.   unsigned char k;   //此变量为switch的入口判断变量

  5.   unsigned char a; //观察此变量的变化来理解switch的执行顺序

  6.   a=0;
  7.   k=2;

  8.   switch(k)
  9.   {
  10.      case 0:  //入口0
  11.        a++;
  12.        break; //跳出switch
  13.      case 1:  //入口1
  14.        a++;
  15.      case 2:  //入口2,上述k等于2所以从这里进来
  16.        a++;
  17.      case 3:  //入口3
  18.        a++;
  19.      case 4:  //入口4
  20.        a++;
  21.        break;  //跳出switch
  22.      case 5:  //入口5
  23.        a++;
  24.        break;  //跳出switch
  25.      default: //入口default,相当于else。当前面没有遇到对应的case入口时,就默认进入此default入口
  26.        a++;
  27.        break;  //跳出switch
  28.   }            //最后一个switch的花括号也是跳出switch
  29.       
  30.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示

  31.   
  32.         
  33. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  34.    while(1)  
  35.    {
  36.       initial();
  37.       key_service();
  38.       display_service();
  39.    }

  40. }
复制代码


        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
        变量a为3。
        下节预告:空返回空形参的函数。
(未完待续)


作者: jianhong_wu    时间: 2015-12-20 01:20
本帖最后由 jianhong_wu 于 2015-12-20 01:25 编辑

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

        51.1   增加子函数内容后如何运用第10节模板程序调试
        前面章节讲的内容因为没有涉及到子函数,范例程序的编辑调试只需要在一个main主函数里操作就可以,原来的调试方式如下:
  1. void main() //主函数
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4. unsigned char  a;  //普通局部变量的定义     
  5.   unsigned char  b;  //普通局部变量的定义   
  6.   ……     //一些程序代码
  7.   ……
  8.   ……
  9. GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  10. GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  11.   ……      
  12. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  13.   while(1)  
  14.   {
  15.      initial();
  16.      key_service();
  17.      display_service();
  18.   }
  19. }
复制代码

         本节开始增加子函数内容,所以以后的调试方式如下:

  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. void hanshu(void);  //子函数的声明
  3. unsigned char  x;  //全局变量的定义     
  4. unsigned char  y;  //全局变量的定义   
  5. void hanshu(void)  //子函数的定义
  6. {
  7.    unsigned char  c;  //普通局部变量的定义子函数的语句;
  8. ……
  9. }
  10. void main() //主函数
  11. {
  12. unsigned char  a;  //普通局部变量的定义     
  13.   unsigned char  b;  //普通局部变量的定义   
  14. ……     //其它一些程序代码
  15. hanshu() ; //子函数的调用
  16.   ……     //其它一些程序代码
  17. GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  18. GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  19.   ……      
  20. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  21.   while(1)  
  22.   {
  23.      initial();
  24.      key_service();
  25.      display_service();
  26.   }
  27. }
复制代码

        对比原来,现在的“C语言学习区域的开始”分割线往上移动增大了范围,其它部分内容不用变,还是直接在第10节的模板程序里更改和添加这部分的内容就可以了。


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

  1.   /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. void hanshu(void);   //子函数的声明
  3. void hanshu(void)    //子函数的定义
  4. {
  5. ……     //子函数的代码语句
  6. }
  7. void main() //主函数
  8. {
  9. hanshu() ;      //子函数的调用
  10. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  11.   while(1)  
  12.   {
  13.       ……
  14.   }
  15. }
复制代码



         51.3   子函数在主函数里的执行顺序
        子函数被其它函数调用时,子函数的名字就相当于一个跳转地址,而子函数的定义就是要跳转的实际地址,单片机在主函数里遇到子函数名称,就直接跳转到子函数定义那里执行子函数定义部分的代码,执行完子函数后再返回到主函数,继续执行子函数名称后面部分的代码,请看下面这个代码的执行顺序,就可以一目了然:

  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. void hanshu(void);   //子函数的声明
  3. void hanshu(void)    //子函数的定义
  4. {
  5. 语句1;
  6. 语句2;
  7. }
  8. void main() //主函数
  9. {
  10. 语句3;
  11. hanshu() ;      //子函数的调用
  12. 语句4;
  13. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  14.   while(1)  
  15.   {
  16.       ……
  17.   }
  18. }
复制代码

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

        下节预告:普通局部变量和全局变量在函数里的作用域和优先级。
(未完待续)


作者: jianhong_wu    时间: 2015-12-27 02:04
本帖最后由 jianhong_wu 于 2015-12-27 02:07 编辑

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


        52.1  什么是局部变量,什么是全局变量。
        局部变量就是在函数内部定义的变量,全局变量就是在函数外面定义的变量。下面举的例子能很清晰的说明局部变量和全局变量的特点:

  1. unsigned char a;     //在函数外面定义的,所以是全局变量。
  2.      void main()
  3. {
  4.           unsigned char b; //在函数内部定义的,所以是局部变量。
  5. }
复制代码


        52.2  局部变量和全局变量的内存模型。
        我在第三节里讲到,单片机内存包括ROM和RAM两部分,ROM存储的是单片机程序中的指令和一些不可更改的常量数据,而RAM存放的是可以被更改的数据,这些可以被程序更改的数据就叫做变量。变量包括局部变量和全局变量,但是局部变量和全局变量在RAM中的数据结构有本质不同。欲知不同在哪,首先要从RAM说起。对于大部分的单片机,RAM都至少分成两部分,一部分是给全局变量,一部分是给局部变量。给全局变量那部分的RAM叫全局数据区,给局部变量那部分的RAM叫做“栈”,因为我后面会用宾馆来比喻“栈”,所以为了方便记忆,可以把“栈”想象成 “客栈”来记忆。 全局数据区就像你自己家的房间,是唯一的,一个房间的地址只能你一个人住,而且是永久的,所以说每个全局变量都有唯一对应的RAM地址,不可能重复的。而“栈”就像宾馆客栈,每天晚上住的人不一样,每个人在里面居住的时间是有期限的,不是长久的,一个房间的地址每天可能住进不同的人,不是唯一的。全局区的全局变量拥有永久产权,“栈”区的局部变量只能临时居住在宾馆客栈,地址不是唯一的,有期限的。全局变量像私人区,局部变量像公共区。


         52.3  借用宾馆客栈比喻局部变量的期限。
  1. void hanshu(void);   //子函数的声明
  2. void hanshu(void)    //子函数的定义
  3. {
  4.      unsigned char a;   //局部变量
  5.      a=1;
  6. }
  7. void main() //主函数
  8. {
  9. hanshu() ;      //子函数的调用
  10. }
复制代码

          请看上面的程序例子,单片机从主函数main往下执行,首先遇到hanshu子函数的调用,所以就跳到hanshu函数的定义那里开始执行,此时的局部变量a开始被分配在RAM的栈区的某个地址,相当于你入住宾馆被分配到某个房间。单片机执行完子函数hanshu后,局部变量a在RAM的栈区所分配的地址被收回,局部变量a消失,被收回的RAM地址可能会被系统重新分配给其它被调用的函数的局部变量,此时相当于你离开宾馆,原来在宾馆居住那个房间被收回,你离开宾馆,从此你跟那个宾馆的房间没有啥关系,它可能会被宾馆老板重新分配给其他的客人入住。结论:局部变量的作用域就是它所在函数的内部范围,而全局变量的作用域是永久性不受范围限制的。


          52.4  局部变量和全局变量的优先级。
          上面最后一段话中得到一个结论:局部变量的作用域就是它所在函数的内部范围,而全局变量的作用域是永久性并且不受范围限制的。那么问题来,假如局部变量和全局变量的名字重名了,此时函数内部执行的变量到底是局部变量还是全局变量?这个问题就涉及到优先级。注意,当面对同名的局部变量和全局变量时,函数内部执行的变量是局部变量,也就是局部变量在函数内部要比全局变量的优先级高。

           52.4.1   请看第一个例子:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. unsigned char a=5;    //全局变量的定义
  3. void main() //主程序
  4. {
  5.     unsigned char a=2;  //局部变量的定义
  6.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示,此时的a是局部变量2,而不是5

  7. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  8.    while(1)
  9.    {
  10.       initial();
  11.       key_service();
  12.       display_service();
  13.    }

  14. }
复制代码

           此时输出显示2。


           52.4.2   请看第二个例子:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. void hanshu(void); //函数声明
  3. unsigned char a=5;    //全局变量的定义
  4. void hanshu(void)   //函数定义
  5. {
  6.     unsigned char a=3;  //局部变量的定义
  7. }
  8. void main() //主程序
  9. {
  10.     unsigned char a=2;   //局部变量的定义
  11.     hanshu();  //子函数被调用
  12.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示,此时的a是局部变量2
  13. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  14.    while(1)
  15.    {
  16.       initial();
  17.       key_service();
  18.       display_service();
  19.    }

  20. }
复制代码

           此时输出显示2。
           分析:虽然子函数hanshu内部也有一个a=3,但是当hanshu被调用完结束后,这个a就消失不起作用了,所以此时显示的是main函数内部的局部变量。


           52.4.3   请看第三个例子:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/

  2. void hanshu(void); //函数声明

  3. unsigned char a=5;    //全局变量的定义

  4. void hanshu(void)   //函数定义
  5. {
  6.     unsigned char a=3;  //局部变量的定义
  7. }


  8. void main() //主程序
  9. {

  10.     hanshu();  //子函数被调用

  11.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示,此时的a是全部变量5

  12.       
  13. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  14.    while(1)
  15.    {
  16.       initial();
  17.       key_service();
  18.       display_service();
  19.    }

  20. }
复制代码

          此时输出显示5。
          分析:子函数hanshu 里面的a的作用域只是它本身,因此子函数hanshu里面的a没办法影响到其它函数的a。而main函数内部没有了局部变量,那么此时起作用的只能是全局变量,因此显示5。假如此时连全局变量也没有了怎么办?这种情况是语法错误不能编译通过,因为任何变量的调用都必须要被先定义,定义有两种,要么是在函数内被定义成局部变量,要么是在函数外被定义成全局变量。


          52.5   局部变量和全局变量的总结归纳.
         (1)每定义一个新的全局变量,就意味着多开销一个新的RAM内存。而每定义一个局部变量,只要在函数内部所定义的局部变量总数不超过单片机的“栈”区,此时的局部变量不开销新的RAM内存,因为局部变量是临时借用“栈”区的,使用后就还给“栈”,“栈”是公共区,可以重复利用,可以服务若干个不同的函数内部局部变量。
         (2)单片机每次进入执行函数时,局部变量都会被初始化改变,而全局变量则不会被初始化,全局变量是一直保存之前最后一次更改的值。


下节预告:空返回空形参的函数。
(未完待续)


作者: jianhong_wu    时间: 2016-1-4 05:05
本帖最后由 jianhong_wu 于 2016-1-10 01:36 编辑

第五十三节:函数的作用和四种常见书写类型。
第五十三节_pdf文件.pdf (109.18 KB, 下载次数: 2464)

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

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

【53.2   无输出无输入的函数。】

  1. unsigned char a;  //此变量用来接收最后相加结果的和。
  2. unsigned char g=2;
  3. unsigned char h=3;
  4. void hanshu(void)  //无输出无输入函数的定义。
  5. {
  6.    a=g+h;
  7. }
  8. main()
  9. {
  10.     hanshu();  //函数调用时的样子。
  11. }
复制代码
分析:
         void hanshu(void),此函数名的前缀是void,括号内也是void,属于无输出无输入函数。这类函数表面看是无输出无输入,其实在实际应用中也可以通过全局变量来输入输出,比如上面的例子就是靠a,g,h这三个全局变量来传递信息,只不过表达方式上像隐藏起来一样没有那么直观。

53.3   无输出有输入的函数。】

  1. unsigned char b;  //此变量用来接收最后相加结果的和。
  2. void hanshu(unsigned char i,unsigned char k)   //无输出有输入函数的定义
  3. {
  4.    b=i+k;
  5. }
  6. main()
  7. {
  8.     hanshu(2,3);  //函数调用时的样子。
  9. }
复制代码
分析:
         void hanshu(unsigned char i,unsigned char k),此函数名的前缀是void,括号内是(unsigned char i,unsigned char k),属于无输出有输入的函数。括号的两个变量ik是函数内的局部变量,也是跟对外的桥梁接口,它们有一个专业的名称叫形参。外部要调用此函数时,只要给括号填入对应的变量或者数值,这些变量和数值就会被复制一份传递给作为函数形参的局部变量,从而外部调用者跟函数内部就发生了数据信息的传递。这种书写方式的特点是把输入接口封装了出来。

53.4   有输出无输入的函数。】

  1. unsigned char c;   //此变量用来接收最后相加结果的和。
  2. unsigned char m=2;
  3. unsigned char n=3;
  4. unsigned char hanshu(void)     //有输出无输入函数的定义。
  5. {
  6. unsigned char p;
  7. p=m+n;
  8. return p;
  9. }
  10. main()
  11. {
  12.     c= hanshu();  //函数调用时的样子。
  13. }
复制代码
分析:
         unsigned char hanshu(void),此函数名的前缀是unsigned char类型,括号内是void,属于有输出无输入的函数。函数前缀的unsigned char表示此函数最后退出时会返回一个unsigned char类型的数据给外部调用者。而且这类函数内部必须有一个return语句配套,表示立即退出当前函数并且返回某个变量或者常量的数值给外部调用者。这种书写方式的特点是把输出接口封装了出来。

53.5   有输出有输入的函数。】

  1. unsigned char d;    //此变量用来接收最后相加结果的和。
  2. unsigned char hanshu(unsigned char r,unsigned char s)     //有输出无输入函数的定义
  3. {
  4. unsigned char t;
  5. t=r+s;
  6. return t;
  7. }
  8. main()
  9. {
  10.     d= hanshu(2,3);  //函数调用时的样子。
  11. }
复制代码
分析:
        unsigned char hanshu(unsigned char r,unsigned char s),此函数名的前缀是unsigned char类型,括号内是(unsigned char r,unsigned char s),属于有输出有输入的函数。输入输出的特点跟前面介绍的函数一样,不多讲。这种书写方式的特点是把输出和输入接口都封装了出来。

53.6   调用函数时特别要注意。】

        注意:在函数调用时,凡是“void”关键词都要省略,否则编译不通过。

53.7   练习程序。】

       现在把上述的4加法函数写成一个程序,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,添加和修改main程序代码如下:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. //第1种:无输出无输入函数的声明。
  3. void hanshu_1(void);  
  4. //第2种:无输出有输入函数的声明。
  5. void hanshu_2(unsigned char i,unsigned char k);   
  6. //第3种:有输出无输入函数的声明。
  7. unsigned char hanshu_3(void);     
  8. //第4种:有输出无输入函数的声明。
  9. unsigned char hanshu_4(unsigned char r,unsigned char s);  
  10. //第1种:无输出无输入
  11. unsigned char a;  //此变量用来接收最后相加结果的和。
  12. unsigned char g=2;
  13. unsigned char h=3;
  14. //第2种:无输出有输入
  15. unsigned char b;  //此变量用来接收最后相加结果的和。
  16. //第3种:有输出无输入
  17. unsigned char c;   //此变量用来接收最后相加结果的和。
  18. unsigned char m=2;
  19. unsigned char n=3;
  20. //第4种:有输出有输入
  21. unsigned char d;    //此变量用来接收最后相加结果的和。
  22. //第1种:无输出无输入函数的定义。
  23. void hanshu_1(void)  
  24. {
  25.    a=g+h;
  26. }
  27. //第2种:无输出有输入函数的定义
  28. void hanshu_2(unsigned char i,unsigned char k)   
  29. {
  30.    b=i+k;
  31. }
  32. //第3种:有输出无输入函数的定义。
  33. unsigned char hanshu_3(void)   
  34. {
  35. unsigned char p;
  36. p=m+n;
  37. return p;
  38. }
  39. //第4种:有输出无输入函数的定义
  40. unsigned char hanshu_4(unsigned char r,unsigned char s)  
  41. {
  42. unsigned char t;
  43. t=r+s;
  44. return t;
  45. }
  46. void main() //主程序
  47. {
  48.     hanshu_1();       //第1种:无输出无输入函数。
  49.     hanshu_2(2,3);      //第2种:无输出有输入函数。
  50.     c=hanshu_3();      //第3种:有输出无输入函数。
  51.     d=hanshu_4(2,3);     //第4种:有输出无输入函数。
  52.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示。
  53.     GuiWdData1=b;   //把b这个数值放到窗口变量1里面显示。
  54.     GuiWdData2=c;   //把c这个数值放到窗口变量2里面显示。
  55.     GuiWdData3=d;   //把d这个数值放到窗口变量3里面显示。
  56.         
  57. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  58.    while(1)  
  59.    {
  60.       initial();
  61.       key_service();
  62.       display_service();
  63.    }
  64. }
  65.       
复制代码
        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0上坚鸿51学习板观察程序执行的结果如下:
       变量a为5。
       变量b为5。
       变量c为5。
       变量d为5。

       下节预告:return语句在函数中的作用
(未完待续)




作者: jianhong_wu    时间: 2016-1-10 01:19
本帖最后由 jianhong_wu 于 2016-1-10 01:35 编辑

第五十四节:return语句在函数中的作用以及容易被忽略的四个功能
第五十四节_pdf文件.pdf (117.79 KB, 下载次数: 2383)

【54.1   return深入讲解

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

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

       下面的书写格式是合法的:
  1. void hanshu(void)  //无输出/函数的定义。
  2. {
  3. 语句1;
  4. return; //立即退出当前函数。
  5. 语句2;
  6. return; //立即退出当前函数。
  7. 语句3;
  8. return; //立即退出当前函数。
  9. }
复制代码
分析:
        当hanshu此函数被调用时,单片机从“语句1”往下执行,当遇到第一个return语句后,马上退出当前函数。在此函数里,后面的“语句2”等代码永远不会被执行到。

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

       下面的书写格式是合法的:
  1. void hanshu(void)  //无输出/函数的定义。
  2. {
  3. 语句1;
  4. while(1)  //第一个循环
  5. {
  6.     while(1)  //第二个循环中的循环
  7.     {
  8.           return; //立即退出当前函数。
  9.     }
  10.     语句2;
  11.     return; //立即退出当前函数。
  12. }
  13. 语句3;
  14.     return; //立即退出当前函数。
  15. }
复制代码
分析:
        当hanshu此函数被调用时,单片机从“语句1”往下执行,先进入第一个循环,接着进入第二个循环中的循环,然后遇到第一个return语句,马上退出当前函数。在此函数里,后面的“语句2”等代码也是永远不会被执行到。虽然表面看起来有那么多可怕的循环约束着,但是一旦碰上return语句都是浮云,立刻退出当前函数。

【54.4   “有输出函数”时是什么样子的?】

        把上面例子中“无输出函数”改成“有输出函数”后:
  1. unsigned char hanshu(void)  //有输出/函数的定义。
  2. {
  3. unsigned char a=9;
  4. 语句1;
  5. while(1)  //第一个循环
  6. {
  7.     while(1)  //第二个循环中的循环
  8.     {
  9.           return a; //返回a变量的值,并且立即退出当前函数。
  10.     }
  11.     语句2;
  12.     return a; //返回a变量的值,并且立即退出当前函数。
  13. }
  14. 语句3;
  15. return a; //返回a变量的值,并且立即退出当前函数。
  16. }
复制代码
分析:
       因为此函数是“有输出/函数”,所以return语句后面必须配套一个变量或者常量,而执行顺序跟前面54.3的例子是一样的。当hanshu函数被调用时,单片机从“语句1”往下执行,先进入第一个循环,接着进入第二个循环中的循环,然后遇到第一个“return a”语句,马上退出当前函数。在此函数里,后面的“语句2”等代码也是永远不会被执行到的。再一次说明了,return语句不仅有返回某数的功能,还有立即退出的重要功能。

【54.5   项目应用时,中间的return语句往往是跟if语句搭配使用的。】

       前面的例子只是为了解释return语句的执行顺序和功能,实际项目中,如果中间有多个return语句,中间的return语句不可能像前面的例子那样单独使用,它往往是跟if语句一起搭配使用的,否则单独用return就没有什么意义。比如:
  1. void hanshu(void)  //无输出/函数的定义。
  2. {
  3. 语句1;
  4. if(某条件满足)
  5. {
  6.    return; //立即退出当前函数。
  7. }
  8. 语句2;
  9. if(某条件满足)
  10. {
  11.    return; //立即退出当前函数。
  12. }
  13. 语句3;
  14. }
复制代码
分析:
        单片机从“语句1”开始往下执行,至于在哪个“return”语句处退出当前函数,就要看哪个if的条件满不满足了,如果所有的if的条件都不满足,此函数会一直执行完最后的“语句3”才退出当前函数。

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

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

54.7   练习程序。】

        写一个简单的除法函数,在除法运算中,除数不能为0,如果发现除数为0,就立即退出当前函数,并且返回运算结果默认为0最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,添加和修改main程序代码如下:
  1. /*---C语言学习区域的开始---------------------------------------------*/
  2. //函数的声明。
  3. unsigned int chu_fa(unsigned int bei_chu_shu,unsigned int chu_shu);
  4. unsigned int a;//此变量用来接收除法的运算结果。
  5. unsigned int b;//此变量用来接收除法的运算结果。
  6. //函数的定义。
  7. unsigned int chu_fa(unsigned int bei_chu_shu,unsigned int chu_shu)
  8. {
  9.     unsigned int shang;  //返回的除法运算结果:商。
  10.     if(0==chu_shu)   //如果除数等于0,就立即退出当前函数,并返回0
  11.     {
  12.         return 0; // 退出当前函数并且返回0.此时后面的代码不会被执行。
  13.     }
  14.    
  15.     shang=bei_chu_shu/chu_shu;  //除法运算的算法
  16.     return shang;  //返回最后的运算结果:商。并且退出当前函数。
  17. }
  18. void main() //主程序
  19. {
  20.    
  21.     a=chu_fa(128,0);  //函数调用。128除以0,把商返回给a变量。
  22.     b=chu_fa(128,2);  //函数调用。128除以2,把商返回给b变量。
  23.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示。
  24.     GuiWdData1=b;   //把b这个数值放到窗口变量1里面显示。
  25.         
  26. /*---C语言学习区域的结束---------------------------------------------*/
  27.    while(1)  
  28.    {
  29.       initial();
  30.       key_service();
  31.       display_service();
  32.    }
  33. }
复制代码
        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0上坚鸿51学习板观察程序执行的结果如下:
       变量a为0。
       变量b为64。

       下节预告:static静态局部变量在函数中的重要作用。
(未完待续)



作者: jianhong_wu    时间: 2016-1-17 01:58
本帖最后由 jianhong_wu 于 2016-1-17 11:40 编辑

第五十五节:static静态局部变量在函数中的重要作用。
第五十五节_pdf文件.pdf (144.04 KB, 下载次数: 2405)

【55.1   变量前加入static关键词后发生“化学反应”】


          前面介绍了两类变量,一类全局变量,一类局部变量。定义时,在任何一类变量前面加入static关键词,变量原有的特性都会发生某些变化,此时static像化学里的催化剂,具有神奇的功能。加static关键词的书写格式如下:
  1.      static unsigned char a;  //全局变量前加的static关键词
  2.      void hanshu(void)
  3.      {
  4.          static unsigned char i;   //局部变量前加的static关键词
  5.      }
复制代码


【55.2   在全局变量前加static】


          static读作“静态”,全局变量前加static,称为静态全局变量。静态全局变量和普通全局变量的功能大体相同,仅在有效范围(作用域)方面有差异。假设整个工程有多个文件组成,普通全局变量的有效范围能覆盖全部文件,在任何一个文件里都畅通无阻。而静态全局变量只能在当前定义的那个文件里起作用,活动范围完全被限定在一个文件,彷佛被加了紧箍咒,由不得你任性。这部分的内容有个大致印象就可以,暂时不用深入研究,等以后学到多文件编程时再关注,因为我当前的程序例子只有一个源文件,还没涉及多文件编程。

【55.3   在局部变量前加static】

         这是本节重点。我常把局部变量比喻宾馆的客房,客人入住时被分配在哪间客房是随机临时安排的,第二天退房时宾馆会把客房收回继续分配给下一位其他的客人,是临时公共区。而加入static后的局部变量,发生了哪些变化?加入static后的局部变量,称为静态局部变量。静态局部变量就像宾馆的VIP客户,VIP客户财大气粗,把宾馆分配的客房永远包了下来,永远不许再给其它客人入住。总结了静态局部变量的两个重要特性:
        第一个,静态局部变量不会在函数调用时被初始化,它只在单片机刚上电时被编译器初始化了一次,因为它的内存模型不是被分配在“栈”,而是在全局变量同一类数据区,拥有自己唯一的地址。但是跟全局变量又有差别,全局变量的有效范围(作用域)是整个工程,而静态局部变量毕竟是“局部”,仅局限于当前函数。而普通局部变量,众所周知,每次被函数调用时,都会被重新初始化。
        第二个,每次函数调用时,静态局部变量比普通局部变量少开销一条潜在的“初始化语句”,原因是普通局部变量每次被函数调用时都要重新初始化,而静态局部变量不用进行这个操作。也就是说,静态局部变量比普通局部变量的效率高一点,虽然这个“点”的时间开销微不足道,但是不留意这“点”,写程序时容易出现瑕疵。


【55.4   静态局部变量的应用场合】


        静态局部变量适用在那些频繁调用的函数,比如main函数主循环while(1)里直接调用的所有函数,还有以后讲到的定时器中断函数,等等。因为静态局部变量每次被调用都不会被重新初始化,用在这类函数时就省去了每次初始化语句的时间。还有就是那些规定不能被函数初始化的场合,比如在很多用switch搭建的程序函数里,这类switch程序俗称为状态机思路。


【55.5   能用全局变量替代静态局部变量吗】


         能用全局变量替代静态局部变量吗?能。哪怕在整个程序里全部用全局变量都可以。全局变量是一把牛刀,什么场合都用牛刀虽然也能解决问题,但是显得鲁莽没有条理。尽量把全局变量,普通局部变量,静态局部变量各自优势充分发挥出来才是编程之道。能用局部变量的尽量用局部变量,这样可以减少全局变量的使用。当局部变量帮分担一部分工作时,最后全局变量只起到一个作用,那就是在各函数之间传递信息。局部变量与全局变量的分工定位明确了,程序代码阅读起来就没有那么凌乱,思路也清晰很多。


【55.6   程序分析】

         编写一个程序来学习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,添加和修改main程序代码如下:
  1. /*---C语言学习区域的开始-----------------------------------------*/
  2. unsigned char hanshu(void);         //函数声明
  3. unsigned char hanshu_static(void);  //函数声明
  4. unsigned char hanshu(void)  //函数定义
  5. {
  6.    unsigned char i=0;   //普通局部变量,每次函数调用都被初始化为0.
  7.    i++;  //i自加1
  8.    return i;
  9. }
  10. unsigned char hanshu_static(void)  //函数定义
  11. {
  12.    static unsigned char i=0;   //静态局部变量,只在上电是此初始化语句才起作用。
  13.    i++;  //i自加1
  14.    return i;
  15. }
  16. void main() //主程序
  17. {
  18.   unsigned char a; //用来接收函数返回的结果。
  19.   unsigned char b;
  20.   unsigned char c;
  21.   unsigned char d; //用来接收函数返回的结果。
  22.   unsigned char e;
  23.   unsigned char f;
  24.   //下面函数内的i是普通局部变量。
  25.   a=hanshu();  //函数内的i每次重新初始化为0,再自加1,所以a等于1。
  26.   b=hanshu();  //函数内的i每次重新初始化为0,再自加1,所以b等于1。
  27.   c=hanshu();  //函数内的i每次重新初始化为0,再自加1,所以c等于1。
  28.   //下面函数内的i是静态局部变量,第一次上电后默认为0,就不会再被初始化,
  29.   d=hanshu_static(); //d由0自加1后等于1。
  30.   e=hanshu_static(); //e由1自加1后等于2。
  31.   f=hanshu_static(); //f由2自加1后等于3。
  32.      GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示,下同。
  33.   GuiWdData1=b;  
  34.   GuiWdData2=c;  
  35.   GuiWdData3=d;
  36.   GuiWdData4=e;  
  37.   GuiWdData5=f;  
  38.         
  39. /*---C语言学习区域的结束----------------------------------------*/
  40.    while(1)  
  41.    {
  42.       initial();
  43.       key_service();
  44.       display_service();
  45.    }
  46. }
  47.       
复制代码


         查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0上坚鸿51学习板观察程序执行的结果如下:
         变量a为1。
         变量b为1。
         变量c为1。

         变量d为1。
         变量e为2。
         变量f 为3。

【55.7   为什么中止此连载帖的更新了】

    我以前一直想写两本书,一本讲单片机入门基础,一本讲单片机程序框架。现在发现,单片机基础和程序框架并没有明显的分水岭,基础中有框架,框架中有基础,应该合二为一,读起来才会连贯舒畅。所以我决定中止当前已写到55节的《从业十年,教你单片机入门基础》连载帖,另外新开一个连载帖叫《从单片机基础到程序框架(连载)》,希望大家关注我的新连载帖。
    再提一下我2014年写的《从业将近十年,手把手教你单片机程序框架》,一方面受到很多网友的好评,另一方面也有一些热心网友提出了宝贵的意见,我今天看来,确实还有一些可待改进的地方。本来计划在2017年重写《……单片机程序框架》那个老帖,现在看来不用那么折腾了,只要把《……单片机程序框架》的内容也整合到新开的帖子里就可以了,这样对我也比较省事。我的时间计划是,先花4年时间写一
个初稿,然后再花2年时间重写一次,最后再花1年时间整理成书,整个过程大概7年时间左右,今年是2016年,估计到2023年左右就可以新书出版了。
    感谢各位朋友的支持。











欢迎光临 独闷闷网 (http://www.dumenmen.com/) Powered by Discuz! X3.2