51系列单片机的编程语言常用的有两种,一种是汇编语言,一种是C语言。汇编语言的机器代码生成效率很高但可读性并不强,复杂一点的程序就更是难读懂,而C语言在大多数情况下其机器代码生成效率和汇编语言相当,但可读性和可移植性却远远超过汇编语言,而且C语言还可以嵌入汇编来解决高时效性的代码编写问题。对于开发周期来说,中大型的软件编写用C语言的开发周期通常要比用汇编语言短很多。
C语言是结构化语言,有丰富的数据类型,便于维护管理,与汇编语言相比,C语言的优点如下:
(1)不要求编程者详细了解单片机的指令系统,但需了解单片机的存储器结构。
(2)寄存器分配、不同存储器的寻址及数据类型等细节可由编译器管理。
(3)结构清晰,程序可读性强。
(4)编译器提供了很多标准库函数,具有较强的数据处理能力。
C语言仅是一个开发工具,其本身并不难,难的是如何在开发庞大系统时灵活运用C语言的正确逻辑编写出结构完整的程序,因此,对于初学者而言,要立足于在实际应用中去学习C语言的基础知识,在实践中逐渐提高自己的编程技巧。
一、C语言的基本结构
C语言程序以函数形式组织程序结构,C程序中的函数是完成某个特殊任务的子程序段,组成一个程序若干函数可以保存在一个源程序文件中,也可以保存在几个源程序文件中,最后再将它们连接在一起,C语言源程序的扩展名为“.c”。
下面以8LED控制程序为例,来说明C程序的基本组成结构。
从以上程序可看出,C程序的组成结构格式如下:
一个C语言源程序是由一个或若干个函数组成,每一个函数完成相对独立的功能。每个C程序都必须有(且仅有)一个主函数main( ),程序的执行总是从主函数开始,调用其他函数后返回main( )函数,不管函数排列顺序如何,最后在主函数中结束整个程序。
C语言程序中可以有预处理命令,预处理命令通常放在源程序的最前面。
C语言程序使用“;”作为语句的结束符,一条语句可以多行书写,也可以一行书写多条语句。
C程序基本结构如图2.15所示。
图2.15 C程序基本结构
二、标示符和关键字
1.标示符
标识符是用来标识源程序中某个对象的名字的,这些对象可以是语句、数据类型、函数、变量、数组,等等。C语言规定标识符由字符串、数字和下划线等组成,且第一个字符必须是字母或下划线。C语言中大写字母与小写字母被认为是两个不同的字符,如在头文件“reg52.h”中定义的SFR均为大写字母,若在使用时写了小写字母,编译时便会有错误提示。标识符在命名时应当简单,含义清晰,这样有助于阅读理解程序。在C51编译器中,只支持标识符的前32位为有效标识。
2.关键字
关键字是编程语言保留的特殊标识符,它们具有固定名称和含义,在程序编写中不允许标识符与关键字相同。在Keil C51中的关键字除了有ANSI C标准的32个关键字外,还根据51单片机的特点扩展了关键字。
标准和扩展关键字见表2.14和表2.15。
表2.14 ANSIC标准关键字
续表
表2.15 C51编译器的扩展关键字
续表
三、数据类型
1.数据类型
C语言的数据结构是以数据类型决定的,数据类型可分为基本数据类型和复杂数据类型,复杂数据类型是由基本数据类型构造而成。C语言的数据类型如图2.16所示。
图2.16 C语言数据类型
在标准C语言中基本的数据类型为char、int、long、float和double。对于Keil C51编译器来说,double型和float型相同。表2.16中列出的是Keil C51编译器所支持的数据类型。
表2.16 Keil C51编译器所支持的数据类型
续表
下面对C51中常用的数据类型说明如下:
(1)char字符类型。
char类型的长度是一个字节,通常用于定义处理字符数据的变量或常量。分无符号字符类型unsigned char和有符号字符类型signed char,默认值为signed char类型。unsigned char类型用字节中所有的位来表示数值,可以表达的数值范围是0~255。signed char类型用字节中高位字节表示数据的符号,“0”表示正数,“1”表示负数,负数用补码表示。所能表示的数值范围是-128~+127。unsigned char常用于处理ASCII字符或用于处理小于或等于255的整型数。
(2)int整型。
int整型长度为两个字节,用于存放一个双字节数据。分有符号int整型数signed int和无符号整型数unsigned int,默认值为signed int类型。signed int表示的数值范围是-32768~+32767,字节中高位表示数据的符号,“0”表示正数,“1”表示负数。unsigned int表示的数值范围是0~65535。
(3)bit位标量。
bit位标量是C51编译器的一种扩充数据类型,利用它可定义一个位标量,但不能定义位指针,也不能定义位数组。它的值是一个二进制位,不是0就是1。
(4)sfr特殊功能寄存器。
sfr也是一种扩充数据类型,值域为0~255。利用它可以访问51单片机内部的所有特殊功能寄存器。
C51能够识别单片机存储单元的地址,但却不能识别SFR的符号名称,如用sfr P2=0xA0;定义P2为P2端口在片内的寄存器,在后面的语句中用以用P2=0x55;之类的语句来操作特殊功能寄存器。这样与单片机对应的SFR名称相同,便于理解。
(5)sfr16 16位特殊功能寄存器。
sfr16占用两个内存单元,值域为0~65535。sfr16和sfr一样用于操作特殊功能寄存器,所不同的是它用于操作占两个字节的寄存器,如定时器T0和T1。
(6)sbit可寻址位。
sbit同样是C51中的一种扩充数据类型,利用它可以访问芯片内部的RAM中的可寻址位或特殊功能寄存器中的可寻址位。如定义sbit P1_1=0x91,这样在以后的程序语句中就可以用P1_1来对P1.1引脚进行读写操作了。通常这些可以直接使用系统提供的预处理文件,里面已定义好各特殊功能寄存器的简单名字,直接引用可以省去一点时间。
C51编译器中已将特殊功能寄存器及其可位寻址的位,在头文件reg52.h中做了定义。文件内容如下:
2.数据类型之间的转换
在C语言程序的表达式或变量的赋值运算中,有时会出现运算对象的数据类型不一样的情况,C语言程序允许在标准数据类型之间隐式转换,隐式转换按以下优先级别(由低到高)自动进行:
bit→char→int→long→float
signed→unsigned
一般来说,如果有几个不同类型的数据同时运算,先将低级别类型的数据转换成高级别类型,再做运算处理,并且运算结果为高级别类型数据。C语言除了能对数据类型做自动的隐式转换之外,还可以采用强制类型转换符“(类型)”对数据类型做显式的人为转换。
四、常量和变量
1.常量
常量是在程序执行过程中其值不能改变的量。常量的数据类型有整型、字符型、浮点型等,C51编译器扩充了位(bit)标量。
C51中常用的常量数据类型说明:
(1)整型常量可以表示为十进制形式,如123、0、-89等。十六进制形式,以0x开头,如0xAA、-0x3B等。八进制形式,以0开头,如023、067等。
一个整型常量的后面若加一个字母u,就认为是unsigned int型,若加一个字母l或L,就认为是long int型。
(2)字符型常量是单引号内的字符,如‘a’、‘D’、‘?’等,以及不可以显示的控制字符。
(3)字符串型常量由双引号内的字符组成,如“keil”、“89c52”等。当引号内的没有字符时,为空字符串。在C51中字符串常量是作为字符类型数组来处理的,在存储字符串时系统会在字符串尾部加上‘\0’以作为该字符串的结束符。字符串常量“A”和字符常量‘A’是不同的,前者在存储时多占用一个字节的字间。
(4)位标量。它的值是一个二进制量,即0或1。
2.变量
变量就是一种在程序执行过程中其值能不断变化的量。要在程序中使用变量必须先用标识符作为变量名,并指出所用的数据类型和存储模式,这样编译系统才能为变量分配相应的存储空间。定义一个变量的格式如下:
在定义格式中除了数据类型和变量名表是必要的,其他都是可选项。
可选项中存储种类是指变量在程序执行过程中的作用范围。C51变量的存储种类有4种,分别是自动(auto)、外部(extern)、静态(static)和寄存器(register)。简介如下:
(1)auto:使用auto定义的变量称为自动变量。其作用范围在定义它的函数体或复合语句内部,当定义它的函数体或复合语句执行时,C51才为该变量分配内存空间,当函数调用结束返回或复合语句执行结束时,自动变量所占用的内存空间释放。自动变量一般分配在内存的堆栈空间中。定义变量时若省略存储种类,该变量则默认为自动(auto)变量。
(2)extern:使用extern定义的变量称为外部变量。在一个函数体内要使用一个已在该函数体外或别的程序中定义过的外部变量时,该变量在该函数体内要用extern说明。外部变量被定义后分配固定的内存空间,在整个程序执行时间内都有效,直到程序结束才释放。
(3)static:使用static定义的变量称为静态变量。它又分为内部静态变量和外部静态变量。在函数体内部定义的静态变量为内部静态变量,它在对应的函数体内有效,一直存在,但在函数体外不可见,这样不仅使变量在定义它的函数体外被保护,还可以实现当离开函数时值不被改变。外部静态变量是在函数外部定义的静态变量,它在程序中一直存在,但在定义的范围之外是不可见的,如在多文件或多模块处理中,外部静态变量只在文件内部或模块内部有效。
(4)register:使用register定义的变量称为寄存器变量。它定义的变量存放在CPU内部的寄存器中,处理速度快,但数目少。C51编译器编译时能自动识别程序中使用频率最高的变量,并自动将其作为寄存器变量,用户可以无须专门声明。
一个变量的数据类型后,还可选择说明该变量的存储器类型。存储器类型的说明就是指定该变量在C51硬件系统中所使用的存储区域,并在编译时准确的定位。表2.17中是Keil C51所能识别的存储器类型。
表2.17 存储器类型
如果省略存储器类型,系统则会按编译模式SMALL,COMPACT或LARGE所规定的默认存储器类型去指定变量的存储区域。
表2.18 C51支持的存储器编译模式
SMALL存储模式把所有函数变量和局部数据段放在8051系统的内部数据存储区,这使访问数据非常快,但SMALL存储模式的地址空间受限。在写小型的应用程序时,因为访问速度快,变量和数据放在data内部数据存储器中是很好的,但在较大的应用程序中,data区最好只存放小的变量、数据或常用的变量,而大的数据则放置在别的存储区域。
COMPACT存储模式中所有的函数和程序变量和局部数据段定位在8051系统的外部数据存储区。
LARGE存储模式所有函数和过程的变量和局部数据段都定位在8051系统的外部数据区,外部数据区最多可有64KB,这要求用DPTR数据指针访问数据。
C51编译器中变量存储模式的设置见图2.17。
图2.17 存储模式设置
8051系列单片机共有21个特殊功能寄存器,它们离散分布在片内RAM的高128字节中,如程序状态字PSW、定时器控制字TCON等。为了能够直接访问这些特殊功能寄存器,C51扩充了关键字sfr、sfr16,利用这种扩充关键字可以在C语言源程序中直接对单片机的特殊功能寄存器变量进行定义,定义方法如下:
sfr特殊功能寄存器名=地址常数;
例如:sfr P1=0x90;//定义特殊功能寄存器P1口的地址为90H。
sfr后面的名字可以任意选取,但应符合一般习惯,地址常数必须在是特殊功能寄存器的地址范围之内。
访问16位的特殊功能寄存器,采用关键字sfr16进行定义。例如定义8052单片机的定时器/计数器T2,就可用如下方法定义:
sfr16 T2=0xCC;//定义T2,其地址为T2L=CCH,T2H=CDH
在8051系列单片机应用系统中经常需要访问特殊功能寄存器中的某些位,C51编译器扩充了关键字sbit,利用它可以访问位寻址对象,使用方法如下:
(1)sbit位变量名=位地址;
这种方法将位的绝对地址赋给位变量,位地址必须位于80H~FFH之间。例如:
sbit OV=0xD2;//定义位变量OV(溢出标志),其位地址为D2H。
sbit P1_0=0x90;//定义位变量P1_0,其位地址为90H。
(2)sbit位变量名=特殊功能寄存器名^位位置;
适用已定义的特殊功能寄存器位变量的定义,位位置值为0~7。
如:sft P1=0x90;
sbit P1_0=P1^0;//定义位变量P1_0,它是P1口的第0位。
(3)sbit位变量名=字节地址^位位置;
这种方法是以特殊功能寄存器的地址作为基址,其值位于80H~FFH之间,位位置值为0~7。
如:sbit P1_0=0x90^0;//定义位变量P1_0,直接指明了特殊功能寄存器P1的地址,它是0x90地址单元的第0位。
从变量的作用范围来看,有全局变量和局部变量之分。
全局变量是指在程序开始处或各个功能函数的外面所定义的变量,在程序开始处定义的变量在整个程序中有效,可供程序中所有的函数共同使用;在各功能函数外面定义的全局变量只对定义处开始往后的各个函数有效,只有从定义处往后的各个功能函数可以使用该变量。
局部变量是指函数内部或以花括号“{}”围起来的功能块内部所定义的变量,局部变量只在定义它的函数或功能块内有效,在该函数或功能块以外则不能使用它。局部变量可以与全局变量同名,但在这种情况下局部变量的优先级较高,而同名的全局变量在该功能块内被暂时屏蔽。
从变量的存在时间来看又可分静态存储变量和动态存储变量。
静态存储变量是指在程序运行期间其存储空间固定不变的变量。动态存储变量是指该变量的存储空间不确定,在程序运行期间根据需要动态地为该变量分配存储空间。一般来说全局变量为静态存储变量,局部变量为动态存储变量。
在使用一个变量或常量之前,必须先对该变量或常量进行定义,指出它的数据类型和存储器类型,以便编译系统为它们分配相应的存储单元。
变量的数据类型选择原则:
(1)若能预算出变量的变化范围,则可根据变量长度来选择变量的类型,则尽量减少变量的长度。
(2)如果程序中不需使用负数,则选择无符号数类型的变量。
(3)如果程序中不需使用浮点数,则要避免使用浮点数变量。
五、运算符和表达式
C语言对数据具有很强的表达能力,具有十分丰富的运算符,利用这些运算符可以组成各种表达式及语句。运算符就是完成某种特定运算的符号。
C语言的运算符可以分为15类,如表2.19所示。
表2.19 C语言的运算符优先级和结合性
续表
按照运算符在表达式中所起的作用,可分为赋值运算符、算数运算符、关系运算符、自增和自减运算符、逻辑运算符、位运算符、条件运算符、强制类型转换运算符等。按照其表达式中与运算符的关系可分为单目运算符,双目运算符和三目运算符。单目就是指需要有一个运算对象,双目就要求有两个运算对象,三目则要三个运算对象。
表达式是由运算符及运算对象所组成的具有特定意义的式子,由运算符和表达式可以组成C语言程序的各种语句。C语言是一种表达式语言,表达式后面加“;”号就构成了一个表达式语句。
考虑到C51编程的特点,这里主要介绍赋值运算符、算数运算符、关系运算符、增量减量运算符、逻辑运算符、位运算符、逗号运算符、条件运算符等。
1.赋值运算符
运算符是“=”,它的功能是给变量赋值,称为赋值运算符。如:x=10;由此可见利用赋值运算符将一个变量与一个表达式连接起来的式子为赋值表达式,在表达式后面加“;”便构成了赋值语句。
赋值语句格式:变量=表达式;
例如:a=0x55; //将常数十六进制数55赋予变量a
b=c=33;//同时赋值给变量b,c
d=e; //将变量e的值赋予变量d
f=a+b; //将变量a+b的值赋予变量f
由上面的例子可以知道赋值语句的意义就是先计算出“=”右边的表达式的值,然后将得到的值赋给左边的变量。而且右边的表达式可以是一个赋值表达式。
2.算术运算符
C51中的算术运算符有5个,分别是:
+ 加或取正值运算符
- 减或取负值运算符
* 乘运算符
/ 除运算符
% 取余运算符
它们都是双目运算符,加、减和乘运算符符合一般的运算规则,而除法运算和一般的算术运算规则有所不同,如是两浮点数相除,其结果为浮点数,如10.0/20.0所得值为0.5,而两个整数相除时,所得值就是整数,如7/3,值为2。取余运算要求两个运算对象均为整型数据,例如5%2的结果为1。
用算术运算符将运算对象连接起来的式子即为算数表达式,算术表达式的一般形式:
表达式1 算术运算符 表达式2
3.关系运算符
C51中有六种关系运算符,分别是:
> 大于
< 小于
>= 大于等于
<= 小于等于
== 等于
!= 不等于
其中,前四个具有相同的优先级,后两个也具有相同的优先级,但是前四个的优先级要高于后两个的。
当两个表达式用关系运算符连接起来时,这时就是关系表达式。一般形式为:
表达式1 关系运算符 表达式2
关系表达式通常用来判别某个条件是否满足。要注意的是,用关系运算符的运算结果只有1和0两种,也就是逻辑的真与假,当指定的条件满足时结果为1,不满足时结果为0。
例如:关系表达式 8==4表达式的值为0。
关系表达式 5>0 表达式的值为1。
4.自增和自减运算符
自增运算符(++)和自减运算符(--),这两个运算符是C语言中特有的一种运算符。作用就是对运算对象做加1和减1运算。要注意的是运算对象在符号前或后,其含义都是不同的,虽然同是加1或减1。
(1)前置运算──++变量、--变量,即先增减、后运算。
(2)后置运算──变量++、变量--,即先运算、后增减。
增减量运算符只允许用于变量的运算中,不能用于常数或表达式。
例2-1 设unshegned int x,y,z;x=y=8;分别进行x和y的自增、自减操作,要求编程并仿真。
程序如下:
仿真结果如图2.18所示。
图2.18 自增和自减运算仿真
5.逻辑运算符
C51语言提供了三种逻辑运算符,分别是!(逻辑非)、&&(逻辑与)、||(逻辑或)。
用逻辑运算符将关系表达式或逻辑量连接起来就是逻辑表达式了,逻辑表达式的一般形式为:
逻辑与:表达式1&&表达式2
逻辑或:表达式1||表达式2
逻辑非:!表达式
逻辑运算符优先级从高到低为:!(逻辑非)→&&(逻辑与)→||(逻辑或)。
逻辑运算的结果只有两个:“真”为1,“假”为0。
逻辑与表达式1和表达式2都为真时结果为真(非0值),否则为假(0值)。也就是说运算会先对表达式1进行判断,如果为真(非0值),则继续对表达式2进行判断,当结果为真时,逻辑运算的结果为真(值为1),如果结果不为真时,逻辑运算的结果为假(0值)。如果在判断表达式1时就不为真的话,就不用再判断表达式2了,而直接给出运算结果为假。
逻辑或运算时只要两个运算条件中有一个为真时,运算结果就为真,只有当表达式都不为真时,逻辑运算结果才为假。
逻辑非则是把逻辑运算结果值取反。
6.位运算
C51能对运算对象进行按位操作,从而使C51也能具有一定的对硬件直接进行操作的能力。位运算符的作用是按位对变量进行运算,但是并不改变参与运算的变量的值。如果要求按位改变变量的值,则要利用相应的赋值运算。还有就是位运算符是不能用来对浮点型数据进行操作的。C51中共有6种位运算符。
位运算一般的表达形式如下:变量1 位运算符 变量2
位运算符也有优先级,从高到低依次是:
“~”(按位取反)→“<<”(左移)→“>>”(右移)→“&”(按位与)→“^”(按位异或)→“|”(按位或)
例2-2 设unsigned char x=0xaa,y=0x3c;编程实现各逻辑运算,并分别将运算结果输出至P2口。
程序如下:
各种位运算均将运算对象按位进行相应的逻辑操作。
7.逗号运算符
用它将两个或多个表达式连接起来,形成逗号表达式。逗号表达式的一般形式为:
表达式1,表达式2,表达式3……表达式n
这样用逗号运算符组成的表达式在程序运行时,是从左到右计算出各个表达式的值,而整个用逗号运算符组成的表达式的值等于最右边表达式的值,就是“表达式n”的值。在实际的应用中,大部分情况下,使用逗号表达式的目的只是为了分别得到各个表达式的值,而并不一定要得到和使用整个逗号表达式的值。要注意的还有,并不是在程序的任何位置出现的逗号,都可以认为是逗号运算符。如函数中的参数,同类型变量的定义中的逗号只是用来间隔之用而不是逗号运算符。
8.条件运算符
三目运算符,它就是“?:”条件运算符,它要求有三个运算对象。它可以把三个表达式连接构成一个条件表达式。条件表达式的一般形式如下:
逻辑表达式?表达式1:表达式2
条件运算符的作用简单来说就是根据逻辑表达式的值选择使用表达式的值。当逻辑表达式的值为真(非0值)时,整个表达式的值为表达式1的值;当逻辑表达式的值为假(值为0)时,整个表达式的值为表达式2的值。要注意的是,条件表达式中逻辑表达式的类型可以与表达式1和表达式2的类型不一样。
9.复合赋值运算符
复合赋值运算符就是在赋值运算符“=”的前面加上其他运算符。以下是C语言中的复合赋值运算符:(www.daowen.com)
+= 加法赋值 >>= 右移位赋值
-= 减法赋值 &= 逻辑与赋值
*= 乘法赋值 |= 逻辑或赋值
/= 除法赋值 ^= 逻辑异或赋值
%= 取模赋值 -= 逻辑非赋值
<<=左移位赋值
复合运算的一般形式为:
变量 复合赋值运算符 表达式
其含义就是变量与表达式先进行运算符所要求的运算,再把运算结果赋值给参与运算的变量。其实这是C语言中一种简化程序的方法,凡是二目运算都可以用复合赋值运算符去简化表达。例如:
a+=56等价于a=a+56
y/=x+9等价于y=y/(x+9)
采用复合赋值运算符能使程序代码简单化,并能提高编译的效率,但会降低程序的可读性。
六、基本语句与结构控制语句
C语言程序的执行部分由语句组成,C语言提供了丰富的程序控制语句,按照结构化程序设计的基本结构:顺序结构、选择结构和循环结构,组成各种复杂程序。这些语句主要包括表达式语句、复合语句、选择语句和循环语句等。
1.表达式语句
表达式语句是最基本的C语言语句。表达式语句由表达式加上分号“;”组成,其一般形式如下:
表达式;
执行表达式语句就是计算表达式的值。
如:
P1_0=~P1_0;
x=8;++k;
每个表达式后面都必须带“;”号,还可以仅由一个分号“;”形成一个表达式语句,这种语句称为空语句。程序执行空语句时需要占用一条指令的执行时间,但是什么也不做。在C51程序中常常把空语句作为循环体,用于消耗CPU时间等待事件发生的场合。
2.复合语句
把多个语句用大括号{}括起来,组合在一起形成具有一定功能的模块,这种由若干条语句组合而成的语句块称为复合语句。
复合语句的一般形式为:
在程序中应把复合语句看成是单条语句,而不是多条语句。
要注意的是,在复合语句中所定义的变量,称为局部变量,所谓局部变量就是指它的有效范围只在复合语句中,而函数也算是复合语句,所以函数内定义的变量有效范围也只在函数内部。
3.条件语句
条件语句又被称为分支语句,其关键字是由if构成。C语言提供了3种形式的条件语句:
(1)基本if语句的形式。
if(表达式) 语句组;
if语句执行过程:当“表达式”的结果为“真”时,执行其后的“语句组”,否则跳过该语句组,继续执行下面的语句。流程图如图2.19所示。
图2.19 基本if语句流程图
例如:if(a==b)a++;//当a等于b时,a就加1
if语句中的“表达式”通常为逻辑表达式或关系表达式,也可以是任何其他的表达式或类型数据,只要表达式的值非0即为“真”。
(2)一般if语句的形式。
if(表达式) 语句组1;
else 语句组2;
if-else语句执行过程:当“表达式”的结果为“真”时,执行其后的“语句组1”,否则执行“语句组2”。流程图如图2.20所示。
例如if(a==b)a++;
图2.20 一般if语句流程图
else a--;
当a等于b时,a加1,否则a-1。
(3)多分支if语句形式。
if(表达式1) 语句组1;
else if(表达式2)语句组2;
else if(表达式n)语句组n;
else语句组n+1;
流程图如图2.21所示。
图2.21 多分支if语句流程图
这是由if else语句组成的嵌套,用来实现多方向条件分支,使用时应注意if和else的配对使用,要是少了一个就会语法出错,记住else总是与最临近的if相配对。
例2-3 按键与彩灯电路如图2.3所示,要求按下按键,8LED按如下规律显示:
按键 显示
S1 灭灭灭灭灭灭亮亮
S2 灭灭灭灭亮亮灭灭
S3 灭灭亮亮灭灭灭灭
S4 亮亮灭灭灭灭灭灭
无键按下 灭灭灭灭灭灭灭灭
分析:分别按下按钮,共有5种显示状态,则可用if-else4级嵌套判断完成。
程序如下:
4.switch/case开关语句
用多个条件语句可以实现多方向条件分支,但是可以发现使用过多的条件语句实现多方向分支会使条件语句嵌套过多,程序冗长,这样读起来也很不好读。这时使用开关语句同样可以达到处理多分支选择的目的,又可以使程序结构清晰。
多分支选择的switch语句,其一般形式如下:
运行中switch后面的表达式的值将会作为条件,与case后面的各个常量表达式的值相对比,如果相等时则执行后面的语句,再执行break语句,跳出switch语句。如果case没有和条件相等的值时就执行default后的语句。当要求没有符合的条件时不做任何处理,则可以不写default语句。
例2-4 用switch/case语句编程实现例2-3要求。
程序如下:
由例可看出,处理多分支选择,用switch/case语句实现,速度快且易于理解。
5.循环语句
在C语言中构成循环控制的语句有while,dowhile,for和goto语句。同样都是起到循环作用,但具体的作用和用法又不一样。
(1)while语句。
一般形式如下:
while(条件表达式)语句组;
其执行过程是:当条件为真时执行后面的语句,执行完后再次回到while执行条件判断,为真时重复执行语句,为假时退出循环体。当条件一开始就为假时,while后面的循环体将一次都不执行就退出循环。流程图如图2.22所示。
图2.22 while语句流程图
(2)do while语句。
一般形式如下:
do语句组; while(条件表达式);
其执行过程是:先执行循环体语句组,再根据条件判断是否要退出循环。这样就决定了循环体无论在任何条件下都会至少被执行一次。流程图如图2.23所示。
(3)for语句。
一般形式如下:
图2.23 do-while语句流程图
for(循环变量赋初值;循环条件;循环变量增值)
语句组;
其执行过程是:
①循环变量赋初值。
②判断循环是否继续,若值为真(值为非0),则执行for语句中指定的内嵌语句,然后执行下面第3步。若为假(值为0),则结束循环,转到第5步。
③调整循环变量值。
④转回上面第2步继续执行。
⑤循环结束,执行for语句下面的一个语句。
流程图如图2.24所示。
例2-5 图2.3中当检测到P1.4为低电平时,做加1计数,并将计数值以二进制形式输出至P2口。要求:分别用while和do-while语句编程。
方法1:用while循环
图2.24 for语句流程图
若运行程序后,始终P1!=0xef,则c变量的值等于0。
方法2:用do-while循环
若运行程序后,始终P1!=0xef,则c变量的值等于1。
(4)goto语句。
一般形式如下:
goto语句标号;
它是一个无条件的转向语句,只要执行到这个语句,程序指针就会跳转到goto后的标号所在的程序段。
(5)continue语句。
一般形式如下:
continue;
continue语句是用于中断的语句,通常使用在循环中,它的作用是结束本次循环,跳过循环体中没有执行的语句,跳转到下一次循环周期。continue同时也是一个无条件跳转语句,但功能和前面说到的break语句有所不同,continue执行后不是跳出循环,而是跳到循环的开始并执行下一次的循环。
(6)break语句。
使用break语句还可以从循环体中跳出循环,提前结束循环而接着执行循环结构下面的语句。它不能用在除了循环语句和switch语句之外的任何其他语句中。
例2-6 下面一段程序用于计算圆的面积,当计算到面积大于100时,由break语句跳出循环。
程序如下:
七、函数
在C语言程序中,子程序的作用是由函数来实现的,函数是C语言的基本组成模块,一个C语言程序就是由若干个模块化的函数组成的。
C语言程序总是从主函数main( )开始执行的,main( )函数是一个控制流程的特殊函数,它是控制流程的起点。
所有函数在定义时是相互独立的,它们之间是平行关系。函数可以相互调用,但不能调用主函数main( )。
从使用者的角度来看,有标准库函数和用户自定义函数两种。
(1)标准库函数。
标准库函数是由C51的编译器提供的,用户不必定义这些函数,可以直接调用。Keil C51编译器提供了一百多个库函数。常用的C51库函数包括一般I/O口函数、访问SFR地址函数等,在C51编译环境中,以头文件的形式给出。
(2)用户自定义函数。
用户自定义函数是用户根据需要自行编写的函数,它必须先定义之后才能被调用。
1.函数定义
一般格式如下:
格式说明:
“函数类型”说明了自定义函数返回值的类型。“函数名”是自定义函数的名字。“形式参数表”给出函数被调用时传递数据的形式参数,形式参数的类型必须要加以说明。“局部变量定义”是对在函数内部使用的局部变量进行定义。“函数体”是为完成函数的特定功能而设置的语句。
2.函数的返回值
函数的返回值,是通过return语句完成的。return语句一般放在函数的最后位置,用于终止函数的执行,并控制程序返回调用该函数时所处的位置。返回时还可以通过return语句带回返回值。return语句格式有两种:
(1)return;
(2)return(表达式);
如果return语句后面带有表达式,则要计算表达式的值,并将表达式的值作为函数的返回值。若不需要函数的返回值,则将该函数定义为“void”类型。
3.函数的调用
函数调用就是指一个函数体中引用另一个已定义的函数来实现所需要的功能,这时函数体称为主调用函数,函数体中所引用的函数称为被调用函数。一个函数体中可以调用数个其他的函数,这些被调用的函数同样也可以调用其他函数。调用函数的一般形式如下:
函数名(实际参数表);
“函数名”就是指被调用的函数。实际参数表可以为零或多个参数,多个参数时要用逗号隔开,每个参数的类型、位置应与函数定义时的形式参数一一对应,它的作用就是把参数传到被调用函数中的形式参数,如果类型不对应就会产生一些错误。调用的函数是无参函数时不写参数,但不能省后面的括号。
函数调用的三种方式:
(1)函数调用语句。
在主调函数中将函数调用作为一条语句,例如:
delay(10);
不要求返回函数值
(2)函数表达式调用。
函数的调用作为一个运算对象出现在表达式中,可以称为函数表达式,例如:
x=sum(10);
要注意的是,这种调用方式要求被调用的函数能返回一个同类型的值,否则会出现不可预料的错误。
(3)嵌套函数调用。
在主调函数中将函数调用作为另一个函数调用的实际参数,例如:
m=max(a,max(b,c));
4.函数的声明
C语言中规定,若功能子函数放在主调函数之后,则要在主调函数前进行声明。
在C51中,函数声明一般形式如下:
[extern] 函数类型 函数名(形式参数表);
函数的声明是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便调用函数时系统进行对照检查。所以函数定义和声明中的“类型、形参表、名称”等都要相一致。
如果声明的函数在文件内部,则声明时不用extern,如果声明的函数不在文件内部,而在另一个文件中,声明时须带extern,指明使用的函数在另一个文件中。
(1)子函数写在主函数之前。
例2-7 在主调函数之前定义被调函数delay( )。
从以上程序可看出,程序形式如下:
(2)子函数写在主函数之后。
例2-8 在主调函数之后定义被调函数delay( )。
从以上程序可看出,程序形式如下:
程序中第一行include<reg52.h>是编译预处理命令中的文件包含,用于包含头文件reg52.h,作用是用MCS-51系列单片机特殊功能寄存器SFR和位的符号名称与其在存储器的地址对应,如这里用到的P2口,否则这些符号在C51中将被当作普通变量。reg52.h是Keil C51编译器提供的头文件,保存在文件夹”keil\c51\inc“下。
八、数组
数组是一组有序集合。数组中的所有元素都属于同一数据类型,它们按顺序存放在一个连续的存储空间中,数组中元素的次序是由下标来确定的,下标从0开始顺序编号。数组可以是一维或多维的。常用的有一维数组、二维数组和字符数组,与普通变量一样,数组也要求先定义后使用。
1.一维数组的定义和引用
(1)定义。
数据类型[存储器类型]数组名[常量表达式];
其中,“数据类型”是指数组中的各个元素的类型。“数组名”是整个数组的标识,命名方法和变量命名方法是一样的。在编译时系统会根据数组大小和类型为变量分配空间,数组名可以说就是所分配空间的首地址的标识。“常量表达式”是表示数组的长度,它必须用“[]”括起,括号里的数不能是变量只能是常量。“存储器类型”若省略则将数组定义在DATA存储区中。
例如:
unsigned int y[3]={1,2,3};
定义了一个无符号整型数组,数组名为y,数组中元素个数为3,定义的同时给数组中的三个元素赋初值,赋初值分别为1、2、3。
(2)数组元素的引用。
只能逐个引用数组中的元素,不能一次引用整个数组。
数组元素的引用形式:
数组名[下标];
下标可以是整型常量或整型表达式。
x=y[2];//变量x的值为3
2.字符数组的定义与引用
在C51语言中,字符数组用于存放一组字符或字符串,字符串以“\0”作为结束符,只存放一般字符的字符数组的赋值与使用和一般的数组完全相同。对于存放字符串的字符数组,既可以对字符数组的元素逐个进行访问,也可以对整个数组按字符串的方式进行处理。
例如:
char string1[6]={′H′,′E′,′L′,′L′,′O′,′\0′};
char string2[6]={″HELLO″};
上面定义了两个字符数组,均定义了6个元素,其中string2数组自动加了结束符‘\0’。
例2-9 要求多任务选择器中8LED按数组元素排列顺序显示。
程序如下:
8LED按数组tab设定的规律显示。若要实现其他规律的显示,使用数组是比较容易的一种方法。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。