第3章C51语言基础 【学习指南】 通过本章的学习,首先要掌握C51特定的数据类型应用方法,掌握变量存储类型的应用,了解变量的存储模式; 运算符和表达式是C语言基础内容,需要熟练掌握; if、for、while等语句表达式也是C语言基础内容,同样非常重要,也需要熟练掌握; 了解库函数、中断、子函数等各类函数的应用。本章与C语言衔接,并引入单片机的控制方法,是C51软件的基础,一定要吃透相关知识,否则无法熟练进行后面的编程。 对于单片机应用系统,除了硬件电路外,还需要软件系统的配合。MCS51单片机有两种开发语言,即汇编语言和C51语言。汇编语言控制精确,效率高,但可读性差,编程难度较大; C51语言是由C语言继承而来的,与C语言不同的是,C51语言运行于单片机平台,而C语言则运行于普通的桌面平台。C51语言继承了C语言的优点,可读性和可移植性强,且结合了对单片机直接控制的特点,已被开发人员广泛接受。 3.1C51语言基础 C51语言除了有ANSI C的所有标准数据类型外,还加入了一些与MCS51单片机密切相关的特殊数据类型。对于具备C语言基础的初学者,要重点学习C51语言特定的数据类型,这些数据类型是单片机和C语言的桥梁,是C语言控制单片机的基础。 3.1.1数据类型 C51语言的数据类型较多,常用的包括unsigned char、unsigned int、sbit等,需要理解这些数据类型的定义和应用场合。表31为C51语言的数据类型。 表31C51语言的数据类型 数据类型说明长度值域 unsigned char无符号字符型单字节 0~255 signed char带符号字符型单字节 -128~+127 unsigned int无符号整型双字节 0~65535 signed int带符号整型双字节 -32768~+32767 unsigned long无符号长整型四字节0~4294967295 signed long带符号长整型四字节-2147483648~+2147483647 float单精度型四字节±1.175494E-38~±3.402823E+38 *指针1~3 字节 对象的地址 bit位变量位 0或 1 sfr8位特殊功能寄存器单字节 0~255 sfr1616位特殊功能寄存器双字节 0~65535 sbit位寻址定义位 0或 1 提示: 表中的无符号字符型(unsigned char)和无符号整型(unsigned int)的定义和值域经常用到,要记住。 其中,bit、sfr、sfr16和sbit是C51语言中特殊的变量类型,下面进行详细介绍。 1. bit位变量 bit位变量是C51编译器的一种扩充数据类型。利用它可定义位变量,但不能定义位指针,也不能定义位数组。它的值是一个二进制数: 0或1,应用在单片机存储器的0x20~0x2F区域。 2. sfr特殊功能寄存器 sfr用来定义8位特殊功能寄存器,占用一个内存单元地址,值域为0~255(0x80~0xFF)。sfr 是C51语言非常重要的关键字,通过sfr可直接访问MCS51单片机内部的所有特殊功能寄存器。 其用法: sfr 特殊功能寄存器名 = 特殊功能寄存器地址常数; 如: sfr P1=0x90; /*定义P1口,其地址90H*/ P1=0xFF; /*把FFH送入P1中(对P1 口的所有引脚置高电平)*/ 3. sfr16为16位特殊功能寄存器 sfr16用来定义16位特殊功能寄存器,占用两字节。 其用法: sfr16 特殊功能寄存器名= 特殊功能寄存器地址常数; 如: 8052的T2定时器,可以定义为 sfr16 T2=0xCC; /*定义8052定时器2,地址为T2L=CCH,T2H=CDH*/ 用sfr16定义16位特殊功能寄存器时,等号后面是它的低位地址,高位地址一定要位于物理低位地址之上。 4. sbit位寻址定义 sbit是一种非常重要且常用的特殊数据类型。sbit定义位寻址对象,访问特殊功能寄存器的某位。 sbit 的用法有三种: (1) sbit 位变量名=位地址,例如: sbit P1_1=0x91; (2) sbit 位变量名=特殊功能寄存器名 ^ 位序号,例如: sfr P1=0x90; sbit P1_1=P1^ 1;/*P1_1 为P1口的P1.1 引脚*/ (3) sbit 位变量名=字节地址 ^ 位序号,例如: sbit P1_1=0x90 ^ 1 3.1.2存储类型 说明了一个变量数据类型后,还可选择说明该变量的存储器类型,指定该变量在C51硬件系统中所使用的存储区域,并在编译时准确地定位。 表32列出的存储器类型和存储器的应用息息相关,特别是单片机的内存相对较小,需要合理分配存储空间、提高运行速度时,必须透彻理解存储器的应用场合。下面详细介绍各种存储器的特点。 表32C51编译器存储器类型 存储器类型说明地址 data内部数据存储器(128字节),访问速度最快0x00~0x7F bdata位/字节寻址内部数据存储器(16字节)0x20~0x2F idata内部数据存储器(256 字节)0x00~0xFF pdata外部数据存储器(256 字节)0x00~0xFF xdata外部数据存储器(64KB)0x0000~0xFFFF code程序存储器(64KB)0x000~0xFFFFH 1. data区 data区的寻址是最快的,应该把使用频率高的变量放在data区,由于空间有限(128B),注意节约使用。 data区的声明如下: unsigned char data ar1; unsigned int data bar[2]; 2. bdata区 位寻址的数据存储区位于0x20~0x2F,可将要求位寻址的数据定义为bdata。如: unsigned char bdata ibr; /*在位寻址区定义 unsigned char 类型的变量 ibr*/ int bdata ab[2]; /*在位寻址区定义数组 ab[2],这些也称为可寻址位对象*/ 如: bit ibr7=ibr^7; /*访问位寻址对象其中一位*/ bit ab12=ab[1]^12;/*操作符“^”后面的位置最大值取决于指定的基址类型,比如: char(0-7),int(0-15),long(0-31) */ 3. idata区 idata区也可以存放使用比较频繁的变量。与外部存储器寻址比较,它的指令执行周期和代码都比较短。例如: unsigned char idata st=0; char idata su; 4. pdata和xdata区 pdata和xdata都定义在外部存储器区域,pdata区只有256字节,而xdata可达65536字节。例如: unsigned char pdata pd; unsigned int xdata px; pdata区的寻址要比xdata区寻址快,因为pdata区寻址只需要装入8位地址,而xdata区需要装入16位地址。 5. code区 code定义在单片机的程序存储器区,数据不可以改变。code区可以存放数据表、跳转向量或状态表,code区在编译时要初始化。由于MCS51单片机的数据存储器空间有限,而程序存储器空间比较充裕,可以把一些不发生变化的数据放在程序存储器。例如: unsigned char code data[8]={0x01,0x02,0x04,0x00,0x06,0x21,0x54,0x32}; 把上述几种存储器类型的特点和地址空间进行归纳,如表33所示。 表33存储器类型特点和地址空间 存储器地址空间容量C51编译器中变(常) 量存储器类型汇编语言中的 寻址方式访问速度 内 部 数 据 区 工作寄存器区0x00~0x1F32data,idata寄存器寻址最快 位地址区0x20~0x2F16bdata,data位寻址、直接寻址快 数据缓冲区 0x30~0x7F80data,idata直接寻址、寄存器间接寻址data快 idata 中 0x80~0xFF128idata寄存器间接寻址中 特殊功能寄存器区0x80~0xFF128—直接寻址快 内(外)部程序存储区0x0000~0xFFFF65536code变址间接寻址最慢 外部数据存储区0x0000~0xFFFF65536xdata、pdata(0x00~0xFF)寄存器间接寻址pdata慢 xdata最慢 提示: 变量存储类型关系到变量在存储器中的地址分配,在C51语言中占有重要位置,要好好理解,看懂并能够记住表33中的地址空间和存储器类型的关系。 3.1.3存储模式 C51编译器允许采用三种存储模式: 小编译模式(SMALL)、紧凑编译模式(COMPACT)、大编译模式(LARGE)。存储模式用于决定未标明存储器类型变量的默认存储器类型。存储模式如表34 所示。 表34存储模式 存储模式说明 SMALL默认的存储类型是data,参数及局部变量放入可直接寻址片内RAM的用户区中(最大128字节)。另外,所有对象(包括堆栈)都必须嵌入片内RAM。栈长很关键,因为实际栈长依赖于函数嵌套调用层数 COMPACT默认的存储类型是pdata,参数及局部变量放入分页的外部数据存储区,栈空间位于片内数据存储区中 LARGE默认的存储类型是xdata,参数及局部变量直接放入片外数据存储区。用此数据指针进行访问效率较低,尤其对两个或多个字节的变量,这种数据类型的访问机制直接影响代码的长度 3.1.4绝对地址访问 1. 绝对宏 C51编译器提供了一组宏定义对单片机的data区、pdata区和xdata区、code区等不同的存储区域进行绝对地址的访问。在程序中,用“#include <absacc.h>”即可使用声明的宏来访问绝对地址,包括CBYTE、DBYTE、PBYTE、 XBYTE、CWORD、DWORD、PWORD和XWORD等。 CBYTE以字节方式寻址code区; CWORD以字方式寻址code区; DBYTE以字节方式寻址data区; DWORD以字方式寻址data区; PBYTE以字节方式寻址pdata区; PWORD以字方式寻址pdata区; XBYTE以字节方式寻址xdata区; XWORD以字方式寻址xdata区。 如: 包含头文件#include <absacc.h>后,通过DBYTE、XBYTE、CBYTE等可访问绝对地址: xvar=XBYTE[0x2000];//把外部数据存储器0x2000单元的一个字节数据送到变量xvar中; XBYTE[0x1F00]=0xf0;//向外部数据存储器0x1F00单元写入数据0xf0; 2. _at_关键字 采用_at_关键字可以指定变量在存储空间中的绝对地址,一般格式如下: 数据类型 [存储器类型] 标识符 _at_ 地址常数 _at_关键字用法比较简单,但需注意以下几点: (1) 不能初始化; (2) bit型变量不能被_at_指定; (3) _at_定义的变量必须是全局变量,不能放在主程序或函数中,否则编译出错。如: unsigned char data ur _at_ 0x20; //ur变量的地址为内部数据存储区0x20 3. 指针 用指针进行绝对地址的访问,更加灵活、简单。定义一个指针变量,把地址赋予绝对地址,就可以访问该变量了。 如: unsigned char data *p; //定义一个指针,指定在data区 p=0x20; //赋地址给指定指针 *p=0x38 //把0x38送给内部数据存储器0x20单元 提示: 以上三种绝对地址访问方式比较重要,特别是指针的访问方式,应用很广,要掌握好。 3.2C51预处理 预处理功能包括宏定义、文件包含和条件编译3个主要部分。预处理命令不同于C语言语句,具有以下特点: (1) 预处理命令以“#”开头,后面不加分号; (2) 预处理命令在编译前执行; (3) 多数预处理命令习惯放在文件的开头。 1. 宏定义 不带参数宏定义的格式为: #define新名称原内容 如:#define uchar unsigned char 该指令的作用是用#define后面的第一个字母组合代替该字母后面的所有内容。 如: #define PI 3.14,以后在程序中用PI代替3.14。 2. 包含文件 包含文件的含义是在一个程序文件中包含其他文件的内容。用文件包含命令可以实现文件包含功能,命令格式为: #include<文件名>或#include "文件名" 例如,在文件中第一句经常为#include <reg51.h>,在编译预处理时,对#include命令进行文件包含处理。实际上就是将文件reg51.h中的全部内容复制插入#include <reg51.h>的命令处。 3. 条件编译 提供一种在编译过程中根据所求条件的值有选择地包含不同代码的手段,实现对程序源代码的各部分有选择地进行编译,称为条件编译。 #if语句中包含一个常量表达式,若该表达式求值的结果不等于0,则执行其后的各行,直到遇到#endif、#elif或#else语句为止(预处理elif相当于else if)。在#if语句中可以使用一个特殊的表达式defined(标识符): 当标识符已经定义时,其值为1; 否则,其值为0。 例如,为了保证hdr.h文件的内容只被包含一次,可用条件语句把该文件的内容包含起来: #ifndef(hdr) #define hdr #include(hdr.h) #endif 3.3运算符与表达式 C语言有丰富的运算符,绝大多数操作都可以通过运算符来处理。运算符就是完成某种特定运算的符号,包括算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、条件运算符等。按照表达式中运算对象的个数又可将运算符分为单目运算符、双目运算符和三目运算符。单目运算符只需一个运算对象,双目运算符要求有两个运算对象,三目运算符则要求有三个运算对象。 表达式是由运算符和运算对象所组成的具有特定含义的式子。运算符和表达式可以组成C语言程序的各种语句。 1. 算术运算符 算术运算符包括以下几种: +加或取正值运算符 -减或取负值运算符 *乘运算符 /除运算符 %取余运算符 ++自增运算符 --自减运算符 注: 自增、自减运算符的作用是使变量的值加1或减1。 ++i先使i值加1,然后再使用; --i先使i值减1,然后再使用; i++使用完i的值以后,再使i值加1; i--使用完i的值以后,再使i值减1。 2. 赋值运算符 “=”就是赋值运算符,其功能是将一个数据赋给一个变量。例如: a=8; b=5; c=a/b; a=b=6; 以上语句执行时,先计算出右边表达式的值,再将该值赋给左边的变量。 3. 关系运算符 关系运算符的功能是判断两个数的关系。C语言有以下六种关系运算符: >大于 <小于 >=大于或等于 <=小于或等于 ==测试等于 !=测试不等于 关系运算符的优先级低于算术运算符,高于赋值运算符。 六种关系运算符中前四种具有相同的优先级,后两种具有相同的优先级,而且前四种的优先级高于后两种。 注: 赋值运算符“=”和测试等于“==”关系运算符不一样。 两个表达式用关系运算符连接起来就构成了关系表达式。关系表达式的值为逻辑值,即只有真(true)和假(false)两种状态,在C语言中用1表示真,用0表示假。若关系表达式的条件成立,则表达式的值为真(1),否则为假(0)。 4. 逻辑运算符 逻辑运算符的功能是通过逻辑运算求条件式的逻辑值。 C语言有以下三种逻辑运算符: &&逻辑与 ||逻辑或 !逻辑非 逻辑表达式的格式如下: 逻辑与: 条件式1 && 条件式2 逻辑或: 条件式1 || 条件式2 逻辑非: ! 条件式 三种逻辑运算中,逻辑非的优先级别最高,且高于算术运算符; 逻辑或的优先级别最低,低于关系运算符,但高于赋值运算符。 5. 位运算符 位运算符的功能是对变量按位进行运算,但并不改变运算变量的值。C语言有以下六种位运算符: &位与 |位或 ~位取反 ^位异或 <<左移 >>右移 六种位运算符的优先级由高到低的顺序为: 位取反~、左移<<、右移>>、位与&、位异或^、位或|。 6. 复合赋值运算符 复合赋值运算符是C语言的一种特色,它简化了代码的编写。该类运算符的功能是将某个变量先与表达式进行指定的运算,再将运算结果赋予该变量。C语言有以下10种复合赋值运算符: +=加并赋值运算符 -=减并赋值运算符 *=乘并赋值运算符 /=除并赋值运算符 %=取余并赋值运算符 <<=左移并赋值运算符 >>=右移并赋值运算符 &=位与并赋值运算符 |=位或并赋值运算符 ^=位异或并赋值运算符 C语言中凡是双目运算都可以用复合赋值运算符来表示,格式如下: 变量复合赋值运算符表达式 如: a+=5,相当于a=a+5; a*=b-6,相当于a=a*(b-6); y/=x+9,相当于y=y/(x+9)。 7. 条件运算符 条件运算符是三目运算符,格式如下: 判断结果=(判断式)?结果1: 结果2 其含义是先求判断式的值,若为真,则判断结果=结果1; 若为假,则判断结果=结果2。如: a=8;b=10; max=a>b?a:b; 结果: max=10; 8. 指针和地址运算符 * 指针运算符(取内容) & 取地址运算符(取地址) 一般形式分别如下: 取内容: 变量= * 指针变量 取地址: 指针变量= & 目标变量 变量前面加“*”说明该变量为指针,所以操作时取的不是变量的值,而是将指针变量所指向的目标变量的值赋给左边的变量; 取地址运算是将目标变量的地址赋给左边的变量。“*”和“&”运算符均为单目运算符。 3.4C51语句 C51语句是单片机执行的操作命令,每条语句都以分号结尾。需要注意的是,变量、函数的声明部分也以分号结尾,但不是语句。 3.4.1表达式语句 由一个表达式加上一个分号就构成了表达式语句。如: i=7; j=a=b; i++; 3.4.2复合语句 用大括号“{ }”将多条语句括起来就组成了复合语句,也称为功能块。 复合语句中的每一条语句都必须以“;”结束,而不允许将“;”写在“}”外。复合语句不需要以“;”结束。 C语言中将复合语句视为一条单语句,也就是说在语法上等同于一条单语句。对于一个函数而言,函数体就是一个复合语句。例如: { i=7; j=a=b; i++; } 3.4.3空语句 空语句是仅由一个分号“;”组成的语句。空语句什么也不做。 语句格式: ; 3.4.4函数调用语句 函数调用的一般形式加上分号就构成了函数调用语句。 语句格式: 函数名(实际参数表); 执行函数调用语句就是调用函数体并把实际参数赋予函数定义中的形式参数,然后执行被调函数体中的语句。 3.4.5控制语句 控制语句用于控制程序的流程,以实现程序的各种结构方式。C51语言的控制语句有以下几类: 1. 选择语句 if (1) if分支结构 if(表达式) { 语句序列; } 其他语句 功能: 如果表达式的值为真,则执行语句,否则不执行语句。流程图如图31所示。 (2) ifelse分支结构 if(表达式) {语句序列1;} else {语句序列2;} 其他语句; 功能: 如果表达式的值为真,则执行语句序列1,否则执行语句序列2。流程图如图32所示。 图31if分支结构流程图 图32ifelse分支结构流程图 (3) ifelse if 分支结构 if(表达式1){语句序列1;} else if(表达式2){语句序列2;} else if(表达式3){语句序列3;} … else if(表达式n){语句序列n;} else {语句序列n+1;} 其他语句; 流程图如图33所示。 图33ifelse if 分支结构流程图 2. switch语句 switch语句是多分支选择语句,也称开关语句。一般格式如下: switch(表达式) { case 常量表达式1:语句序列1; case 常量表达式2:语句序列2; … case 常量表达式n:语句序列n; default :语句序列n+1; } 每个case和default出现的顺序不影响执行结果,但每个常量表达式值必须互不相同。该语句的执行过程如下: (1) 求switch后括号内的表达式的值,并将其值与各case后的常量表达式值进行比较; (2) 当表达式的值与某个常量表达式值相等时,则执行该常量表达式后边的语句序列; (3) 接着执行下一个常量表达式后边的语句序列,直到后边所有的语句序列都执行完(即执行到语句序列n+1); (4) 如果表达式的值与所有case后的常量表达式值都不相等,则执行default后面的语句序列。 通常当某个常量表达式的值与switch后表达式的值相等时,只需要执行该case后的语句序列,不希望程序一直执行下去,直到语句序列n+1。要达到这一目的,只需要在每个语句序列后加上“break”语句即可。 格式如下: switch(表达式) { case 常量表达式1:语句序列1; break; case 常量表达式2:语句序列2; break; … case 常量表达式n:语句序列n; break; default :语句序列n+1; } 流程图如图34所示。 图34switch分支结构流程图 3. for语句 for语句格式如下: for(表达式1;表达式2;表达式3) {语句序列;} //循环体,可为空 表达式1通常为赋值表达式,用来确定循环结构中控制循环次数的变量的初始值,实现循环控制变量的初始化。 表达式2通常为关系表达式或逻辑表达式,用来判断循环是否继续进行。 表达式3通常为表达式语句,用来描述循环控制变量的变化,最常见的是自增或自减表达式,实现对循环控制变量的修改,当循环条件满足时就执行循环体内的语句序列。 图35for分支结构流程图 语句序列可以是简单语句,也可以是复合语句。若只有一条语句,则可以省略{ }。 for语句的执行过程如下: (1) 计算表达式1的值,为循环控制变量赋初值。 (2) 计算表达式2的值,如果为“真”则执行循环体一次,否则退出循环,执行for循环后的语句。 (3) 如果执行了循环体语句,则执行循环体后,要计算表达式3的值,调整循环控制变量。然后回到第(2)步重复执行,直到表达式2的值为“假”时,退出循环。流程图如图35所示。 4. while 语句 (1) while语句 while 语句用于实现“当型”循环的语句,格式为: while (条件表达式) { 语句序列; //循环体 } 流程图如图36所示。 (2) dowhile 语句 dowhile 语句用于实现“直到型”循环的语句,格式如下: do { 语句序列; } while(表达式); 流程图如图37所示。 图36while 分支结构流程图 图37dowhile 分支结构流程图 提示: if、for、while等语句是程序代码的重要组成部分,难度不算大,是编程基础代码,要好好掌握。 3.5C51函数 3.5.1函数的一般格式 1. 函数的定义 C程序由一个主函数main()和若干个其他函数组成。由主函数调用其他函数,其他函数也可以互相调用,同一个函数可以被调用多次。 函数定义的一般格式为: 函数类型 函数名 (形式参数列表) { 局部变量声明; 语句; (有返回值的要有return语句) } 2. 函数返回值 返回语句return用来回送一个数值给定义的函数,从函数中退出。返回值是通过return语句返回的。如果函数无须返回值,可以用void类型指明函数无返回值。 3. 形式参数与实际参数 与使用变量一样,在调用一个函数之前,必须对该函数进行声明。函数声明的一般格式为: 函数类型函数名(形式参数列表) 函数定义时参数列表中的参数称为形式参数,简称形参。 函数调用时所使用的替换参数,是实际参数,简称实参。定义的形参与函数调用的实参类型应该一致,书写顺序应该相同。 4. 调用函数的方式 被调用的函数必须是已经存在的函数。 (1) 函数作为语句。把函数调用作为一个语句,不使用函数返回值,只是完成函数所定义的操作。例如: DelayMS(150); (2) 函数作为表达式。函数调用出现在一个表达式中,使用函数的返回值。例如: int k; k=sum(a,b); (3) 函数作为一个参数。函数调用作为另一个函数的实参。例如: int k; k=sum(sum(a,b),c); 3.5.2中断函数 C51语言中断函数的结构与其他函数的结构类似,但中断函数不带任何参数,而且使用中断函数之前不需要声明。 定义中断函数的格式如下: void函数名( ) interrupt 中断号n (using 工作寄存器组m) 在中断函数中为了避免数据的冲突,可指定一个寄存器组; 若不需要指定,则该项可以省略。MCS51单片机中断源和入口地址如表35所示。 表35中断源与入口地址 中断号n中断源入口地址8n+3 0外部中断00x03 1定时器/计数器00x0B 2外部中断10x13 3定时器/计数器10x1B 4串行口0x23 3.5.3C51的库函数 C51语言具有丰富的可供直接调用的库函数,使用库函数可使程序代码简单,结构清晰,易于调试和维护。 每个函数都在相应的头文件(.h)中有原型声明。如果使用库函数,必须在源程序中用预处理命令“#include”将与该函数相关的头文件(即包含了该函数的原型声明文件)包含进来,否则将不能保证函数的正确执行。 3.5.4本征库函数和非本征库函数 本征库函数是指编译时直接将固定的代码插入当前行,大大提高了函数访问的效率。 C51有以下9个本征库函数: _crol_,_cror_: 将无符号字符型变量循环向左(右)移动指定位数后返回; _irol_,_iror_: 将无符号整型变量循环向左(右)移动指定位数后返回; _lrol_,lror_: 将无符号长整型变量循环向左(右)移动指定位数后返回; _nop_: 空操作; _testbit_: 测试该位变量并跳转,同时将该位变量清除; _chkfloat_: 检查浮点数的类型。 3.5.5几类重要的库函数 1. 内部函数intrins.h 如: #inclucle <intrins.h> void main() { unsigned int x; x=0x00ff; x=_irol_(x,4); } 运行后,x=0x0ff0。 2. 绝对地址访问函数absacc.h 通过包含头文件 absacc.h 进行绝对地址访问。 方法: 把通用指针指向各存储空间的首地址,并按存取对象类型实施指针强制,再用定义宏说明为数组名。对于绝对地址对象的存取,用指定下标的抽象数组来实现。 char类型: CBYTE[i]DBYTE[i]PBYTE[i]XBYTE[i] int类型: CWORD[i]DWORD[i]PWORD[i]XWORD[i] 3. 缓冲区处理函数string.h (1) 计算字符串s的长度strlen。 原型: extern int strlen(char *s); 说明: 返回s的长度,不包括结束符NULL。 (2) 由src所指内存区域复制count个字节到dest所指内存区域。 memcpy原型: extern void *memcpy(void *dest, void *src, unsigned int count); 说明: src和dest所指内存区域不能重叠,函数返回指向dest的指针。 (3) 由src所指内存区域复制count个字节到dest所指内存区域。 memmove原型: extern void *memmove(void *dest, const void *src, unsigned int count); 说明: 与memcpy工作方式相同,但src和dest所指内存区域可以重叠,复制后src内容会被更改。函数返回指向dest的指针。 (4) 比较内存区域buf1和buf2的前count个字节。 memcmp原型: extern int memcmp(void *buf1, void *buf2, unsigned int count); 说明: 当buf1<buf2时,返回值<0; 当buf1=buf2时,返回值=0; 当buf1>buf2时,返回值>0。 (5) 把buffer所指内存区域的前count个字节设置成字符c。 memset原型: extern void *memset(void *buffer, int c, int count); 说明: 返回指向buffer的指针。 (6) 从buf所指内存区域的前count个字节查找字符ch。 memchr原型: extern void *memchr(void *buf, char ch, unsigned count); 说明: 当第一次遇到字符ch时停止查找。如果成功,返回指向字符ch的指针; 否则返回NULL。 提高篇 Keil编译器支持多文件系统,但文件再多,也只能包含一个入口函数main(); 建立ex1.c,ex2.c和ex3.c三个文件,其中main()函数放在ex1.c文件中。下面比较绝对地址的几种应用方法。 ex1.c文件 #include "reg51.h" //DBYTE、CBYTE、XBYTE必须先加absacc.h头文件 #include "absacc.h" #define uchar unsigned char uchar xx; void main() { DBYTE[0x20]=0x88; //把数据0x88送到内部RAM0x20单元 DBYTE[0x21]=0x99; //把数据0x99送到内部RAM0x21单元 xx=0x21; //把数据0x21送到变量xx func1(); func2(); } Keil main程序运行如图38所示。 图38Keil main程序运行图 ex2.c文件 #define uchar unsigned char data uchar *p; void func1() { p=0x20; //指针地址指向0x20 *p=0x44; //把数据0x44送到内部RAM0x20单元 } Keil func1程序运行如图39所示。 图39Keil func1程序运行图 ex3.c文件 #define uchar unsigned char /*注意:此_at_变量定义不能放在函数体内,否则编译出错*/ unsigned char a_20h _at_ 0x20; void func2() { a_20h=0x22; //把数据0x22送到内部RAM0x20单元 } Keil func2程序运行如图310所示。 图310Keil func2程序运行图 注: 查看内部存储器指令 D:0x20(X外部RAM,D内部RAM(0x000x7F),I内部RAM(0x0000xFF),C程序存储器)。 习题与思考 31请归纳C51语言有哪些关键字? 32判断下列bit型变量定义的正误: bitdataa1; bitbdataa2; bitpdataa3; bitxdataa4; 33在C51程序里,一般函数和中断函数有什么不同? 34按给定存储器类型和数据类型,写出下列变量的说明形式: (1) 在data区定义字符变量val1; (2) 在idata区定义整型变量val2; (3) 在xdata区定义无符号字符数组val3[9]; (4) 定义位寻址变量flag; (5) 定义特殊功能寄存器P3; (6) 定义特殊功能寄存器TCON; (7) 定义16位特殊功能寄存器T0。 35用三种不同的循环结构实现1~100的求和。 36用指令实现下列功能: (1) 用绝对宏实现: 读出外部数据存储器0x40内容,送到内部存储器0x30单元。 (2) 用指针实现: 读出外部数据存储器0x20内容,送到内部存储器0x20单元。 (3) 用_at_关键字实现: 读出外部数据存储器0x20内容,送到内部存储器0x20单元。 (4) 用_at_关键字实现: 读出程序存储器0x20内容,送到外部数据存储器0x20单元。 37如图311所示,当开关K闭合时4个发光二极管亮,K断开时4个发光二极管灭。试编写程序。 图311按键控制指示灯电路图 38写出下列代码的while和dowhile的算法: unsigned char i; unsigned int s=0; void main( ) { for(i=0;i<250;i++){s=s+i;} }