1 C 语言介绍
C 语言是在 70 年代初问世的。 一九七八年由美国电话电报公司(AT&T)贝尔实验室正式发表了C 语言。 同时由 B.W.Kernighan 和 D.M.Ritchit 合著了著名的“THE C PROGRAMMING LANGUAGE” 一书。 通常简称为《K&R》 , 也有人称之为《K&R》标准。 但是, 在《K&R》 中并没有定义一个完整的标准 C 语言, 后来由美国国家标准协会(American National Standards Institute) 在此基础上制定了一个C 语言标准, 于一九八三年发表。 通常称之为 ANSI C。
早期的 C 语言主要是用于 UNIX 系统。 由于C 语言的强大功能和各方面的优点逐渐为人们认识, 到了八十年代, C 开始进入其它操作系统, 并很快在各类大、中、 小和微型计算机上得到了广泛的使用, 成为当代最优秀的程序设计语言之一。在实际工程应用中, 51 单片机的程序设计一般都是采用 C 语言编写, 通过相应的编译器, 得到可执行代码, 以提高程序开发效率。 但由于单片机的内部资源有限, 并结合单片机的特点(如位操作) , 与标准 C 语言相比有所不同, 称为C51 程序。
C 语言的特点
--语言简洁、 紧凑, 使用方便、 灵活。
--运算符丰富。
--数据结构丰富。 具有现代化语言的各种数据结构。
--可进行结构化程序设计。
--可以直接对计算机硬件进行操作。
--生成的目标代码质量高, 程序执行效率高。
--可移植性好。
C 语言程序采用函数结构, 每个 C 语言程序由一个或多个函数组成, 在这些函数中至少应包含一个主函数 main(), 也可以包含一个 main()函数和若干个其它的功能函数。 不管 main()函数放于何处, 程序总是从 main()函数开始执行,执行到 main()函数结束则结束。 在 main()函数中调用其它函数, 其它函数也可以相互调用, 但 main()函数只能调用其它的功能函数, 而不能被其它的函数所调用。 功能函数可以是 C 语言编译器提供的库函数, 也可以是由用户定义的自定义函数。在编制 C 程序时, 程序的开始部分一般是预处理命令、 函数说明和变量定义等。
用 C 语言编写 51 单片机程序与用汇编语言编写 51 单片机程序不同, 汇编语言必须要考虑其存储器结构, 尤其必须考虑其片内数据存储器与特殊功能寄存器的使用以及按实际地址处理端口数据。
用 C 语言编写的 51 单片机应用程序, 则不用像汇编语言那样须具体组织、分配存储器资源和处理端口数据, 但在 C 语言编程中, 对数据类型与变量的定义,必须要与单片机的存储结构相关联, 否则编译器不能正确地映射定位。
用 C 语言编写单片机应用程序与标准的 C 语言程序也有相应的区别: C 语言编写单片机应用程序时, 需根据单片机存储结构及内部资源定义相应的数据类型和变量, 而标准的 C 语言程序不需要考虑这些问题。
C51 包含的数据类型、 变量存储模式、 输入输出处理、 函数等方面与标准的C 语言有一定的区别。 其它的语法规则、 程序结构及程序设计方法等与标准的 C语言程序设计相同。
1.1 C51 和 C 的区别
C51 的语法规定、 程序结构及程序设计方法都与标准的 C 语言程序设计相同,但 C51 程序与标准的 C 程序在以下几个方面不一样:
(1) C51 中定义的库函数和标准 C 语言定义的库函数不同。 标准的 C 语言定义的库函数是按通用微型计算机来定义的, 而 C51 中的库函数是按 51 单片机相应情况来定义的;
(2) C51 中的数据类型与标准 C 的数据类型也有一定的区别, 在 C51 中还增加了几种针对 51 单片机特有的数据类型;
(3) C51 变量的存储模式与标准 C 中变量的存储模式不一样, C51 中变量的存储模式是与 51 单片机的存储器紧密相关;
(4) C51 与标准 C 的输入输出处理不一样, C51 中的输入输出是通过 51 串行口来完成的, 输入输出指令执行前必须要对串行口进行初始化;
5) C51 与标准 C 在函数使用方面也有一定的区别, C51 中有专门的中断函数。
2 C 语言基础知识
2.1 数据类型
C 语言包含的数据类型如下图所示:
C51 的数据类型分为基本数据类型和组合数据类型, 情况与标准 C 中的数据类型基本相同, 但其中 char 型与 short 型相同, float 型与 double 型相同, 另外, C51 中还有专门针对于 51 单片机的特殊功能寄存器型和位类型。
(1) C51 基本数据类型如下图所示:
(2) *指针型:
指针型本身就是一个变量, 在这个变量中存放的指向另一个数据的地址。 这个指针变量要占用一定的内存单元, 对不同的处理器其长度不一样, 在 C51 中它的长度一般为 1~3 个字节。
(3) 特殊功能寄存器型
这是 C51 扩充的数据类型, 用于访问 51 单片机中的特殊功能寄存器数据,它分 sfr 和 sfr16 两种类型。 其中: sfr 为字节型特殊功能寄存器类型, 占一个内存单元, 利用它可以访问 51 内部的所有特殊功能寄存器; sfr16 为双字节型特殊功能寄存器类型, 占用两个字节单元, 利用它可以访问 51 内部的所有两个字节的特殊功能寄存器。 在 C51 中对特殊功能寄存器的访问必须先用 sfr 或sfr16 进行声明。
(4) 位类型
这也是 C51 中扩充的数据类型, 用于访问 51 单片机中的可寻址的位单元。在 C51 中, 支持两种位类型: bit 型和 sbit 型。 它们在内存中都只占一个二进制位, 其值可以是“1” 或“0” 。 其中: 用 bit 定义的位变量在 C51 编译器编译时, 在不同的时候位地址是可以变化的, 而用 sbit 定义的位变量必须与 51 单片机的一个可以寻址位单元或可位寻址的字节单元中的某一位联系在一起, 在 C51编译器编译时, 其对应的位地址是不可变化的。
KEIL C51 编译器能够识别的基本数据类型:
在 C51 语言程序中, 有可能会出现在运算中数据类型不一致的情况。 C51 允 许任何标准数据类型的隐式转换, 隐式转换的优先级顺序如下:
Bit→char→int→long→float→signed→unsigned
也就是说, 当 char 型与 int 型进行运算时, 先自动对 char 型扩展为 int 型, 然后与 int 型进行运算, 运算结果为 int 型。 C51 除了支持隐式类型转换外, 还可以通过强制类型转换符“() ” 对数据类型进行人为的强制转换。
C5l 编译器除了能支持以上这些基本数据类型之外, 还能支持一些复杂的组合型数据类型, 如数组类型、 指针类型、 结构类型、 联合类型等这些复杂的数据类型。 对于初学者我们要求先掌握 C 语言基础知识, 把基础的掌握了在学习复杂的。
2.2 C51 运算量
(1) 常量
常量是指在程序执行过程中其值不能改变的量。 在 C51 中支持整型常量、 浮点型常量、 字符型常量和字符串型常量。
1, 整型常量
整型常量也就是整型常数, 根据其值范围在计算机中分配不同的字节数来存放。 在 C51 中它可以表示成以下几种形式:
十进制整数。 如 234、 -56、 0 等。
十六进制整数。 以 0x 开头表示, 如 0x12 表示十六进制数 12H。
长整数。 在 C51 中当一个整数的值达到长整型的范围, 则该数按长整型存放,在存储器中占四个字节, 另外, 如一个整数后面加一个字母 L, 这个数在存储器中也按长整型存放。 如123L 在存储器中占四个字节。
2, 浮点型常量
浮点型常量也就是实型常数。 有十进制表示形式和指数表示形式。 十进制表示形式又称定点表示形式, 由数字和小数点组成。 如 0.123、 34.645 等都是十进制数表示形式的浮点型常量。 指数表示形式为: [] 数字 [.数字] e []数字
例如: 123.456e-3、 -3.123e2 等都是指数形式的浮点型常量。
3, 字符型常量
字符型常量是用单引号引起的字符, 如‘a’ 、 ‘1’ 、 ‘F’ 等。 可以是可显示的 ASCII 字符, 也可以是不可显示的控制字符。 对不可显示的控制字符须在前面加上反斜杠“\” 组成转义字符。 利用它可以完成一些特殊功能和输出时的格式控制。 常用的转义字符如下表所示。
4, 字符串型常量
字符串型常量由双引号“” 括起的字符组成。 如“D” 、 “1234” 、 “ABCD”等。 注意字符串常量与字符常量是不一样, 一个字符常量在计算机内只用一个字节存放, 而一个字符串常量在内存中存放时不仅双引号内的字符一个占一个字节, 而且系统会自动的在后面加一个转义字符“\o” 作为字符串结束符。 因此不要将字符常量和字符串常量混淆, 如字符常量‘A’ 和字符串常量“A” 是不一样
的。
(2) 变量
变量是在程序运行过程中其值可以改变的量。 一个变量由两部分组成: 变量名和变量值。 在 C51 中, 变量在使用前必须对变量进行定义, 指出变量的数据类型和存储模式。 以便编译系统为它分配相应的存储单元。 定义的格式如下:
[存储种类] 数据类型说明符 [存储器类型] 变量名 1[=初值], 变量名2[初值]…;
1, 数据类型说明符
在定义变量时, 必须通过数据类型说明符指明变量的数据类型, 指明变量在存储器中占用的字节数。 可以是基本数据类型说明符, 也可以是组合数据类型说明符, 还可以是用 typedef 定义的类型别名。
在 C51 中, 为了增加程序的可读性, 允许用户为系统固有的数据类型说明符
用 typedef 起别名, 格式如下:
typedef c51 固有的数据类型说明符 别名;
定义别名后, 就可以用别名代替数据类型说明符对变量进行定义。 别名可以用大写, 也可以用小写, 为了区别一般用大写字母表示。
【例】 typedef 的使用。
typedef unsigned int u8;
typedef unsigned char u16;
u8 a1=0x12;
u16 a2=0x1234;
2, 变量名
变量名是 C51 区分不同变量, 为不同变量取的名称。 在 C51 中规定变量名可以由字母、 数字和下划线三种字符组成, 且第一个字母必须为字母或下划线。 变量名有两种: 普通变量名和指针变量名。 它们的区别是指针变量名前面要带“*”
号。
3, 存储种类
存储种类是指变量在程序执行过程中的作用范围。 C51 变量的存储种类有四种, 分别是自动(auto)、 外部(extern)、 静态(static)和寄存器(register)。
a. auto:
使用 auto 定义的变量称为自动变量, 其作用范围在定义它的函数体或复合语句内部, 当定义它的函数体或复合语句执行时, C51 才为该变量分配内存空间,结束时占用的内存空间释放。 自动变量一般分配在内存的堆栈空间中。 定义变量时, 如果省略存储种类, 则该变量默认为自动(auto)变量。
b. extern:
使用 extern 定义的变量称为外部变量。 在一个函数体内, 要使用一个已在该函数体外或别的程序中定义过的外部变量时, 该变量在该函数体内要用extern 说明。 外部变量被定义后分配固定的内存空间, 在程序整个执行时间内都有效, 直到程序结束才释放。
c. static:
使用 static 定义的变量称为静态变量。 它又分为内部静态变量和外部静态变量。 在函数体内部定义的静态变量为内部静态变量, 它在对应的函数体内有效,一直存在, 但在函数体外不可见, 这样不仅使变量在定义它的函数体外被保护,还可以实现当离开函数时值不被改变。 外部静态变量上在函数外部定义的静态变量。 它在程序中一直存在, 但在定义的范围之外是不可见的。 如在多文件或多模块处理中, 外部静态变量只在文件内部或模块内部有效。
d. register:
使用 register 定义的变量称为寄存器变量。 它定义的变量存放在 CPU 内部的寄存器中, 处理速度快, 但数目少。 C51 编译器编译时能自动识别程序中使用频率最高的变量, 并自动将其作为寄存器变量, 用户可以无需专门声明。
4, 存储器类型
存储器类型是用于指明变量所处的单片机的存储器区域情况。 存储器类型与存储种类完全不同。 C51 编译器能识别的存储器类型有以下几种, 见表所示。
定义变量时也可以省“存储器类型” , 省时 C51 编译器将按编译模式默认存
储器类型。
5, 特殊功能寄存器变量
51 系列单片机片内有许多特殊功能寄存器, 通过这些特殊功能寄存器可以控制 51 系列单片机的定时器、 计数器、 串口、 I/O 及其它功能部件, 每一个特殊功能寄存器在片内 RAM 中都对应于一个字节单元或两个字节单元。
在 C51 中, 允许用户对这些特殊功能寄存器进行访问, 访问时须通过 sfr 或sfr16 类型说明符进行定义, 定义时须指明它们所对应的片内 RAM 单元的地址。
格式如下:
sfr 或 sfr16 特殊功能寄存器名=地址;
sfr 用于对 51 单片机中单字节的特殊功能寄存器进行定义, sfr16 用于对双字节特殊功能寄存器进行定义。 特殊功能寄存器名一般用大写字母表示。 地址一般用直接地址形式。
【例】 特殊功能寄存器的定义。
sfr sfr | PSW=0xd0; SCON=0x98; |
6, 位变量
在 C51 中, 允许用户通过位类型符定义位变量。位类型符有两个: bit 和 sbit。可以定义两种位变量。
bit 位类型符用于定义一般的可位处理位变量。 它的格式如下:
bit 位变量名;
在格式中可以加上各种修饰, 但注意存储器类型只能是 bdata、 data、 idata。只能是片内 RAM 的可位寻址区, 严格来说只能是 bdata。
sbit 位类型符用于定义在可位寻址字节或特殊功能寄存器中的位, 定义时须指明其位地址, 可以是位直接地址, 可以是可位寻址变量带位号, 也可以是特殊功能寄存器名带位号。 格式如下:
sbit 位变量名=位地址;
如位地址为位直接地址, 其取值范围为 0x00~0xff; 如位地址是可位寻址变量带位号或特殊功能寄存器名带位号, 则在它前面须对可位寻址变量或特殊功能寄存器进行定义。 字节地址与位号之间、 特殊功能寄存器与位号之间一般用“^”作间隔。 如定义 51 单片机管脚:
sbit LED=P1^0;
在 C51 中, 为了用户处理方便, C51 编译器把 51 单片机的常用的特殊功能寄存器和特殊位进行了定义, 放在一个“reg51.h” 或“reg52.h” 的头文件中, 当用户要使用时, 只须要在使用之前用一条预处理命令#include <reg52.h>把这个头文件包含到程序中, 然后就可使用殊功能寄存器名和特殊位名称。
2.3 C51 运算符
(1) 赋值运算符
赋值运算符“=” , 在 C51 中, 它的功能是将一个数据的值赋给一个变量,如 x=10。 利用赋值运算符将一个变量与一个表达式连接起来的式子称为赋值表达式, 在赋值表达式的后面加一个分号“; ” 就构成了赋值语句, 一个赋值语句的格式如下:
变量=表达式;
执行时先计算出右边表达式的值, 然后赋给左边的变量。 例如:
x=8+9; | /*将 8+9 的值赋绐变量 x*/ |
x=y=5; | /*将常数 5 同时赋给变量 x 和 y*/ |
在 C51 中, 允许在一个语句中同时给多个变量赋值, 赋值顺序自右向左。 |
(2)算术运算符
C51支持的算术的运算符有:
+ 加或取正值运算符
- 减或取负值运算符
* 乘运算符
/ 除运算符
% 取余运算符
加、 减、 乘运算相对比较简单, 而对于除运算, 如相除的两个数为浮点数,则运算的结果也为浮点数, 如相除的两个数为整数, 则运算的结果也为整数, 即为整除。 如 25.0/20.0 结果为 1.25, 而 25/20 结果为 1。
对于取余运算, 则要求参加运算的两个数必须为整数, 运算结果为它们的余
数。 例如: x=5%3, 结果 x 的值为 2。
(3) 关系运算符
C51 中有 6 种关系运算符:
> < | 大于 小于 | |
>= <= | 大于等于 小于等于 | |
= = | 等于 | |
!= | 不等于 | |
关系运算用于比较两个数的大小, 用关系运算符将两个表达式连接起来形成的式子称为关系表达式。 关系表达式通常用来作为判别条件构造分支或循环程序。 关系表达式的一般形式如下: | ||
表达式 1 | 关系运算符 | 表达式 2 |
关系运算的结果为逻辑量, 成立为真(1) , 不成立为假(0) 。 其结果可以作为一个逻辑量参与逻辑运算。 例如: 5>3, 结果为真(1) , 而 10= =100, 结果为假(0) 。
注意: 关系运算符等于“= =” 是由两个“=” 组成。
(4) 逻辑运算符
C51 有 3 种逻辑运算符:
|| && ! | 逻辑或 逻辑与 逻辑非 |
关系运算符用于反映两个表达式之间的大小关系, 逻辑运算符则用于求条件式的逻辑值, 用逻辑运算符将关系表达式或逻辑量连接起来的式子就是逻辑表达式。
逻辑与, 格式:
条件式 1 && 条件式 2
当条件式 1 与条件式 2 都为真时结果为真(非 0 值) , 否则为假(0 值) 。
逻辑或, 格式:
条件式 1 || 条件式 2
当条件式 1 与条件式 2 都为假时结果为假(0 值) , 否则为真(非 0 值) 。
逻辑非, 格式:
! 条件式
当条件式原来为真(非 0 值) , 逻辑非后结果为假(0 值) 。 当条件式原来为假(0 值) , 逻辑非后结果为真(非 0 值) 。
例如: 若 a=8, b=3, c=0, 则! a 为假, a && b 为真, b && c 为假。
(5) 位运算符
C51 语言能对运算对象按位进行操作, 它与汇编语言使用一样方便。 位运算是按位对变量进行运算, 但并不改变参与运算的变量的值。 如果要求按位改变变量的值, 则要利用相应的赋值运算。 C51 中位运算符只能对整数进行操作, 不能对浮点数进行操作。 C51 中的位运算符有:
& | ^ ~ << >> | 按位与 按位或 按位异或 按位取反 左移 右移 |
【例】 设 a=0x45=01010100B, b=0x3b=00111011B, 则 a&b、 a|b、 a^b、 ~a、a<<2、 b>>2 分别为多少?
a&b=00010000b=0x10。
a|b=01111111B=0x7f。
a^b=01101111B=0x6f。
~a=10101011B=0xab。
a<<2=01010000B=0x50。
b>>2=00001110B=0x0e。
(6) 复合赋值运算符
C51 语言中支持在赋值运算符“=” 的前面加上其它运算符, 组成复合赋值运算符。 下面是 C51 中支持的复合赋值运算符
+= *= %= |= ~= <<= | 加法赋值 乘法赋值 取模赋值 逻辑或赋值 逻辑非赋值 左移位赋值 | ?+ /= &= ^= >>= | 减法赋值 除法赋值 逻辑与赋值 逻辑异或赋值 右移位赋值 |
复合赋值运算的一般格式如下:
变量 复合运算赋值符 表达式
它的处理过程: 先把变量与后面的表达式进行某种运算, 然后将运算的结果赋给前面的变量。 其实这是 C51 语言中简化程序的一种方法, 大多数二目运算都可以用复合赋值运算符简化表示。 例如: a+=6 相当于 a=a+6; a*=5 相当于 a=a*5;b&=0x55 相当于 b=b&0x55; x>>=2 相当于 x=x>>2。
(7) 逗号运算符
在 C51 语言中, 逗号“, ” 是一个特殊的运算符, 可以用它将两个或两个以上的表达式连接起来, 称为逗号表达式。 逗号表达式的一般格式为:
表达式 1, 表达式 2, ……, 表达式 n
程序执行时对逗号表达式的处理: 按从左至右的顺序依次计算出各个表达式的值, 而整个逗号表达式的值是最右边的表达式( 表达式 n) 的值。 例如:x=(a=3,6*3)结果 x 的值为 18。
(8) 条件运算符
条件运算符“? : ” 是 C51 语言中唯一的一个三目运算符, 它要求有三个运算对象, 用它可以将三个表达式连接在一起构成一个条件表达式。 条件表达式的
一般格式为:
逻辑表达式? 表达式 1: 表达式 2
其功能是先计算逻辑表达式的值, 当逻辑表达式的值为真(非 0 值) 时, 将计算的表达式 1 的值作为整个条件表达式的值; 当逻辑表达式的值为假(0 值)时, 将计算的表达式 2 的值作为整个条件表达式的值。
例如: 条件表达式max=(a>b)?a:b 的执行结果是将 a 和 b 中较大的数赋值给变量 max。
(9) 指针与地址运算符
指针是 C51 语言中的一个十分重要的概念, 在 C51 中的数据类型中专门有一种指针类型。 指针为变量的访问提供了另一种方式, 变量的指针就是该变量的地址, 还可以定义一个专门指向某个变量的地址的指针变量。
为了表示指针变量和它所指向的变量地址之间的关系, C51 中提供了两个专门的运算符:
* 指针运算符
& 取地址运算符
指针运算符“*” 放在指针变量前面, 通过它实现访问以指针变量的内容为地址所指向的存储单元。 例如: 指针变量 p 中的地址为 2000H, 则*p 所访问的是地址为 2000H 的存储单元, x=*p, 实现把地址为 2000H 的存储单元的内容送给变量 x。
取地址运算符“&” 放在变量的前面, 通过它取得变量的地址, 变量的地址通常送给指针变量。 例如: 设变量 x 的内容为 12H, 地址为 2000H, 则&x 的值为2000H, 如有一指针变量 p, 则通常用 p=&x, 实现将 x 变量的地址送给指针变量p, 指针变量 p 指向变量 x, 以后可以通过*p 访问变量 x。
2.4 C51 表达式及符合语句
(1) 表达式语句
在表达式的后边加一个分号“; ” 就构成了表达式语句 ,如:
a=++b*9;
x=8; y=7;
++k;
可以一行放一个表达式形成表达式语句, 也可以一行放多个表达式形成表达式语句, 这时每个表达式后面都必须带“; ” 号, 另外, 还可以仅由—个分号“; ”占一行形成一个表达式语句, 这种语句称为空语句。
空语句在程序设计中通常用于两种情况:
①在程序中为有关语句提供标号, 用以标记程序执行的位置。 例如采用下面的语句可以构成一个循环。
repeat: ;
:
goto repeat;
②在用 while 语句构成的循环语句后面加一个分号, 形成一个不执行其它操作的空循环体。 这种结构通常用于对某位进行判断, 当不满足条件则等待, 满足
条件则执行。
(2) 复合语句
复合语句是由若干条语句组合而成的一种语句, 在 C51 中, 用一个大括号“{ }” 将若干条语句括在一起就形成了一个复合语句, 复合语句最后不需要以分号“; ” 结束, 但它内部的各条语句仍需以分号“; ” 结束。 复合语句的一般
形式为:
{
局部变量定义;
语句 l;
语句 2;
}
复合语句在执行时, 其中的各条单语句按顺序依次执行, 整个复合语句在语法上等价于一条单语句, 因此在 C51 中可以将复合语句视为一条单语句。 通常复合语句出现在函数中, 实际上, 函数的执行部分(即函数体) 就是一个复合语句;复合语句中的单语句一般是可执行语句, 此外还可以是变量的定义语句(说明变量的数据类型) 。 在复合语句内部语句所定义的变量, 称为该复合语句中的局部变量, 它仅在当前这个复合语句中有效。 利用复合语句将多条单语句组合在—起,以及在复合语句中进行局部变量定义是 C51 语言的一个重要特征。
2.5 C51 基本结构和相关语句
(1)C51 的基本结构
1, 顺序结构
顺序结构是最基本、 最简单的结构, 在这种结构中, 程序由低地址到高地址依次执行, 如图给出顺序结构流程图, 程序先执行 A 操作, 然后再执行 B 操作。
2, 选择结构
选择结构可使程序根据不同的情况, 选择执行不同的分支, 在选择结构中,程序先都对一个条件进行判断。 当条件成立, 即条件语句为“真” 时, 执行一个分支, 当条件不成立时, 即条件语句为“假” 时, 执行另一个分支。 如图: 当条件 S 成立时, 执行分支 A, 当条件 P 不成立时, 执行分支 B。
在C51中,实现选择结构的语句为if/else,if/else if语句。另外在C51中还支持多分支结构,多分支结构既可以通过if和else if语句嵌套实现,可用switch/case 语句实现。
3.循环结构
在程序处理过程中, 有时需要某一段程序重复执行多次, 这时就需要循环结构来实现, 循环结构就是能够使程序段重复执行的结构。 循环结构又分为两种:
当(while) 型循环结构和直到(do...while) 型循环结构。
①当型循环结构
当型循环结构如图: 当条件 P 成立(为“真” ) 时, 重复执行语句 A, 当条件不成立(为“假” ) 时才停止重复, 执行后面的程序。
②直到型循环结构
直到型循环结构, 先执行语句 A, 再判断条件 P, 当条件成立(为“真” )时, 再重复执行语句 A, 直到条件不成立(为“假” ) 时才停止重复, 执行后面的程序。 构成循环结构的语句主要有: while、 do while、 for、 goto。
(2) C51 的相关语句
1, if 语句
if 语句是 C51 中的一个基本条件选择语句, 它通常有三种格式:
①if (表达式) {语句; }
②if (表达式) {语句 1; } else {语句 2; }
③if (表达式 1) {语句 1; }
else if (表达式2) (语句2;)
else if (表达式3) (语句3;)
......
else if (表达式n-1) (语句n-1;)
else {语句n}
【例】 if 语句的用法。
(1) if (x!=y) printf(“x=%d,y=%d\n” ,x,y);
执行上面语句时, 如果 x 不等于 y, 则输出 x 的值和 y 的值。
(2) if (x>y) max=x;
else max=y;
执行上面语句时, 如 x 大于 y 成立, 则把 x 送给最大值变量 max, 如 x 大于y 不成立, 则把 y 送给最大值变量 max。 使 max 变量得到 x、 y 中的大数。
(3)if (score>=90) printf(“Your result is an A\n” );
else if (score>=80) printf(“Your result is an B\n” );
else if (score>=70) printf(“Your result is an C\n” );
else if (score>=60) printf(“Your result is an D\n” );
else printf(“Your result is an E\n” );
执行上面语句后, 能够根据分数 score 分别打出 A、 B、 C、 D、 E 五个等级。
2, switch/case 语句
if 语句通过嵌套可以实现多分支结构, 但结构复杂。 switch 是 C51 中提供的专门处理多分支结构的多分支选择语句。 它的格式如下:
switch (表达式)
{case 常量表达式 1: {语句 1; }break;
case 常量表达式 2: {语句 2; }break;
……
case 常量表达式 n: {语句 n; }break;
default: {语句 n+1; }
说明如下:
(1) switch 后面括号内的表达式, 可以是整型或字符型表达式。
(2) 当该表达式的值与某一“case” 后面的常量表达式的值相等时, 就执行该“case” 后面的语句, 然后遇到 break 语句退出 switch 语句。 若表达式的值与所有 case 后的常量表达式的值都不相同, 则执行 default 后面的语句, 然后退出 switch 结构。
(3) 每一个 case 常量表达式的值必须不同否则会出现自相矛盾的现象。
(4) case 语句和 default 语句的出现次序对执行过程没有影响。
(5) 每个 case 语句后面可以有“break” , 也可以没有。 有 break 语句,执行到 break 则退出 switch 结构, 若没有, 则会顺次执行后面的语句, 直到遇到 break 或结束。
(6) 每一个 case 语句后面可以带一个语句, 也可以带多个语句, 还可以不
带。 语句可以用花括号括起, 也可以不括。
(7) 多个 case 可以共用一组执行语句。
【例】 switch/case 语句的用法。
对学生成绩划分为 A~D, 对应不同的百分制分数, 要求根据不同的等级打印出它的对应百分数。 可以通过下面的 switch/case 语句实现。
……
switch(grade)
{
case ‘A’ ; printf(” 90~100\n” ) ; break;
case ‘B’ ; printf(” 80~90\n” ) ; break;
case ‘C’ ; printf(” 70~80\n” ) ; break;
case ‘D’ ; printf(” 60~70\n” ) ; break;
case ‘E’ ; printf(” <60\n” ) ; break;
default; printf(” error” \n)
}
3, while 语句
while 语句在 C51 中用于实现当型循环结构, 它的格式如下:
while(表达式)
{语句; } /*循环体*/
while 语句后面的表达式是能否循环的条件, 后面的语句是循环体。 当表达式为非 0(真) 时, 就重复执行循环体内的语句; 当表达式为 0(假),则中止while 循环, 程序将执行循环结构之外的下一条语句。 它的特点是: 先判断条件,后执行循环体。 在循环体中对条件进行改变, 然后再判断条件, 如条件成立, 则再执行循环体, 如条件不成立, 则退出循环。 如条件第一次就不成立, 则循环体一次也不执行。
4, do...while 语句
Do...while 语句在 C51 中用于实现直到型循环结构, 它的格式如下:
do
{语句; } /*循环体*/
while(表达式) ;
它的特点是: 先执行循环体中的语句, 后判断表达式。 如表达式成立(真),则再执行循环体, 然后又判断, 直到有表达式不成立(假) 时, 退出循环, 执行do while 结构的下一条语句。 do while 语句在执行时, 循环体内的语句至少会被执行一次。
5, for 语句
for(表达式 1; 表达式 2; 表达式 3)
{语句; } /*循环体*/
for 语句后面带三个表达式, 它的执行过程如下:
(1) 先求解表达式 1 的值。
(2) 求解表达式 2 的值, 如表达式 2 的值为真, 则执行循环休中的语句,
然后执行下一步(3) 的操作, 如表达式 2 的值为假, 则结束 for 循环, 转到最后一步。
(3) 若表达式 2 的值为真, 则执行完循环体中的语句后, 求解表达式 3, 然后转到第四步。
(4) 转到(2) 继续执行。
(5) 退出 for 循环, 执行下面的一条语句。
在 for 循环中, 一般表达式 1 为初值表达式, 用于给循环变量赋初值; 表达式 2 为条件表达式, 对循环变量进行判断; 表达式 3 为循环变量更新表达式, 用于对循环变量的值进行更新, 使循环变量能不满足条件而退出循环。
6, 循环的嵌套
在一个循环的循环体中允许又包含一个完整的循环结构, 这种结构称为循环的嵌套。 外面的循环称为外循环, 里面的循环称为内循环, 如果在内循环的循环体内又包含循环结构, 就构成了多重循环。 在 C51 中, 允许三种循环结构相互嵌套。
【例】 用嵌套结构构造一个延时程序。
void delay(unsigned int x)
{
unsigned char j;
while(x--)
{for (j=0;j<125;j++);}
}
这里, 用内循环构造一个基准的延时, 调用时通过参数设置外循环的次数,这样就可以形成各种延时关系。
7, break 和 continue 语句
break 和 continue 语句通常用于循环结构中, 用来跳出循环结构。 但是二者又有所不同, 下面分别介绍。
(1) break 语句
前面已介绍过用 break 语句可以跳出 switch 结构, 使程序继续执行 switch结构后面的一个语句。 使用 break 语句还可以从循环体中跳出循环, 提前结束循环而接着执行循环结构下面的语句。 它不能用在除了循环语句和 switch 语句之外的任何其它语句中。
【例】 下面一段程序用于计算圆的面积, 当计算到面积大于 100 时, 由 break语句跳出循环。
for (r=1; r<=10; r++)
{
area=pi*r*r;
if (area>100) break;
printf(“%f\n” , area);
}
(2) continue 语句
continue 语句用在循环结构中, 用于结束本次循环, 跳过循环体中 continue下面尚未执行的语句, 直接进行下一次是否执行循环的判定。
continue 语句和 break 语句的区别在于: continue 语句只是结束本次循环而不是终止整个循环; break 语句则是结束循环, 不再进行条件判断。
【例】 输出 100~200 间不能被 3 整除的数。
for (i=100; i<=200; i++)
{
if (i%3= =0) continue;
printf(“%d ” ; i);
}
在程序中, 当 i 能被 3 整除时, 执行 continue 语句, 结束本次循环, 跳过
printf 函数, 只有能被 3 整除时才执行 printf 函数。
8, return 语句
return 语句一般放在函数的最后位置, 用于终止函数的执行, 并控制程序返回调用该函数时所处的位置。 返回时还可以通过 return 语句带回返回值。 return语句格式有两种:
(1) return;
(2) return (表达式);
如果 return 语句后面带有表达式, 则要计算表达式的值, 并将表达式的值作为函数的返回值。 若不带表达式, 则函数返回时将返回一个不确定的值。 通常我们用 return 语句把调用函数取得的值返回给主调用函数。
2.6 函数
(1) 函数的定义
函数定义的一般格式如下:
函数类型 函数名(形式参数表) [reentrant][interrupt m][using n]
形式参数说明
{
局部变量定义
函数体
}
前面部件称为函数的首部, 后面称为函数的尾部, 格式说明:
1. 函数类型
函数类型说明了函数返回值的类型。
2. 函数名
函数名是用户为自定义函数取的名字以便调用函数时使用。
3. 形式参数表
形式参数表用于列录在主调函数与被调用函数之间进行数据传递的形式参数。
【例】 定义一个返回两个整数的最大值的函数 max()。
int max(int x,int y)
{
int z;
z=x>y?x: y;
return(z) ;
}
4. reentrant 修饰符
这个修饰符用于把函数定义为可重入函数。 所谓可重入函数就是允许被递归调用的函数。 函数的递归调用是指当一个函数正被调用尚未返回时, 又直接或间接调用函数本身。 一般的函数不能做到这样, 只有重入函数才允许递归调用。
关于重入函数, 注意以下几点:
(1) 用 reentrant 修饰的重入函数被调用时, 实参表内不允许使用 bit 类型的参数。 函数体内也不允许存在任何关于位变量的操作, 更不能返回 bit 类型的值。
(2) 编译时, 系统为重入函数在内部或外部存储器中建立一个模拟堆栈区,称为重入栈。 重入函数的局部变量及参数被放在重入栈中, 使重入函数可以实现递归调用。
(3) 在参数的传递上, 实际参数可以传递给间接调用的重入函数。 无重入属性的间接调用函数不能包含调用参数, 但是可以使用定义的全局变量来进行参数传递。
5. interrupt m 修饰符
interrupt m 是 C51 函数中非常重要的一个修饰符, 这是因为中断函数必须通过它进行修饰。 在 C51 程序设计中, 当函数定义时用了 interrupt m 修饰符,系统编译时把对应函数转化为中断函数, 自动加上程序头段和尾段, 并按 51 系统中断的处理方式自动把它安排在程序存储器中的相应位置。在该修饰符中, m 的取值为 0~31, 对应的中断情况如下:
0——外部中断 0
1——定时/计数器 T0
2——外部中断 1
3——定时/计数器 T1
4——串行口中断
5——定时/计数器 T2
其它值预留。
(2) 函数的调用与声明
1, 函数的调用
函数调用的一般形式如下:
函数名(实参列表) ;
对于有参数的函数调用, 若实参列表包含多个实参, 则各个实参之间用逗号隔开。
按照函数调用在主调函数中出现的位置, 函数调用方式有以下三种:
(1) 函数语句。 把被调用函数作为主调用函数的一个语句。
(2) 函数表达式。 函数被放在一个表达式中, 以一个运算对象的方式出现。
这时的被调用函数要求带有返回语句, 以返回一个明确的数值参加表达式的运算。
(3) 函数参数。 被调用函数作为另一个函数的参数。
2, 自定义函数的声明
在 C51 中, 函数原型一般形式如下:
[extern] 函数类型 函数名(形式参数表) ;
函数的声明是把函数的名字、 函数类型以及形参的类型、 个数和顺序通知编译系统, 以便调用函数时系统进行对照检查。 函数的声明后面要加分号。
如果声明的函数在文件内部, 则声明时不用 extern, 如果声明的函数不在文件内部, 而在另一个文件中, 声明时须带 extern, 指明使用的函数在另一个文件中。
2.7 C51 构造数据类型
(1) 数组
1, 一维数组
一维数组只有一个下标, 定义的形式如下:
数据类型说明符 数组名[常量表达式][={初值, 初值……}]
各部分说明如下:
(1) “数据类型说明符” 说明了数组中各个元素存储的数据的类型。
(2) “数组名” 是整个数组的标识符, 它的取名方法与变量的取名方法相同。
(3) “常量表达式” , 常量表达式要求取值要为整型常量, 必须用方括号“[]” 括起来。 用于说明该数组的长度, 即该数组元素的个数。
(4) “初值部分” 用于给数组元素赋初值, 这部分在数组定义时属于可选项。 对数组元素赋值, 可以在定义时赋值, 也可以定义之后赋值。 在定义时赋值,后面须带等号, 初值须用花括号括起来, 括号内的初值两两之间用逗号间隔, 可以对数组的全部元素赋值, 也可以只对部分元素赋值。 初值为 0 的元素可以只用逗号占位而不写初值 0。
例如: 下面是定义数组的两个例子。
unsigned char x[5];
unsigned int y[3]={1,2,3};
第一句定义了一个无符号字符数组, 数组名为 x, 数组中的元素个数为 5。
第二句定义了一个无符号整型数组, 数组名为 y, 数组中元素个数为 3, 定义的同时给数组中的三个元素赋初值, 赋初值分别为 1、 2、 3。
需要注意的是, C51 语言中数组的下标是从 0 开始的, 因此上面第一句定义的 5 个元素分别是: x[0]、 x[1]、 x[2]、 x[3]、 x[4]。 第二句定义的 3 个元素分别是: y[0]、 y[1]、 y[2]。 赋值情况为: y[0]=1; y[1]=2; y[2]=3。
C51 规定在引用数组时, 只能逐个引用数组中的各个元素, 而不能一次引用整个数组。 但如果是字符数组则可以一次引用整个数组。
2, 字符数组
用来存放字符数据的数组称为字符数组, 它是 C 语言中常用的一种数组。 字符数组中的每一个元素都用来存放一个字符, 也可用字符数组来存放字符串。 字符数组的定义下一般数组相同, 只是在定义时把数据类型定义为 char 型。
例如: char string1[10];
char string2[20];
上面定义了两个字符数组, 分别定义了 10 个元素和 20 个元素。
在 C51 语言中, 字符数组用于存放一组字符或字符串, 字符串以“\0” 作为结束符, 只存放一般字符的字符数组的赋值与使用和一般的数组完全相同。 对于存放字符串的字符数组。 既可以对字符数组的元素逐个进行访问, 也可以对整个数组按字符串的方式进行处理。
3 一个简单的单片机 C 程序
根据前面小节的介绍, 我们知道一个 C 程序必须有且只有一个 main 函数,程序的执行都是从该函数开始。 我们使用 C 语言来开发 51 单片机程序就会涉及到单片机内部寄存器的操作, 这些在 KEIL 开发环境中都已经做好了, 我们只需要将“reg51.h” 或“reg52.h” 头文件包括进来即可。 下面我们就列举一个简单的单片机 C 程序, 如下:
#include “reg52.h”
void main()
{
}