2.1 数据类型与运算符
课件 C51语言中的基本数据类型
视频 C51语言中的基本数据类型
单片机C51语言是由C语言继承而来的。和C语言不同的是,C51语言运行于单片机平台,而C语言运行于普通的桌面平台。与C语言相同的是,C51语言具有和C语言类似的语法结构,便于学习,同时具有汇编语言的硬件操作能力。因此,具有C语言编程基础的人,能够很轻松地掌握单片机C51语言的程序设计。
C语言本身的常用语法就不多,而支持51单片机的C51语言常用语法更少。这里首先介绍一些C51语言的初步语法知识。随着学习的深入,在后面的章节中,边用边学,会把当前程序应用到的C51语言相关语法知识再进行深入介绍。
1.基本数据类型
数据类型通常指一个值的集合以及定义在这个值集上的一组操作。数据类型是编译程序进行存储器分配的主要依据,编程的时候会根据需要来申请内存,大数据用大内存,小数据就用小内存,这样就可以充分地利用有限的内存,使之效用最大化。尤其是对单片机而言,其内存很小,准确地设定数据类型就显得很有必要。
C51语言中的基本数据类型如表2-1所示。
表2-1 C51语言中的基本数据类型
注:此外还有一些数据类型,如枚举型、短整型、单精度浮点型等。在本书中基本没有应用,所以不多做介绍,遇到时可查阅相关资料。
带有signed符号的类型称为有符号类型,signed符号通常省略,如signed char也可以简略地写成char,它占用1字节(8位)的空间,因为这个类型有符号位所以用来表示数值大小的空间只有7个二进制位,因此它所能表示的数值范围是-128~127。而unsigned表示无符号类型,不能够省略,unsigned char同样占用1字节(8位)的空间,因为这个类型没有符号,所以全部8位都用来表示整数值,因此它所能表示的数值范围是0~255。其他int、long等类型的signed、unsigned符号标识与此含义相同。
在使用C51变量时,尤其要注意变量的数据类型所能表达的最大数值范围,不要超出其界限值。假设定义了某个变量a为unsigned char型(0~255),当a的值为255,此时进行a=a+1操作,得到a的值并不会是256;同样,若当a=0时,进行a=a-1的操作,也不会得到-1;这二者的操作均会产生溢出,造成计算结果错误。因此,在使用变量时,必须要明确地知道其对应的数据类型的数值范围,确保运算不会超出界限范围。
在单片机程序中经常需要直接访问各种功能模块,如串、并行接口,定时/计数器,中断系统等,这要求C51语言必须提供直接访问硬件资源的手段。C51中可以通过定义sfr、sfr16、sbit等数据类型的变量,实现对硬件资源的访问和操作。sbit、sfr和sfr16是专门为访问51单片机中SFR(特殊功能寄存器)中的控制位以及8位SFR和16位SFR所特有的类型,它们并不是标准C中的类型。
C51语言中对于特殊功能寄存器已经预先进行了定义,这个定义包含在头文件(reg51.h)中,编程时直接引入这个头文件就可以使用其中定义的符号来访问特殊功能寄存器了。比如在这个头文件中使用P0、P1、P2、P3分别代表51单片机的4个并行端口等。引入了该头文件后,就可以用访问SFR的形式实现对硬件的访问。因此,熟悉头文件中的SFR定义,在程序设计中直接使用预定义的符号来操作相应的设备或端口,在很大程度上简化了对单片机的操作。reg51.h头文件内容如图2-1所示。
图2-1 reg51.h头文件内容
2.常量与变量
课件 C51语言中常量与变量
视频 C51语言中常量与变量
C51语言中的数据有常量和变量之分。所谓常量是指在程序运行过程中,其值不能改变的量,称为常量。常量无名,也不会长期存在,使用过一次就会被释放掉,常量存在的意义一般来讲就是为了给对应的变量赋值。所谓变量就是指在程序运行过程中,其值是可以动态改变的量。常量和变量都需要有数据类型,只有确定了它们的数据类型,系统才能为其分配相应的存储运行空间。每一个变量都必须要有一个名字,这个名字不仅是一个简单的称谓代号,其实也是变量所在空间的地址代号,通过这个地址代号就能够在内存空间中找到这个变量,并读取其中存放的数值。在变量空间存放的数值就是变量值。这个变量值的大小不能超过变量数据类型的界限范围。
1)定义变量
C51语言中对变量的要求必须是“先定义,后使用”。使用某个变量之前必须先对它进行定义。必须指明该变量的数据类型,以便在内存当中分配适当大小的空间。
定义变量的语法形式有两种:
变量命名时一般要用小写字母,变量名不要过长。变量命名的原则最好是见名知意,可以选用有含义的英文单词作为标识。一定要注意不要与C51语言的关键字重复。C51语言中的关键字很少。所谓关键字,是指C51语言保留的一些有特殊作用的词语,由于这些关键字都有着特殊的含义,因此不能用作变量或常量等的命名。
如果定义了某种类型的变量,但未赋初值,C51中会对部分类型自动赋予初值。
如:int a; //定义了整形变量a,但未对a赋初值,系统默认赋初值为0
int a=1; //定义了整型变量a,并对a赋了初始值为1
2)定义访问硬件的位变量
使用单片机进行程序设计时经常要访问硬件,如果需要对于特殊功能寄存器(SFR)的某一位访问,就可以使用前面讲过的sbit类型,使用sbit类型变量可以直接访问到具体指定位(引脚)的内容。
定义sbit类型的变量语法如下:
赋值号(=)右侧的特殊功能寄存器就是在头文件中定义过的SFR,请读者查阅reg51.h文件,熟悉预定义的SFR。位序是指要访问的功能位处于特殊功能寄存器的第几位,注意这个位序是从右向左排列的,最右边的位序为0。
比如欲操作端口P0的1号引脚,先查阅reg51.h文件,见图2-1,找到P0端口的定义为P0,这是一个BYTE型的SFR,共有8位。其内容与引脚编号一一对应排列,如图2-2所示,其1号引脚位于第D1位。
图2-2 P0端口内部引脚排列顺序
则定义语句为:
这个语句含义就是定义了一个sbit类型的变量名为led,该变量映射到P0端口的1号引脚,这样就可以使用led这个变量可以直接操作P0端口的1号引脚。假设这个引脚接了一个发光二极管,如下命令就能控制二极管的亮灭了。
3)符号常量与预定义代码
常量一般没有名字,但有数据类型的区别,这个类型可以通过字面的书写形式直接分辨。如12、0等是整型常量,'a'、'b'等为字符型常量等等。这种常量通常被称为字面常量或直接常量,是C51语言中出现较多的常量表现形式,这些常量主要用来给对应的变量赋值。除此之外,还有一种常量称为符号常量,需要使用#define命令进行定义。
定义符号常量的语法形式为:
符号常量名通常都用大写字母。使用符号常量不仅可以增加程序的易读性,而且在系统升级或修改时,还可实现“牵一发动全身”,只做符号常量定义处的修改即可。如
#define PI 3.1415926 //这里定义了符号常量PI,它的值是3.1415926
今后当系统移植时,如果因硬件不同,要对PI的取值长度修改,则只在这里进行修改即可,简化了程序的修改工作。
另外,在程序设计中,还经常使用#define进行一些预定义代码来简化程序中数据类型的输入。比如unsigned char、unsigned int等数据类型输入比较麻烦,通过预定义后可分别用uchar和uint来代替,以此简化程序录入时数据类型名称过长带来的输入麻烦。
需要注意的是,使用#define的预定义语句后面是没有任何标点符号的。
3.常用运算符
课件 C51语言中常用运算符
视频 C51语言中常用运算符
C51语言中提供了算术运算符、关系运算符、逻辑运算符、位运算符等多种运算。
1)算术运算符
运用算术运算符可以完成所有的算术运算。另外,灵活运用算术运算符可以完成诸如动态获取组成某多位数的各位数字。C51语言中的算术运算符如表2-2所示。
表2-2 算术运算符
“/”两数相除,如果除数与被除数都是整数,则结果一定是整数,如10/4=2;如果除数或被除数有一个是浮点数,则结果是浮点数,如10/2.5=4.0。
在单片机程序设计中,除了普通的数据运算要用到算术运算符外,利用取余和整除运算还能实现获取多位数中每一位数字的运算效果。
如:已知某测温系统获取到的动态温度值n为3位数,现需要分别求出组成该3位数的百位、十位、个位数字并在数码管中显示出来。
使用算术运算符可以轻松完成这类任务。
2)关系运算符和逻辑运算符
关系运算和逻辑运算的结果均为真或假。C51中通常1代表真,0代表假。在关系运算中,关系成立其结果为真就是1,关系不成立其结果为假就是0。逻辑运算中参与运算的操作数凡是不为0的数字均代表真,0代表假;逻辑运算的结果同样是条件成立就是真结果为1,条件不成立就是假结果为0。关系运算符和逻辑运算符见表2-3。
表2-3 关系运算符和逻辑运算符
关系运算符主要用来判定两个操作数数量上的关系(大、小、等于、不等于)。当关系运算符和赋值符号相连时,要先进行关系运算然后再进行赋值。如a=5>7,因为5小于7,所以5>7这个关系运算的结果为假(即0),原式变为a=0,则a最终结果为0。
逻辑运算主要是与运算、或运算和非运算。参与逻辑运算的操作数通常为关系表达式或纯数字,在C51中参与逻辑运算的数字,如果不为0则代表真;如果为0则代表假,逻辑运算的结果非0即1。当逻辑运算与关系运算混合时,先进行关系运算再进行逻辑运算。如表达式5>7&&6<8||23中,因为5并不大于7其值为0,而6确实小于8其值为1,23为数字参与逻辑运算可以表示为真1,所以原式可以变为0&&1||1,最终结果为1。其实这里涉及了运算符的优先级问题,关系运算优先于逻辑运算。
3)位运算符
位运算是指二进制位的运算。位运算中除了取反(~)运算外,都要求运算符号两侧各有一个操作数。这里位运算的符号与操作规则和上一模块中的位运算叙述完全相同。位运算符见表2-4。
表2-4 位运算符
在单片机程序中灵活使用位运算可以非常方便地实现对端口和引脚的控制。如需要将P0口的高3位清0但其余各位保持原状态,就可以使用P0=P0&0x1f实现,类似地,如果要将P0口的高3位置1但其余各位保持原状态,就可以使用P0=P0|0xe0实现;实现P0口的低4位翻转,高4位不变,则可用P0=P0^0x0f实现。这样的操作还有很多,需要读者在日后的使用中,多动脑多练习,结合C51的分支、循环语句可以控制硬件实现更多有趣的操作。
4)其他运算符
C51中除了上述常用的运算符外,以下几种运算符也经常出现,如表2-5所示。
(1)复合赋值运算符。给变量赋值,最简单的写法是“变量名=常量”,如a=5表示向变量a赋值,使其值为5。如果稍微复杂一点,将表达式的值赋值给变量,如P1=P1*2这样写有些麻烦,于是出现了复合型的赋值运算符,即将算术、关系、逻辑、位运算等运算符与赋值号写在一起,既表达赋值也表达运算,如上式可简化为P1*=2;再比如P0=P0&0x1f,可简化为P0&=0x1f。也就是说,凡是赋值运算符两侧变量同名并进行其他运算的,均可以采用复合赋值运算符进行简化书写。更多的复合赋值运算符见表2-5中序号1的部分。
(2)条件运算符。条件运算符在今后的编程实践中会有广泛应用。这个运算符只用了两个符号就解决了条件分支判断的问题。灵活运用这个运算符会大大简化代码,提高程序可读性。其使用形式比较简单,如下所示:
其含义是:如果条件成立,则返回结果1作为整个表达式的结果;如果条件不成立,则返回结果2作为整个表达式的结果。可以应用这个运算符,快速返回两个数中的较大数,如c=a>b?a:b,c中存放的就是a和b中的较大数。
(3)自加与自减。自加与自减也是很多语言中都有的运算符,其含义是变量在自身的基础上自加1或自减1。下面以++运算符为例进行说明,--运算符的道理与之相同,请自行对照。
++i:表示先进行i=i+1计算,然后再把此时i的值作为表达式的值返回。如i=3;y=++i;计算时i先进行自加i=i+1(此时i值为4),然后再把i的值赋值给y(即y值为4)。
i++:表示先将i的初始值返回作为表达式的值,然后再进行i=i+1。如i=3;y=i++;计算时i初始值是3作为表达式值返回并赋值给y(即y值为3),然后再进行i=i+1(此时i值为4)。
表2-5 其他运算符
上述各类运算符的优先级如下所示:
有括号先算括号→单目运算(自加、自减、取反等)→算术运算符→关系运算符→逻辑运算符(不含!)→条件运算符→赋值运算符→逗号运算符。
同一优先级的运算符,运算次序由结合方向决定。例如*与/具有相同的优先级,其结合方向为自左至右,因此3*4/5的运算次序是先乘后除。而-和++为同一优先级,结合方向为自右至左,因此-i++相当于-(i++)。