2.2 相关的C51语言知识精讲
2.2.1 数据类型概述
数据在单片机内部都是以二进制的形式存储的。C51的数据类型如下:1.基本类型
C51的基本数据类型有字符型、整型、实型等,见表2-1。
表2-1 C51的数据类型
注:1.3.4e-38即为3.4乘以10的负38次方,3.4e38就是3.4乘以10的38次方。
2.bit型是C51语言特有的类型。
2.其他数据类型
除基本类型外,还有构造类型(含数组、结构体、共用体)、指针类型、空类型(void)等。在这里只需先了解一点,在后续的章节中根据需要来详细、深入地学习。
2.2.2 常量和变量
1.常量
在程序运行过程中,其值不能被改变的量称为常量。
(1)整型常量。整型常量通常可以表示为以下几种形式:
十进制整数:如1234、-5678、0等。
十六进制整数:以0x开头的数是十六进制数,如程序中出现的0x123表示十六进制数123H,相当于十进制数291。x用小写或大写都可以。
(2)字符型常量。详见第五章知识链接。
(3)实型常量。如110.38,3.14e-3等。由于实型数据占用的存储空间较大,一般尽量避免采用实型。本书不作介绍。
注意:符号常量
编程时常用一些符号来代替常量的值,这样的符号叫做“符号常量”。符号常量的可用英文的全称或简写,也可用汉语拼音,但不能用系统的关键词。要尽量做到“见名知意”,这样有利于阅读。符号常量须用关键字“const”来修饰,定义格式示例如下:
const doubleP1=3.14.5926;
编译时,符号量被视为一个常量,不被分配内存空间。在程序执行过程,遇到该符号常量,将用其定义时的初值来代替。所以声明符号常量时须赋初值。
用宏定义也可以定义符号常量,如下:
#define LEDON 0xfe
上述语句就是宏定义语句,编译时,LEDON都会用0xfe代替。这样做的好处是:见名知意(例如我们用LEDON表示LED点亮,名字的意义很清楚。注:本例中0xfe为点亮LED的具体数据。硬件的连接不同,则数据不一样)。
2.变量
变量在程序的执行过程,会占用单片机内存(RAM)的空间。
(1)变量是在程序执行过程中其值可以改变的量。C语言程序中的每一个变量都必须有一个标识符作为它的变量名。在使用一个变量之前,必须先对该变量进行定义,指出它的数据类型和存储模式,以便编译系统为它分配相应的存储单元。
(2)C51中对变量进行定义的格式如下:
[存储种类]数据类型[存储器类型]变量名表;
解释如下:
1)“存储种类”和“存储器类型”是可选项。变量的存储种类有四种:自动变量(auto)、全局变量、静态变量(static)、寄存器(register),详见表2-2。
2)变量的数据类型有位(bit)、有符号字符型(signed char)、无符号字符型(un-signed)、有符号整型(signed int)、无符号整型(unsigned int)和浮点型等。
表2-2 变量的4种存储种类
(续)
3)存储器类型。C51编译器还允许说明变量的存储器类型。KeilC51编译器完全支持8051系列单片机的硬件结构,可以访问其硬件系统的所有部分。对每个变量可以准确地赋予其存储器类型,从而可使其能在单片机系统内准确地定位。如果省略存储器类型,则默认将存储器类型定义为“date”型,在该类型下,可直接访问单片机内部数据存储器,访问速度最快。一般都是将变量定义为“date”型。
例如:unsigned intx,y;该语句定义了无符号整形变量x和y,省略了存储种类和存储器类型。
定义变量注意事项:
定义变量时,只要值域(数值范围)够用,就应尽量定义使用较小的数据类型,如char型、bit型,因为较小的数据类型占用的内存单元较小。例如,若x的值是1,当将x定义为unsignedint型时,占用两个字节的存储空间,若定义为unsignedchar型,则只占用1B的存储空间,若定义为bit型,则只占用1bit的存储空间。
51系列单片机是8位机,进行8位数据运算要比16位及更多位数的数据的运算快得多,所以要尽量用char型。
如果满足需要,尽量使用unsigned(即无符号)的数据类型,因为单片机处理有符号的数据时,要对符号进行判断和处理,运算速度会变慢。由于单片机的速度比不上PC,单片机又是工作在实时状态,所以任何可以提高效率的措施都要重视。
4)变量名可用任意合法的标识符。
2.2.3 标识符和关键字
1.标识符
在编程时,标识符用来表示自定义对象名称,其中自定义对象可以是常量、变量、数组、函数、语句标号等。
标识符必须以字母或下划线开头,后面可使用若干字母、下划线或数字的组合,但长度一般不超过32个,不能使用系统关键字,例如,area、PI、a_array、s123、P101p都是合法的标识符,而456P、code-y、a&b都是非法的。标识符是区分大小写的,例如A1和a1表示两个不同的标识符。
为了便于阅读,标识符应尽量简单,并且能清楚地看出其含义。一般可用英文单词的简写、汉语拼音或汉语拼音的简写。
2.关键字
关键字是C51编译器保留的一些特殊标识符,具有特定的定义和用法。
单片机C51程序语言继承了ANSIC标准定义的32个关键字,同时又结合自身的特点扩展了一些C51中的关键词,详见附录。
2.2.4 C51的函数简介[1]
1.函数的基本类型
就是具有特定功能的代码段。函数的分类详见表2-3。
表2-3 函数的分类
①定义可理解为写出具体内容。
2.函数的特点
C51单片机语言支持库函数和自定义函数,这是C51强大功能的直接体现。库函数添加在头文件里,编程开始,包含了头文件后,就可以使用头文件里的库函数了,这就可以达到简化代码设计、减轻工作量的目的。使用自定义函数则可以使代码结构化、模块化。
在C51语言中,对函数的个数没有限制。但是,对这么多个函数,究竟从哪个函数开始执行呢?C51语言中,提供了一个特殊的函数,即main函数,又称为主函数,主函数中可以调用其他函数(注:为了区别,将除主函数之外的其他函数叫做子函数),其他函数(子函数)之间可以相互调用,但不能调用主函数。程序首先从该函数的第一个语句开始执行,然后再依次逐句执行。在执行过程中如果遇到调用子函数的语句,则转到相应的子函数去逐条执行子函数内部的语句,子函数执行完毕,则返回到原调用的位置继续向下执行。
注意:
①在一个函数体的内部,不能再定义另一个函数,即不能嵌套定义。
②函数可以自己调用自己,称为递归调用。
③函数之间允许嵌套调用。
④同一个函数可以被一个或多个函数调用任意次。
2.2.5 单片机C语言程序的基本结构
单片机C语言程序有清晰的结构和条理。一般包含六个部分,详见表2-4。
表2-4 单片机C语言的基本结构
2.2.6 再论局部变量与全局变量
1.局部变量
在函数内部定义的变量叫局部变量,它只在本函数范围内有效,只有在调用该函数时才给该变量分配内存单元,调用完毕则将内存单元收回。
注意:
①主函数中定义的变量只在主函数中有效,在主函数调用的子函数中无效。
②不同的子函数中可以使用相同名字的变量,但它们代表的对象不同,互不干扰。
③函数的形式参数也是局部变量,只能在该函数中使用。
④在{}内的复合语句中可以定义变量,但这些变量只能在本复合语句中使用。
2.全局变量
一个源文件可以包含一个或多个函数。在函数之外定义的变量称为全局变量,全局变量在该源文件内可供所有的函数使用。
注意:
①一个函数既可以使用本函数中定义的局部变量,又可以使用函数之外定义的全局变量。
②如果不是十分必要,应尽量少用全局变量。理由有:a)全局变量在程序执行的全部过程一直占用存储单元,而不是像局部变量那样仅在需要时才占用存储单元。b)全局变量会降低函数的通用性。我们在编写函数时,都希望函数具有很好的可移植性,以便其他程序可以方便地使用。c)使用全局变量过多,整个程序的清晰性变差。因为在调试程序时如果一个全局变量的值与设想的不同,则不能很快地判断是哪个函数出了问题。
③在同一源文件中,如果全局变量与局部变量同名,则在局部变量的作用范围内,全局变量会被屏蔽。
2.2.7 C语言的算术运算符和算术表达式
C语言的运算符范围很宽,除了控制语句和输入、输出语句外,大多数基本操作均为运算符处理。运算符较多,其中算术运算符和算术表达式的知识详见表2-5。
表2-5 C语言算术运算符和算术表达式
2.2.8 关系运算符和关系表达式
1.关系运算符
C语言一共提供了6种关系运算符,详见表2-6。
2.关系表达式
用关系运算符将两个运算对象连接起来形成的式子叫关系表达式,如a+b>b+c,a==b<c。
注意:关系表达式如果成立,则该表达式的值为1,若不成立则该表达式的值为0。
例如,对表达式“a=c>b”的理解是:当c的值大于b的值时,关系表达式“c>b”的值为1,该值赋给a,所以a的值为1,否则,若c的值小于b,则“c<b”的值为0,所以a的值为0。
表2-6 C语言的关系运算符
2.2.9 自增减运算符
自增减运算符的作用是使变量的值加1或减1,详见表2-7。
表2-7 自增减运算符
2.2.10 单片机的几个周期介绍
学习单片机,需要掌握时钟周期、机器周期和指令周期三个概念,详见表2-8。
表2-8 单片机的时钟周期、机器周期和指令周期
2.2.11 while循环语句和for循环语句
1.while语句是一个循环语句
while语句的形式是:
while(条件表达式)
{若干条程序语句;}
while语句的执行过程是:判断()内的条件表达式是否成立,若不成立(即表达式的值为0),则{}内的语句不被执行,直接跳到执行{}后的语句。若表达式成立(即表达式的值为1),则执行{}内的各条程序语句,执行完毕后再返回判断条件表达式是否成立,若仍然成立则执行{}内的语句,若不成立则执行{}后的语句,如图2-3所示。
图2-3 while循环语句执行的流程图
【示例】 用while循环语句可写成延时函数,如下:
执行过程是:首先判断i>0是否成立,只要是成立的,就执行i=i-1;直到i减小到0时,i>0不成立,则跳出循环,不成立执行后面的语句,这样起到了延时作用。
注意:给变量赋的值必须在变量类型的取值范围内,否则会出错。例如第02行,若写成i=70000,则会出错,因为i是unsigned int型变量,其取值范围是0~65535。给它赋值为70000,超出了取值范围。
while语句()中的条件表达式可以是一个常数(如1)、一个运算式或者一个带返回值的函数。
2.for循环语句
for循环语句的一般结构是:
for(给循环变量赋初值;条件表达式;循环变量增或减)
{若干条程序语句;}
执行过程是:
第一步:给循环量赋初值。
第二步:判断条件表达式是否成立,若不成立,则{}内的语句不被执行,直接跳出for循环,执行{}以后的语句。若条件表达式成立,则执行{}内部的程序语句,执行完毕后,返回到for后面的()内执行一次循环变量的增或减,然后再判断条件表达式是否成立,若不成立,则跳出for循环语句而执行{}以后的语句,若成立则执行{}以内的语句。这样不断地循环,直到跳出循环为止,如图2-4所示。
图2-4 for循环语句的执行流程图
注意:for循环语句的{}内的语句可以为空,这时{}就可以不写,即for循环语句就可写成:for(给循环变量赋初值;条件表达式;循环变量增或减);注意:分号不能少。
例如用for循环语句可写成延时函数,如下:
执行过程是:先给i赋初值,再判断i>0是否成立,若不成立则跳出for循环,若成立,由于后面没有{}的内容,所以省掉了执行{}内语句的过程,接着再执行i--,再判断i>0是否成立…,直到i=0时(要执行i自减3000次),i>0才不会成立,才会跳出for循环,这样起到了延时作用。
2.2.12 不带参数和带参数函数的写法和调用
1.不带参数函数的写法和调用
如果在程序中某些语句多次用到且语句的内容完全相同,则可以把这些语句写成一个不带参数的子函数,当在主函数中需要用到这些语句时,直接调用这个子函数就可以了。例如:1s的延时子函数如下:
执行过程:首先执行第03行。开始x=1000,x>0为真,所以就执行第04行(即y由110逐步减1,直到减小到0,所耗时间约为1ms),第04行执行完毕后,再执行x--,x的值变为999,再判断x>0是否为真,结果是为真,所以又执行第04行(耗时约1ms),然后又执行一次x--,这样循环下去。每执行一次x减1,y就要从110逐步减1,直到减小到0,X共要自减1000次,所以耗时约为1s。
注意:子函数可以写在主函数的前面或后面,但不能写在主函数里面。如果写在主函数后面,需在主函数的前面进行声明。
声明的方法是:返回值特性 函数名()。
若函数无参数,则()内为空,如果函数有参数,则要在()内依次写上参数的类型。
【应用示例】 用调用延时子函数的方法,写出一个程序,使图2-1中的发光二极管(LED)D1每间隔600ms亮、灭闪烁。
2.带参数函数的写法
如果在一个程序里需要不同的延时时间,就需要写多个不同的延时函数,用上述不带参数的子函数就不方便了,这时宜采用带参数的子函数。经典程序如下:
【应用示例】 详见2.3节。