本节书摘来自华章出版社《迷人的8051单片机》一书中的第3章,第3.1节,作者高显生,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
第3章
入门C语言
单片机是一种可编程的器件,我们需要将程序预先编写好,并保存到单片机的存储器中,单片机才能按照预先的设定执行程序。在给单片机开发应用程序时,使用C语言编写代码已经是一种趋势,C语言博大精深,学精不易,但入门却十分简单,本章将带领你用最便捷的方式快速学习C语言,并且在短时间内学会编写C应用程序。
3.1 数据和运算
3.1.1 C语言的由来
语言是编写程序时人与单片机之间的交流方式,最初人们使用机器码(0与1组合)来给单片机编写程序,后来开始使用汇编语言来编写程序,汇编语言和单片机的硬件结合性好,代码简洁高效,使用汇编语言开发的程序在单片机存储空间有限的环境里能大显身手。随着近年来FLASH存储器技术被大量地应用,单片机的存储空间已经不再是瓶颈,使用汇编语言开发程序的优势已经不复存在,而C语言因其具有描述能力强、可移植性好、逻辑缜密、模块化结构等诸多优点,非常适合大型程序的开发,近年来在嵌入式系统的程序开发中被越来越广泛地应用。
C语言有着悠久的历史和众多用户群。1970年,美国贝尔实验室的 Ken Thompson,以BCPL语言为基础,设计出既简单又接近硬件的B语言,并且用B语言编写了第一个UNIX操作系统。1972年,美国贝尔实验室的 D.M.Ritchie 在B语言的基础上设计出一种新的语言,并且以BCPL语言的第二个字母作为这种语言的名字,即C语言。1977年,D.M.Ritchie发表了不依赖于具体机器系统的C语言编译文本——《可移植的C语言编译程序》,使C语言程序可以使用在任意架构的处理器上,只要该处理器具有对应的C语言编译器和库,然后将C源代码编译、链接成目标二进制文件之后即可在目标处理器上运行。
1982年,很多有识之士和美国国家标准学会(ANSI)为了使C语言健康地发展下去,成立了C标准委员会,建立了C语言的标准。1989年,ANSI发布了第一个完整的C语言标准ANSI X3.159#1989,简称“C89”,也就是我们经常说的“ANSI C”,C语言从此步入了规范化的道路。
3.1.2 数的进制
我们在日常生活中大多使用十进制,即逢十进一,这主要是由人们的使用习惯决定的。其实在生活中还有许多不同的进位制度,如时间的表示方法是六十进制,即一小时等于六十分钟,一分钟等于六十秒等,还有常用的表示数量的单位“一打”是十二进制等。在计算机中,常用的进位制度有二进制、十进制、八进制和十六进制。
1. 二进制(Binary)
二进制数由0和1两个符号来表示,基数为2,按逢2进1、借1算2的规则计数。
例如:
10110011 1010 1111000010101101
2. 十进制(Decimal)
十进制数由0、1、2、3、4、5、6、7、8、9十个数字符号表示,基数为10,按逢10进1、借1算10的规则计数。例如:
128 23 47
3. 八进制(Octal)
八进制数由0、1、2、3、4、5、6、7八个数字符号表示,基数为8,按逢8进1、借1算8的规则计数。例如:
70 360 777
4. 十六进制(Hexadecimal)
十六进制数由0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F十六个数字符号表示,基数为16,按逢16进1、借1算16的规则计数。在C语言中表示十六进制数时,大小写字母的含义相同。例如:
EF08 5ac 7BF
对于不同进制的数字间的转换在这里不做太多的叙述,对于初学者来讲,使用PC中的计算器来进行不同进制数字间的转换是一个方便快捷的办法,计算器的使用如图3-1所示。打开计算器软件,如要将十进制数“254”转换成二进制数,可以首先在计算器中选择十进制,输入数字“254”,再用鼠标单击选择二进制,这时计算器中即可显示经转换后的二进制数“11111110”。
3.1.3 码制
在计算机内部,所有的信息都要使用二进制的方法来表示,因为二进制的0和1两个数字恰好与存储单元的“有”和“无”相对应。不仅如此,数的符号“+”或“-”也需要用二进制数来表示,在通常情况下,用0表示正数的符号“+”,用1表示负数的符号“-”。当数的符号和数值表示方法使用二进制时,这样的数被称为“机器码”。机器码有不同的码制,对应不同的表示方法,常用的码制有原码、反码和补码三种。
1)原码:原码用最高位表示数的符号位,数值部分用二进制的绝对值表示。
2)反码:正数的反码与其原码相同,负数的反码是将符号位除外,其他各位按位取反。
3)补码:正数的补码与其原码相同,负数的补码是其反码加1。
数的原码、反码和补码的表示方法详见表3-1。
3.1.4 数据类型
程序运行的目的是对数据进行处理,在C语言中数据是有类型区分的,具体分类如下:
在以上的数据类型分类中,基本类型是不可以再次拆分为其他数据类型的;构造类型则是在基本类型的基础上,按照一定方式组合而成的数据类型;指针类型是一种特殊的数据类型,其值通常用来表示某一个量在内存中的存放地址;空类型是指在对函数进行定义时,若函数没有返回值,我们会在函数的名称前面加上“void”,以此表明该函数是“空类型”。
3.1.5 常量
常量是指在程序的运行过程中,其值不能被改变的量。常量的种类有整型、实型、字符型和字符串常量四种。
1)整型常量:十进制的整数表示方法非常简单,如29、-18、156等。十六进制的整数通常以0x(或0X)开头,如0xFE、0xD7A9、0x7D等。八进制的整数则以0开头,如057,其值相当于十进制的47。
2)实型常量:实数有两种表示方法,一种是十进制的小数形式,如0.625、-16.5等;另一种是采用指数形式,即用e(或E)后面跟一个整数,表示以10为底的幂指数,如256.5的表示方法是2.565e2。
3)字符型常量:字符型常量的表示方法是用单引号引出,如‘a’、‘B’等。
4)字符串常量:字符串常量用双引号引出,如“GOOD”“thank you”等。
3.1.6 变量
变量是指在程序运行过程中其值可以改变的量。在C语言中使用变量时,要先给变量命名,还要给变量定义数据类型,有时还须指定变量的存储地点。
为什么要给变量定义数据类型呢?在数学上,一个数可以是+∞也可以是-∞,但是在计算机中,存储单元是有限的,因此必须根据数据的大小为其分配合适的存储空间。定义数据类型实际上就是为变量在内存中分配特定的存储空间,以便于用这个空间来存储相关的数据。如果将变量比喻成用于存储数据的盒子,指定数据类型就是指定盒子的大小,既要装下要装的东西,又不会造成空间的浪费。C语言中变量的数据类型详见表3-2。
变量在程序中需要先定义后使用。定义变量的方法是先给变量指定名称和数据类型,这样编译器才能为变量分配相应的存储空间。定义变量的方式如下:数据类型 变量名表;(多个变量名称之间要用逗号分隔)
在C程序中定义变量的方法可以参考如下语句:
unsigned char a,b,c; //定义a,b,c三个无符号字符型变量
unsigned int num; //定义num 无符号整型变量
3.1.7 运算符
C语言的运算符非常丰富,在程序中使用这些运算符来处理各种基础操作,从而完成特定的功能。C语言的运算符主要有以下几种:
1. 算术运算符
+ :加法运算符,或为取正值运算符。例如,3+5、A+B、+23。
– :减法运算符,或为取负值运算符。例如,18-17、TIME1-TIME2、-78。
* :乘法运算符。例如,5*8、AD*AF。
/ :除法运算符。在这里除法运算符和一般的算术运算规则有所不同,如果是两个浮点数相除,结果也是浮点数。如果两个整数相除,结果也是整数。例如,10.0/20.0 结果为0.5,7/2结果为3,而不是3.5。
% :求余运算符。%两侧均应是整数。例如,10%3结果为1。
在上述的运算符中,我们同样可以用“()”来改变运算的优先级,这同我们在小学时学的是一样的,如(A+B)* C 就需要先计算A与B的和,再计算与C的积。
2. 赋值运算符
= :赋值运算符。在C语言中用于给变量赋值,其方法可以参考以下语句:
num=25; //给变量num赋值25
D=C; //将变量C的值赋给变量D
3. 自增、自减运算符
++ :自增运算符。作用是使变量的值自增1。例如,I++,表示让变量I的值自增1。
- - :自减运算符。作用是使变量的值自减1。例如,A--,表示让变量A的值自减1。
4. 关系运算符
关系运算符通常是用来判别两个变量是否符合某个条件的,所以使用关系运算符的运算结果只有“真”或“假”,即“1”或“0”两种。
> :大于。例如,A>B。
< :小于。例如,NUM1
>= :大于等于。例如,U>=5。
<= :小于等于。例如,P<=7。
= = :等于。例如,TEAM1= =TEAM2 ,在这里要区别于赋值运算符“=”,它表示的意思不是将TEAM2的值赋给TEAM1,而是用来判定TEAM1是不是同TEAM2的值相等。
!= :不等于。例如,A!=B。
5. 位运算符
位运算是C语言的一大特色。所谓位运算形象地说就是指将数值以二进制位的方式进行相关的运算,参与位运算的数必须是整型或字符型的数据,实型(浮点型)的数不能参与位运算。
& :按位“与”运算符。它是实现“必须都有,否则就没有”的运算。它的规则如下。
0 & 0 = 0, 0 & 1 = 0, 1 & 0 = 0, 1 & 1 = 1
在实际应用中,按位“与”运算常用来对某些位清零或保留某些位。
例如,A的值为: A=1001 0010
只想保留A的高四位,则用: A & 1111 0000
“与”运算后A的值为: 1001 0000
| :按位“或”运算符。它是实现“只要其中之一有,就有”的运算。它的规则如下。
0 | 0 = 0, 1 | 0 = 1, 0 | 1 = 1, 1 | 1 = 1
在实际应用中,“或”运算常用来将一个数值的某些位定值为“1”。
例如,A的值为: A=1001 0010
想将A的低四位定值为1, 则用: A|0000 1111
“或”运算后A的值为: 1001 1111
^ :按位“异或”运算符。它是实现“两个不同就有,相同就没有”的运算。它的规则如下。
0 ^ 0 = 0, 1 ^ 0 = 1, 0 ^ 1 = 1, 1 ^ 1 = 0
在实际应用中,“异或”运算常用来使数值的特定位翻转。
例如,A的值为: A=1001 1010
想将A的低四位翻转,即0变1,1变0,则用: A ^ 0000 1111
“异或”运算后A的值为: 1001 0101
~ :按位“取反”运算符。它是实现“是非颠倒”的运算。它的运算规则如下。
~ 0 = 1, ~ 1 = 0
例如,A的值为: 1001 1010
按位“取反”运算后,其值为: 0110 0101
<< :“左移”运算符。它是实现将一个二进制数的每一位都左移若干位的运算。“左移”运算的方法如图3-2所示。
>> :“右移”运算符。它是实现将一个二进制数的每一位都右移若干位的运算。“右移”运算的方法如图3-3所示。
3.1.8 复合赋值运算符
在赋值运算符“=”之前加上其他双目运算符,就可以构成复合赋值运算符。复合赋值运算符有+=、-=、*=、/=、%=、<<=、>>=、&=、^= 和 |=。
构成复合赋值表达式的方式为:
变量 双目运算符 = 表达式
它相当于:
变量 = 变量 运算符 表达式
例如:
num+=15 相当于:num=num+15
a*=b+23 相当于:a=a*(b+23)
对于初学者来说,复合赋值运算符的这种书写方法也许不太习惯,但它有利于编译器的编译和处理,可以产生高质量的目标代码。