第三章MCS-51单片机的C语言程序设计基础•3.1基本概念•3.2变量和常量•3.3运算符和表达式•3.4控制语句•3.5函数•3.6数组和指针•3.7结构与共用体MCS-51单片机由复杂的逻辑电路所组成,它能够“识别”的仅仅是“0”和“1”所代表的二进制数字信号。计算机语言,是以二进制数组成的逻辑序列,称为机器语言。表示二进制的最小单位称为位(bit),计算机最小的存储单元是8位所组成的字节(Byte)。C语言是一种普及率很高的程序设计语言,兼有高级语言和汇编语言的特点,1972年由美国贝尔实验室的M.Ritchie推出,由于其有高效、灵活和较高的移植性等优点而得到程序员的青睐。C语言被称为中级语言,这是因为它和汇编语言类似,能直接访问计算机底层资源,同时它又具备了高级语言的各种优点。首先作为中级语言,C允许对位、字节和地址这些计算机功能中的基本成分进行操作,其次C语言程序非常容易移植,甚至可以设计出能同时运行在Linux,UNIX和Windows等操作系统上的软件。C语言作为一种结构化语言。使用的设计方法为模块化设计方法,每个子问题求解的步骤被定义为模块。在C语言中,其函数就是模块化的体现,函数之间是相互独立的,函数内的数据只能通过接口进行传递。数据与代码是分离的,数据在各个函数之间通过接口传递,因此,设计良好的函数能够在多个程序间反复使用,构成了代码复用的基础。3.1基本概念学习新的程序设计语言的最佳途径是编写程序。编写C语言程序在某种意义上来说就好像是用砖盖房子:首先打好地基,然后使用沙子和水泥把砖堆砌起来,最后建成房子。每个C语言程序至少有一个主函数,即main()函数。它是C语言程序的基础,是程序代码执行的起点。所有的函数都是通过main()函数直接或间接调用的。main()函数通常被认为是最低级的任务,因为它是启动该程序系统所调用的第一个函擞。在很多的情况下,main()函数都只包含很少的语句,这些语句的作用仅仅是初始化和指导从一个函数到另一个函数的程序操作。一个最简单的嵌入式C语言程序如下:#includestdio.hvoidmain(){while(1);∥无限循环}3.2变量和常量一个应用程序通常都要处理各种量,这些量可分为变量和常量。变量是可以改变的值,而常量是固定的值。变量和常量有多种形式和大小,它们在程序存储器中以各种形式存储。3.2.1变量类型变量是通过用于指示变量类型和大小的保留字和跟在保留字后面的标识符来声明的,例如unsignedchari;inttempl;longinttemp2;变量和常量存储在微控制器的存储器中,编译器需要知道为每个变量预留多少存储地址,而不浪费存储器空间,因此程序员必须声明变量,同时指明变量的大小和类型。表3-1列出了单片机所用的C51语言中支持的几种变量类型及其大小。表3-1C51语言所支持的数据类型注:(1)Bit:位标量。Bit位标量是C51语言的一种扩充数据类型,利用它可定义一个位标量,但不能定:义位指针,也不能定义位数组。它的值是一个二进制数,不是0就是1,类似一些高级语言中的Boolean类型中的TRUE和FALSE。(2)SFR:特殊功能寄存器。SFR也是一种扩充数据类型,占用一个内存单元(8位),值域为O~255。利用它可以访问MCS-51单片机内部的所有特殊功能寄存器。如用SFRPl=O*90这一条语句定义PI(T作寄存器),则其为P1端口在片内的寄存器,在后面的语句中我们可以用P1=255(对Pl端口的所有引脚置高电平)之类的语句来操作特殊功能寄存器。(3)SFR16:16位特殊功能寄存器。SFR16占用两个内存单元(16位),值域为0~65535。SFR16和SFR-样用于操作特殊功能寄存器,所不同的是它用于操作占两个字节的寄存器,如定时器TO和Tl。(4)Sbit:可寻址位。Sbit是C51中的一种扩充数据类型,利用它可以访问芯片内部的RAM中的可寻址位或特殊功能寄存器中的可寻址位,如先前定义:SFRPl=0*90;∥INPl端口的寄存器是可位寻址的,所以可以定义SbitPl_l=Pl^1;∥Pl_l为Pl中的Pl.1引脚这样在以后的程序语句中就可以用Pl—1来对Pl.1引脚进行读写操作了。通常编程者可以直接使用系统提供的预处理文件,里面已定义好各特殊功能寄存器的简单名字,直接引用即可。当然用户也可以编写自己的定义文件。关于数据类型转换等相关操作在后面的课程或程序实例中将有所提及。3.2.2变量的作用域变量的作用域是指变量在程序中可访问的范围。变量可被声明为局部变量或全局变量,相应地,具有局部作用域或全局作用域。1.局部变量局部变量是在创建函数时由函数分配存储器的空间,这些变量不能被其他的函数访问,其作用域只限于所声明的函数内部。同一个局部变量可以在多个函数中声明,而不会引起冲突,因为编译器会将这些变量视为每个函数的一部分。2.全局变量全局变量是由编译器分配的存储器空间,可被程序内所有的函数访问。全局变量能被任何函数修改,并且会保持全局变量的值,以便其他函数可以使用。全局变量在main()函数开始执行时进行了清零,此操作通常由编译器产生的启动代码执行,对于程序员来说是不可见的。下面的代码演示了变量的作用域。unsignedchara;∥全局变量voidfunl(void)∥主函数调用的子函数{unsignedint;∥局部变量t-12;∥在其作用域内赋值a-47;∥全局变量,可以在子函数内部引用m-12;∥不可以在此对m赋值,因为m是main()函数的局部变量,∥因此编译器会报错}voidmain(void){unsignedcharm;∥定义m是main(、)函数的局部变量a-34;∥可以在此对全局变量赋值t-12;∥不可以在此对funl()函数的局部变量赋值while(1);∥无限循环}在一个函数内,如果局部变量和全局变量同名,那么局部变量会屏蔽全局变量。在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。3.2.3常量身的一部分,位于只读存储器(ROM)中,不被分配到可变随机存储器(RAM)中。在赋值运算a-3+b:中,数字3是常量,可以由编译器直接编码到加法操作中。常量同样可以是字符形式或字符串形式,例如:printf(IloveMCU51);X='A';文本IloveMCU51和字符'A'存储在ROM中,不会被改变。也可以通过保留字const来声明常量,并且定义其类型和大小。constchara=123;把变量指定为常量,可使该变量存储在ROM中,而不是RAM中,这样有利于节省有限的RAM空间。1.数值型常量数值型常量可以通过指定基数的形式来声明,使程序更具有可读性。(1)无前缀的十进制形式(如123,0,-89等)。(2)前缀为0b的二进制形式(如0b0010)。(3)前缀为0x的十六进制形式(如0*ff)。(4)前缀为0的八进制形式(如0171)。2.字符型常量字符型常量可以是可打印字符(比如0~9,a~z),也可以是无法打印出来的字符(如换行符、回车符或者制表符)。可打印字符型常量可以由单引号引起来,不可以显示的控制字符,可以在该字符前面加一个反斜杠(\)组成专用转义字符。常用转义字符表见表3-2。反斜杠(\)和单引号(’)字符本身必须有一个前导的反斜杠来区分,以免编译器混淆。例如\’是一个单引号字符,\\是一个反斜杠。表3-2常用转义字符3.字符串型常量字符串型常量由双引号内的字符组成,如“hello”“OK”等。当引号内没有字符时为空字符串。在使用特殊字符时同样要使用转义字符如双引号。在C中字符串常量是作为字符类型数组来处理的,在存储字符串时系统会在字符串尾部加上\。转义字符以作为该字符串的结束符,所以字符串常量“A”和字符常量‘A’是不同的,前者在存储时多占用一个字节的空间。3.2.4枚举和定义在C语言程序中,可读性是十分重要的。C语言捉供了枚举类型和定义类型,这使程序员能够用有意义的名字或其他更为有意义的短语来代替数字。在程序中经常要用到一些变量去作程序中的判断标志,如经常要用一个字符或整型变量去储存1和0作判断条件真、假的标志,只有当它等于0或1时才是有效的,而将它赋上别的值,可能会造成程序出错或变得混乱,这个时候就使用枚举数据类型去定义变量,从而避免错误赋值。枚举数据类型就是把某些整型常量的集合用一个名字表示,其中的整型常量就是这种枚举类型变量的可取的合法值。枚举类型的两种定义格式如下。(1)enum枚举名{枚举值列表)变量列表;例:enumTFFlag{False,True)TFF;(2)enum枚举名{枚举值列表);emum枚举名变量列表;例:enumWeek{Sun,Mon,Tue,Wed,Thu,Fri,Sat};enumWeekOldWeek,NewWeek;看了上面的例子,就会发现枚举值不用赋值就能使用,这是因为在枚举列表中,每一项名称代表一个整数值,在默认的情况下,编译器会自动为每一项赋值,第一项赋值为0,第二项为1……如w色ek中的Sun为0,Fri为5。C语言也允许对各项值作初始化赋值,要注意的是在对某项值初始化后,它的后续各项值也随之递增,如:enumWeek(Mon=1,Tue,Wed,Thu,Fri,Sat,Sun);上例的枚举就使week值从1到7,这样的赋值也符合我们日常生活中对周次时序关系的定义。使用枚举就如变量一样,但在程序中不能为其赋值。定义类型在某种程度上同枚举类型相似,因为定义允许用一个文本串代替另一个文本串,例如:#defineucharunsignedchar语句#defineucharunsignedchar的作用是使编译器在遇到单词uchar时用unsignedchar来代替。注意#defineucharunsignedchar这一行的结束没有分号。#define是预处理程序指令。预处理程序指令实际上不是C语言语法的一部分,但是它们同样得到了广泛的接受与使用。程序的预处理与实际程序的编译是分开进行的,预处理在编译开始之前进行。3.2.5存储类型变量可声明为自动变量、静态变量、外部变量和寄存器变量4种存储类型,说明符分别为auto,statlc,extern和register。auto是默认的类型,所以保留字auto可以省略。1.自动变量自动变量是指在函数内部说明的变量,一般常被称为局部变量,用关键字auto进行说明,当auto省略时,对应的都被认为是局部变量。局部变量在函数调用时自动产生,但不会自动初始化,随着函数调用的结束,变量也就自动消失了,下次调用此函数时局部变量再自动产生,还要再次赋值,退出时又自动消失。自动类型的变量声明如下。autocharvaluel;或者charvaluel;2.静态变量statlc称为静态变量。根据变量的类型又可以分为静态局部变量和静态全程变量。(1)静态局部变量。静态局部变量与局部变量的区别在于:在函数退出时,静态局部变量始终存在,但不能被其他函数使用,当再次进入该函数时,该变量将保存上次的结果。(2)静态全程变量。C语言允许将大型程序分成若干独立模块文件分别编译,然后将所有模块的目标文件连接在一起,从而提高编译速度,同时也便于软件的管理和维护。静态全程变量就是指只在定义它的源文件中可见,而在其他源文件中不可见的变量。它与全程变量的区别是:全程变量可以再说明为外部变量(extern),被其他源文件使用,而静态全程变量却不能再被说明为外部的,即只能被所在的源文件使用,例如:staticcharvalue2;3.外部变量extern称为外部变量。为了使变量除了在定义它的源文件中可以使用外,还能被其他文件使用就必须将全程变量通知每一个程序模块文件,此时可用extern来说明。4.寄存器变量寄存器变量与自动变量类似,它只能应用于整型和字符型变量。定义符register说明的变量被编译器存储在CPU的寄存器中,而不是像普通的变量那样存储在内存中,这样可以提高运算速度。但是编译器只允许同时定义两个寄存器变量,