第3章C51语言程序设计基础ppt课件.ppt
第3章 C51语言程序设计基础,本章主要介绍C51语言的数据类型、数据存储类型、数据运算、程序设计的基本结构、数组和指针,要求学习者初步掌握C51语言程序设计的基本方法。,3.1 C51语言概述,C语言是美国国家标准协会(ANSI)制定的编程语言标准。1987年,ANSI公布了87 ANSI C,即标准C语言。C51语言是在ANSI C的基础上针对51单片机的硬件特点进行了扩展,并向51单片机上移植。经过多年努力,C51语言已经成为公认的高效、简洁而又贴近51单片机硬件的实用高级编程语言。,3.1.1 使用C51语言的优点,C51语言具有结构化和模块化的特点,便于阅读和维护。C51语言的可移植性好,很多微控制器都支持C51编译器。C51语言提供的库函数包含许多标准子程序,具有较强的数据处理能力。用C51语言编写的程序比用汇编语言编写的程序更符合人们的思考习惯。寄存器分配、不同存储器的寻址及数据类型等细节交由编译器管理,使开发者可以更专心地考虑算法,而不是考虑一些细节问题。C51语言和微控制器是相对独立的,开发者不必知道处理器的具体内部结构和处理过程。当用新型的微控制器开发程序时,可以很快上手,缩短学习时间和程序开发时间。,3.1.2 C51与ANSI C的主要区别,1. 头文件的区别 51系列单片机的生产厂家有多个,它们的差异在于内部资源如定时器、中断、I/O等的数量以及功能的不同,而对使用者来说,只需要将相应的功能寄存器的头文件加载在程序内,就可实现其所具有的功能。因此,C51系列的头文件集中体现了各系列芯片的不同资源及功能。 2. 数据类型的区别 计算机的CPU是32位或64位的,运算能力强,内存大,ANSI C可以大量使用float型与long int型变量;单片机的CPU一般为8位或16位的,运算能力较弱,因此C51变量类型以char型为主,int型为辅。,3. 数据存储类型的区别 C语言最初是为通用计算机设计的,在通用计算机中只有一个程序和数据统一寻址的内存空间,而51系列单片机有片内、片外程序存储器,还有片内、片外数据存储器。标准C语言并没有提供这部分存储器的地址范围的定义。此外,标准C语言对于51系列单片机中大量的特殊功能寄存器也没有定义。 4. 中断方面的区别 ANSI C语言没有处理单片机中断的定义。而C51中有专门的中断函数。 5. 库函数的区别 由于ANSI C中的部分库函数不适于嵌入式处理器系统,因此被排除在C51之外,如字符屏幕和图形函数等。,6. 目标代码的区别 ANSI C由计算机生成.exe文件,编译完成后直接在计算机上运行;C51由单片机编译生成.bin或.hex代码文件,需要烧写到单片机中并结合外围电路执行。 7. 仿真调试的区别 C51程序在计算机上进行编译,然后需通过仿真器连接后进行仿真调试。近年来随着电路仿真软件的不断完善,也可通过Proteus等软件直接在计算机上进行仿真调试。 但是从数据运算操作、程序控制语句以及函数的使用上来说,C51与ANSI C几乎没有什么明显的差别。如果程序设计者具备了有关标准C的编程基础,只要注意Keil C51与标准C的不同之处,并熟悉MCS-51单片机的硬件结构,就能够较快地掌握 C51的编程。,3.2 C51数据类型,3.2.1 常量与变量常量:程序运行过程中值不能改变的量称为常量。常量存在于ROM中。变量:变量代表存储器中的一个或多个存储单元,用来存放数据。一般来讲,这些数据在程序运行过程中可以改变(只读变量除外)。变量名命名规则:变量名只能由半角的字母、数字、下划线组成,且第一个字符不能是数字。数据类型:变量存在的类型称为数据类型。C51的数据类型如表3-1所示。,3.2.2 变量定义与赋值1. 定义一个变量 变量必须先定义,后使用。例如: int a; char b;2. 变量赋初值 C语言允许在定义变量的同时给变量赋初值。例如:char c =a;int a = 7;int a,b,c=9; /定义a、b、c为整型变量,对c赋初值。int a = 3,b = 3,c = 3;,在存储空间够用的情况下,尽量选择8位即一个字节的char型,特别是unsigned char。对于51系列单片机而言,浮点类型变量将明显增加运算时间和程序长度,如果可以的话,尽量使用灵活巧妙的算法来避免浮点变量的引入。,3.2.3 C51的扩展数据类型1. 位变量bitbit的值可以是1(true),也可以是0(false)。例如:bit lock; /将lock定义为位变量bit dirention; /将direction定义为位变量位变量的使用说明: 位变量不能定义成一个指针,如不能定义:bit *pointer; 位变量不存在位数组,如不能定义:bit b_array ; 定义位变量时,存储类型只允许为data、bdata或者idata,如果将位变量的存储类型定义成其他类型都将导致编译出错。,2. 特殊功能寄存器sfr MCS-51单片机特殊功能寄存器在片内RAM区的80HFFH之间。“sfr”数据类型占用一个内存单元,利用它可访问MCS-51单片机内部的所有特殊功能寄存器。 例如:“sfr P1=0 x90;” 这一语句定义了P1口在片内的寄存器,在后面语句中可用“P1=0 xff”(使P1的所有引脚输出为高电平)之类的语句来操作特殊功能寄存器。 标准特殊功能寄存器在reg51.h、reg52.h等头文件中已经被定义,只要用文件包含做出申明即可使用。,3. 特殊功能寄存器sfr16 “sfr16”数据类型占用两个内存单元。sfr16和sfr一样用于操作特殊功能寄存器。所不同的是它用于操作占两个字节的特殊功能寄存器。 例如:“sfr16 DPTR=0 x82;” 语句定义了片内16位数据指针寄存器DPTR,其低8位字节地址为82H。在后面的语句中可以对DPTR进行操作。,4. 特殊功能位sbit sbit 是指MCS-51单片机片内特殊功能寄存器的可寻址位。 例如: sfr PSW=0 xd0 ; /定义PSW寄存器地址为0 xd0 sbit OVPSW2; /定义OV位为PSW.2 符号“”前面是特殊功能寄存器的名字,“”后面的数字定义了特殊功能寄存器可寻址位在寄存器中的位置,取值必须是07。 注意:不要把bit与sbit混淆。bit用来定义普通的位变量,值只能是二进制的0或1。而sbit定义的是特殊功能位。,3.3 C51数据的存储类型,3.3.1 数据存储类型 MCS-51系列单片机采用了哈佛结构,即程序存储器和数据存储器是分离的。51系列单片提供了三种不同类型的存储区域(memory areas): 程序存储区(program memory); 内部数据存储区(internal data memory); 外部数据存储区(external data memory)。 C51编译器完全支持MCS-51单片机及其系列的结构,可完全访问MCS-51硬件系统所有部分。每个变量可准确地赋予不同的存储器类型(data,idata,pdata,xdata,code)。访问内部数据存储器(idata)要比访问外部数据存储器(xdata)更快一些,因此,可将经常使用的变量置于内部数据存储器中,而将较大及很少使用的数据单元置于外部数据存储器中。,例如: data char charvar; char code msg=“ENTER PARAMETER:”; unsigned long xdata array100; float idata x,y,z; unsigned char xdata vector104; sfr P0=0 x80; sbit RI=0 x98; char bdata flags; sbit flag0=flags0; 说明:声明变量时存储区修饰符和数据类型修饰符的位置可以互换,即“char data x;”和“data char x;”是完全等效的。不过从兼容性的角度考虑,建议使用前一种格式。,3.3.2 存储器模式 如果在变量定义时略去存储类型标识符,编译器会自动选择默认存储类型。默认存储类型进一步由SMALL、COMPACT和LARGE存储模式指令限制。C51变量的存储器模式如表3-3所示。,3.3.3 C51语言的绝对地址访问,1. 绝对宏 C51编译器提供一组宏定义来对code、data、pdata和xdata空间进行绝对寻址,包括CBYTE、CWORD、DBYTE、DWORD、XBYTE、XWORD、PBYTE、PWORD。这些宏包含在名为absacc.h的头文件中。在使用前,需要将头文件包含进来,即#include 。其中: CBYTE:以字节形式对code区寻址; CWORD:以字形式对code区寻址; DBYTE:以字节形式对data区寻址; DWORD:以字形式对data区寻址; XBYTE:以字节形式对xdata区寻址; XWORD:以字形式对xdata区寻址; PBYTE:以字节形式对pdata区寻址; PWORD:以字形式对pdata区寻址。,(1)按字节访问存储器宏的形式 宏名地址 数组中的下标就是存储器的地址,因此使用起来非常方便。 例如: DBYTE0 x30=0 x48;/给片内RAM送数据 XBYTE0 x0002=0 x36;/给片外RAM送数据 dis_buf0=CBYTETABLE+5;/从CODE区读取数据,(2)按整型数访问存储器宏的形式 宏名下标 由于整型数占两个字节,所以下标与地址的关系为:地址=下标2。由于数组中的下标与存储器的地址是倍数关系,使用时要注意。 例如: DWORD0 x20=0 x1234; /给片内RAM的0 x40、0 x41单元送数 XWORD0 x0002=0 x5678; /给片外RAM的0 x0004、0 x0005单元送数,2. _at_关键字 格式如下: 存储器类型 数据类型说明符 变量名 _at_ 地址常数 存储器类型和数据类型必须为C51能识别的。 地址常数必须位于有效的存储器空间之内。 使用 _at_ 定义的变量必须为全局变量。 例如: data unsigned char x1 _at_ 0 x40; /在data区定义字节变量x1,它的地址为40H xdata unsigned int x2 _at_ 0 x2000; /在xdata区定义字变量x2,它的地址为2000H data unsigned char buffer8 _at_ 0 x50; /在data区定义数组buffer,它的起始地址为50H,3.4 C51的数据运算,主要有:算术运算符、逻辑运算符、关系运算符、位运算符及赋值运算符等3.4.1 算术运算算术运算操作符主要包括: +(加法运算符),(减法运算符), *(乘法运算符), /(除法运算符), %(模运算或取余运算符),+(自增运算符),-(自减运算符)。注意: “/”的功能是取除运算结果的整数部分;“%”的功能是取除运算结果的余数部分。 例如:“5/3”的结果为1(商),而“5%3”的结果为2(余数)。,自增和自减运算符的功能是使变量自动加1或减1。自增和自减运算符放在变量之前和变量之后是不同的。 +i,-i:在使用i之前,先使i值加(减)1。 i+,i-:在使用i之后,再使i值加(减)1。例如:若i=4,则执行x=+i时,先使i加1,再引用结果,即x=5,运算结果为i=5,x=5。再如:若i=4,则执行x=i+时,先引用i值,即x=4,再使i加1,运算结果为i=5,x=4。,3.4.2 逻辑运算 逻辑运算操作符主要包括: &(逻辑与),|(逻辑或),!(逻辑非)。 逻辑运算结果为真时取1,否则取0。 3.4.3 关系运算 关系运算主要用于比较操作数的大小关系,包括: (大于),=(大于等于), = =(等于),!=(不等于)。 若关系成立,结果为1;若关系不成立,结果为0。,3.4.4 位运算 位运算是将两个操作数按二进制数展开,然后对应位进行逻辑运算。 位运算操作符包括:&(按位与),|(按位或),(按位异或),(按位取反),(位右移)。位运算的操作对象只能是整型和字符型数据。这些位运算和汇编语言中的位操作指令十分类似。,1. 左移运算符()该运算符用来将一个数的各二进制位全部左移若干位,左高位溢出,右低位补0。例如: b=a2;a=a2;a=2;高位左移后溢出,舍弃掉。在该数左移时被溢出舍弃的高位不为1时,左移1位相当于该数乘以2。例如: 152=60;又如,1432=60,原因是最高位为1,左移后被溢出,不能等同于乘以2。,2. 右移运算符()该运算符用来将一个数的各二进制位全部右移若干位,左高位补0,右低位溢出。例如: b=a2;a=a2;a=2;低位右移后溢出,舍弃掉。在该数右移时被溢出舍弃的低位不为1时,右移1位相当于该数除以2。例如: 162=4又如,632=15,原因是最后2位为1,右移后被溢出,不能等同于除以4。,3. 按位异或运算符() 异或运算的规则是:参加运算的两个二进制位相同时结果为0(假),相异时结果为1(真)。即 00=0;01=1;10=1;11=0; 例如:0 x2D0 x0F=0 x22,将十六进制数0 x2D与十六进制数0 x0F进行按位异或运算,则高四位保持不变,低四位全部翻转。通过按位异或运算可以实现对变量的某一位或某几位的取反操作。,4. 按位取反运算符() 按位取反运算符是一个单目运算符,用来对一个二进制数按位取反,即将0变为1,1变为0。 取反运算符常与移位运算符及按位与、按位或、按位异或运算符结合使用,以实现对某一位或某几位清0、置1、取反的操作。 5. 按位与运算符(&) 参加运算的两个数据,按二进制位进行与运算。 6. 按位或运算符(|) 参加运算的两个数据,按二进制位进行或运算。,3.4.5 赋值运算1. 格 式: 变量名=表达式; 例如: a=2;b=3;c=2*a+b;2. 复合赋值运算符赋值符号前加上其他运算符可构成复合运算符。C51语言10种复合运算符: +=,-=,*=,/=,=,=,=,=,= 例如: a+=b; /等价于a=(a+b) x*=a+b;/等价于x=(x*(a+b) a&=b; /等价于a=(a&b) a=4;/等价于a=(a4),C51基本运算符,运算符的优先级从低到高依次为:赋值运算符、&和|、关系运算符、算术运算符、逻辑非(!)运算符。,3.5 C51程序设计的基本结构,3.5.1 C51语言中语句的分类控制语句:控制程序运行的语句。空语句:只有一个分号的语句,称为空语句。它不进行任何操作。表达式语句:表达式后面加上分号就构成一个表达式语句。函数调用语句也是表达式语句,由函数调用加分号构成。复合语句:可以把多个语句用括起来,构成复合语句,多用于选择或循环结构中。注释语句:在C51中,由“/*”和“*/”符号对包含的内容以及双斜杠“/”后面的内容均表示注释语句。编程人员可使用注释语句来解释程序的功能,标注修改时间等。,3.5.2 C51语言程序结构,C51程序按结构可分为三类,即顺序、选择和循环结构。 1. 顺序结构 顺序结构的程序是从前往后依次执行语句。从整体上看,所有程序都是顺序结构,只不过中间某些部分是由选择结构或循环结构构成,选择结构或循环结构部分执行完成后,程序重新按顺序结构向下执行。,2. 选择结构 选择结构的基本特点是程序由多路分支构成,在程序的一次执行中根据指定的条件,选择执行其中的一条分支,而其他分支上的语句被直接跳过。 通过选择结构,可以使计算机具有决策能力,从而使计算机能够按照我们的意志在某个特定条件下完成相应的操作,即能够“随机应变”。选择结构包括:if语句、switch语句。,图3-1 if语句流程图,(1)if语句 if语句用来判定所给定的条件是否满足,并根据判定结果决定执行给出的两种操作之一。C51语言提供3种形式的if语句: 形式一: if(表达式) 语句; 括号中的表达式成立时,执行大括号内的语句,否则跳过大括号中的语句而直接执行下面的语句。 例如: if(xy) max=x;min=y;,形式二:流程图如图3-2所示。 if(表达式) 语句1; else 语句2;例如: if(xy) max=x; else max=y;,形式三:流程图如图3-3所示。if(表达式1) 语句1;else if(表达式2) 语句2; else if(表达式m) 语句m;else 语句n;,图3-3 if-else if-else语句流程图,例如: if(x1000) key=1; else if(x500) key=2; else if(x300) key=3; else if(x100) key=4; else key=5;,(2)switch语句switch语句是多分支选择语句,一般形式如下:switch(表达式)case 常量表达式1: 语句1; break; case 常量表达式2: 语句2; break; case 常量表达式3: 语句3; break; case 常量表达式n: 语句n; break; default: 语句n+1;,3. 循环结构 循环结构是根据某个或某些条件是否成立,来决定是否重复运行一段相同的程序。(1)采用if和goto构成的循环 采用if和goto语句可以构成“当型”循环,格式如下: Loop:if(表达式) 语句; goto loop; 采用if和goto语句也可以构成“直到型”循环程序,格式如下: Loop: 语句; if(表达式) goto loop; 采用goto语句可构成无条件转向语句,格式如下: goto 语句标号;,(2)for循环语句 其一般格式为: for(表达式1;表达式2;表达式3) 循环体语句(内部可为空)for循环语句的执行过程为:第一步:求解表达式1。第二步:求解表达式2。若其值为真,则执行循环体;若其值为假,则循环语句结束,执行后续语句。第三步:求解表达式3。并转到第二步继续执行,直至条件为假时结束循环。,【例3-1】用for语句求1+2+100的值。 程序如下: #include void main() int i,s; s=0; for(i=0;i=100;i+) s=s+i; printf(“1+2+100=%dn”,s); 注意:3个表达式都是可选项,可以任意省略,但“;”不能省。省略表达式1是不对循环变量赋初值;省略表达式2是不判断循环条件的真假;省略表达式3是不对循环变量进行操作。 例如:for(;)/表示无限循环。,(3)while循环语句 其一般格式为; while(表达式) 循环体语句(内部可为空)流程图while语句先求解循环条件表达式的值。如果为真,则执行循环体;否则跳出循环,执行后续操作。注意:一般来说在循环体中应该有使循环最终能结束的语句。如果表达式初始值为假,则循环体将一次都不执行。,【例3-2】用while语句求1+2+100的值。 程序如下: #include void main() int i,s; i=100; s=0; while(i0) s=s+i; i=i-1; printf(“1+2+100=%dn”,s); ,(4)do-while循环语句 其语句格式为: do 循环体语句; while(表达式); do-while循环是先执行循环体一次,再判断表达式的值,若为真,则继续执行循环,否则退出循环。 do-while语句至少执行循环体一次。,【例3-3】用do-while语句求1+2+100的值。程序如下:#include void main() int i,s; i=100;s=0; do s=s+i; i=i-1; while(i0); printf(“1+2+100=%dn”,s); 注意:do-while语句先执行,后判断。如果循环体内只有一条语句,可以不用 ,循环体 后无分号。while(表达式)后的分号不能省。,综上所述,无限循环可以由下面三种方法实现: for(;) / 代码段; while(1) /代码段; do /代码段; while(1);,(5)break语句、continue语句、goto语句 在循环语句执行过程中,如果需要在满足循环判定条件的情况下跳出代码段,可以使用break或continue语句;如果要从任意地方跳转到代码的某个地方,可以使用goto语句。break语句:用于从循环代码中退出,然后执行循环语句之后的语句,不再进入循环。 格式:break;continue语句:用于退出当前循环,不再执行本轮循环,程序代码从下一轮循环开始执行,直到判断条件不满足为止。其与break语句的区别是该语句不是退出整个循环。 格式:continue;goto语句:是一个无条件转移语句。当执行goto语句时,程序指针跳转到goto给出的下一条代码。 格式:goto 标号;,3.6 C51的数组与指针,C51语言的构造数据类型主要有数组、指针和结构等。在单片机系统中,数组的应用比较广泛,指针则次之,结构用得相对较少。这里我们只讨论数组和指针。 3.6.1 数组简介 数组是同类数据的一个有序结合,用数组名来标识。整型变量的有序结合称为整型数组,字符型变量的有序结合称为字符型数组。数组中的数据,称为数组元素。 数组中各元素的顺序用下标表示,下标为n的元素可以表示为:数组名n。改变 中的下标就可以访问数组中的所有的元素。,C51语言中常用的有一维、二维数组和字符数组。 (1)一维数组 具有一个下标的数组元素组成的数组称为一维数组。一维数组的形式如下: 类型说明符 数组名元素个数 其中,数组名是一个标识符,元素个数是一个常量表达式,不能是含有变量的表达式。例如: char ch10; 该例定义了一个一维字符型数组ch ,有10个元素,每个元素由不同的下标表示,分别为ch0,ch1,ch2,ch9。,在定义数组的时候可对全部元素赋初值,例如: int idata a6=0,1,2,3,4,5;也可只对数组的部分元素初始化,例如: int idata a10=0,1,2,3,4,5在定义数组时,若不对数组全部元素赋初值,则元素被缺省地赋值为0。,(2)二维或多维数组 具有两个或两个以上下标的数组称为二维数组或多维数组。定义二维数组的一般形式为: 数据类型说明符 数组名行数 列数 其中,数组名是一个标识符,行数和列数都是常量表达式。 二维数组可以在定义时进行整体初始化,也可以在定义以后单个进行赋值。 例如: float demo234; /demo2 数组有3 行4 列共12 个实型元素 int a34=1,2,3,4,5,6,7,8,9,10,11,12; /全部初始化,(3)字符数组 若一个数组的元素是字符型的,则该数组就是一个字符数组。例如: char a10= B,E,I, ,J,I,N,G,/0; 上面定义了一个字符型数组a ,有10个数组元素,并且将9个字符(其中包括一个字符串结束标志0)分别赋给了a 0a8,剩余的a9被系统自动赋予空格字符。,C51还允许用字符串直接给字符数组置初值,例如: char a10= “BEI JING”; 用双引号括起来的一串字符,称为字符串常量,C51编译器会自动地在字符串末尾加上结束符0。用单引号括起来的字符为字符的ASCII码值,而不是字符串。例如a表示a的ASCII码值为61H,而“a”表示一个字符串,由两个字符a和0组成。一个字符串可以用一维数组来装入,但数组的元素数目一定要比字符数多一个,以便C51编译器自动在其后面加入结束符0。,3.6.2 数组的应用 在C51编程中,数组的一个非常有用的功能是查表。例如数学运算,编程者更愿意采用查表计算而不是公式计算。又如,对于传感器的非线性转换需要进行补偿,使用查表法就要有效得多。再如,LED显示程序中,根据要显示的数值,通过查表找到对应的显示段码送到LED显示器显示。,【例3-4】使用查表法,计算数09的平方。 #define uchar unsigned charuchar code square10=0,1,4,9,16,25,36,49,64,81; /09的平方表,在程序存储器中uchar fuction(uchar number) return squarenumber; /返回要求得其平方的数 ; main()/主函数 uchar result; result= fuction(7); /函数fuction()的返回值为7的平方49,存入result变量中,3.6.3 数组与存储空间,当程序中设定了一个数组时,C51编译器就会在系统的存储空间中开辟一个区域,用于存放数组的内容。数组就包含在这个由连续存储单元组成的模块的存储体内。对于字符(char)数组而言,它占据了内存中一连串的字节位置。对于整型(int)数组而言,它将在存储区中占据一连串连续的字节对的位置。对于长整型(long)数组或浮点型(float)数组而言,一个成员将占有4字节的存储空间。,当一维数组被创建时,C51编译器就会根据数组的类型在内存中开辟一块大小等于数组长度乘以数据类型长度(即类型占有的字节数)的区域。对于二维数组amn而言,其存储顺序是按行存储,先存第0行元素的第0列、第1列、第2列,直至第n1列;然后存第1行元素的第0列、第1列、第2列,直至第n1列;按此顺序存储,直到第m1行的第n1列。当数组特别是多维数组中大多数元素没有被有效利用时,就会浪费大量的存储空间。对于51单片机,其存储资源极为有限,因此在进行C51语言编程时,要仔细地根据需要来选择数组的大小。,3.7 C51函数,函数是指能够执行特定功能和任务的程序代码段。一个完整的C51程序是由一个主函数和若干个其他函数组成。主函数是main()函数,主函数是唯一的,整个程序从这个主函数开始执行。 使用函数时应注意: 函数数目不受限制; main()函数是主函数,可以调用其他函数,而不允许被其他函数调用; 除主函数外,其他函数之间允许互相调用。,3.7.1 函数的分类1. 按有无返回值分无返回值函数:执行完成后不向主调函数返回函数值。类型说明符为void。有返回值函数:执行完成后向主调函数返回一个执行的结果(返回值)。2. 按有无参数传递分无参函数:主调函数和被调函数之间不进行参数的传递。有参函数:主调函数和被调函数之间存在参数的传递。被调函数所带的参数称为形式参数,主调函数所带的参数称为实际参数。3. 按函数定义分函数按照定义可分为主函数、自定义函数、库函数。,3.7.2 函数的定义 1. 无参函数的定义 此种函数在被调用时,既无参数输入,也不返回结果给调用函数,只是为完成某种操作而编写的函数。无参函数的定义形式为: 函数类型 函数名() 函数体; ,2. 有参函数的定义 调用此种函数时,必须提供实际的输入函数。有参函数的定义形式为: 函数类型 函数名(形式参数列表) 形式参数说明; 函数体; 其中,“函数类型”说明了自定义函数返回值的类型。,【例3-7】设计可调节时间的延时程序。 void delay(unsigned int n) unsigned char i,j; while(n-) for(i = 128;i 0;i-) for(j = 10;j 0;j-); ,3. 空函数 此种函数体内是空白的。调用空函数时,什么工作也不做,不起任何作用。定义空函数的目的,并不是为了执行某种操作,而是为了以后程序功能的扩充。空函数的定义形式为: 返回值类型标识符 函数名() 例如: float min() /空函数,占好位置,1. 函数的参数 函数的参数包括形式参数和实际参数。 形式参数:函数的函数名后面括号中的变量名称为形式参数,简称形参。 实际参数:在函数调用时,主调函数名后面括号中的表达式称为实际参数,简称实参。在C语言的函数调用中,实际参数与形式参数之间的数据传递是单向进行的,只能由实际参数传递给形式参数,而不能由形式参数传递给实际参数。实际参数与形式参数的类型必须一致,否则会发生类型不匹配的错误。,2. 函数的返回值 函数返回值是通过return语句获得的。一个函数可有一个以上的return语句,但是多于一个的return语句必须在选择结构(if或do/case)中使用,因为被调用函数一定只能返回一个变量。函数返回值的类型一般在定义函数时,由返回值的标识符来指定。例如在函数名之前的int,指定函数的返回值的类型为整型数(int)。若没有指定函数的返回值类型,默认返回值为整型类型。当函数没有返回值时,则使用标识符void进行说明。,3.7.4 函数的调用 在一个函数中需要用到某个函数的功能时,就调用该函数。调用者称为主调函数,被调用者称为被调函数。1. 函数调用的一般形式函数名实际参数列表; 若被调函数是有参函数,则主调函数必须把被调函数所需的参数传递给被调函数,要求实参与形参数据在数量、类型和顺序上都一致。实参可以是常量、变量和表达式。实参对形参的数据传递是单向的,即只能将实参传递给形参。,2. 函数调用的方式(1)函数调用语句 函数调用语句把被调用函数的函数名作为主调函数的一个语句。例如: print_message();(2)函数结果作为表达式的一个运算对象 函数结果作为表达式的一个运算对象,例如: result=2*gcd(a,b); 被调用函数作为一个运算对象出现在表达式中。这要求被调用函数带有return语句,以便返回一个明确的数值参加表达式的运算。(3)函数参数 函数参数即被调函数作为另一个函数的实际参数。例如: m=max(a,gcd(u,v); 其中,gcd(u,v)是一次函数调用,它的值作为另一个函数的max()的实际参数之一。,在一个函数调用另一个函数时,须具备以下条件: 被调用函数必须是已经存在的函数(库函数或用户自定义的函数)。 如果程序中使用了库函数,或使用了不在同一文件中的其他自定义函数,则应该在程序的开头处使用#include包含语句,将所有的函数信息包含到程序中来。 如果程序中使用了自定义函数,且该函数与调用它的函数同在一个文件中,则应根据主调函数与被调函数在文件中的位置,决定是否对被调函数做出说明。如果被调函数在主调函数之后,一般应在主调函数中,在被调用函数调之前,对被调函数的返回值类型做出说明。如果被调函数出现在主调函数之前,不用对被调函数进行说明。如果在所有函数定义之前,在文件的开头处,在函数的外部已经说明了函数的类型,则在主调函数中不必对所调用的函数再做返回值类型说明。,3.7.5 中断服务函数,使用interrupt可以将一个函数定义成中断服务函数。中断服务函数的一般形式为: 函数类型 函数名(形式参数表)interrupt n using n 关键字interrupt后的n是中断号。对于51单片机,n取值为04。关键字using后的n是所选择的寄存器组。using是一个选项,可省略。如果没有使用using关键字指明寄存器组,中断函数中的所有工作寄存器的内容将被保存到堆栈中。,3.7.6 变量及存储方式 1. 变 量 局部变量:是某一个函数中存在的变量,它只在该函数内部有效。 全局变量:在整个源文件中都存在的变量。其有效区间是从定义点开始到源文件结束,其中的所有函数都可直接访问该变量。如果定义前的函数需要访问该变量,则需要使用extern关键词对该变量进行说明。如果全局变量声明文件之外的源文件需要访问该变量,也需要使用extern关键词进行说明。,由于全局变量一直存在,占用了大量的内存单元,且加大了程序的耦合性,故不利于程序的移植或复用。全局变量可以使用static关键词进行定义,此时该变量只能在变量定义的源文件内使用,不能被其他源文件引用,这种全局变量称为静态全局变量。如果一个其他文件的非静态全局变量需要被某文件引用,则需要在该文件调用前使用extern关键词对该变量进行声明。,3.7.7 宏定义与文件包含在C51程序设计中要经常用到宏定义、文件包含与条件编译。 1. 宏定义 宏定义语句属于C51语言的预处理指令,使用宏可以使变量书写简化,增强程序的可读性、可维护性和可移植性。宏定义分为简单的宏定义和带参数的宏定义。,(1)简单的宏定义 #define 宏替换名 宏替换体 #define是宏定义指令的关键词。宏替换名一般用大写字母来表示,而宏替换体可以是数值常数、算术表达式、字符和字符串等。宏定义可以出现在程序的任何地方。例如: #define uchar unsigned char 在编译时可由C51编译器把“unsigned char”用“uchar”来替代。,(2)带参数的宏定义 #define 宏替换名(形参) 带形参宏替换体 带参数的宏定义可以出现在程序的任何地方,在编译时可由编译器替换为定义的宏替换体,其中的形参用实际参数代替。由于可以带参数,故其应用范围比简单的宏定义更广。,2. 文件包含 文件包含是指一个程序文件将另一个指定的文件的内容包含进去。文件包含的一般格式为: #include 或 #include “文件名” 例如: #include /将特殊功能寄存器包含文件包含 到程序中来 #include /将标准的输入、输出头文件stdio.h包含到程序中来,在使用#include命令时,应注意以下几点: #include命令出现在程序中的位置,决定了被包含的文件就从此处引入源文件。 一个#include命令只能指定一个被包含文件,如果程序中需要包含多个文件则需要使用多个包含命令。 采用格式时,在头文件目录中查找指定文件;采用“文件名”格式时,在当前目录中查找指定文件。 #include命令末尾不需要以分号“;”结束。 #include命令只能调入ASCII文本文件。,3.7.8 库函数1. 常用库函数 特殊功能寄存器包含文件reg51.h或reg52.h。reg51.h中包含所有的8051的sfr及其位定义。reg52.h中包含所有8052的sfr及其位定义,一般系统都包含reg51.h或reg52.h。对于STC15F2K60S2系列单片机,其包含文件为STC15F2K60S2.h。 绝对地址包含文件absacc.h。该文件定义了几个宏,以确定各类存储空间的绝对地址。 输入/输出流函数位于stadio.h文件中。流函数默认用8051的串口作为数据的输入/输出。如果要修改为用户定义的I/O口读写数据,例如,改为LCD显示,可以修改lib目录中的getkey.c及putchar.c源文件,然后在库中替换它们即可。,2. 本征库函数 Keil C51的本征库函数只有9个,数量虽少,但非常有用。本征库函数在头文件intrins.h中定义。_crol_,_cror_:将char型变量循环向左(右)移动指定位数后返回。_irol_,_iror_:将int型变量循环向左(右)移动指定位数后返回。_irol_,_iror_:将long型变量循环向左(右)移动指定位数后返回。_nop_:相当于插入NOP。_testbit_:相当于JBC bitvar测试该位变量并跳转同时清除。_chkfloat_:测试浮点数的状态。,习 题,3-1 C51有哪几种数据存储类型?其中数据类型“idata,code,data,pdata”各对应MCS-51单片机的哪些存储空间?3-2 bit和sbit定义的位变量有什么区别?3-3 判断下列关系表达式或逻辑表达式的运算结果(1或0)。 10=9+1; 0&0; 10&8; 8|0; !(3+2); x=8&y=x;设x=10,y=9。,3-4 设x=4,y=8,说明下列各式运算后x,y和z的值分别是多少。 z=(x+)*(-y);z=(x+)*(-y); z=(+x)*(-y);z=(x+)*(y-)。3-5 C51中while和do-while的不同点是什么?3-6 若C51中的switch操作漏掉break,会发生什么?3-7 编写C51程序,将片外1000H为首地址的连续10个单元的内容,读