1.数据类型
单片机的存储器十分有限,因此在单片机上编程时需要高效地利用资源,这就要求对各种数据类型有深刻的理解。各种数据类型的含义与长度如表3.1.1所示。
表3.1.1 各种数据类型的含义与长度
续表
2.运算符号与控制流程
C语言常用的运算符号主要包括以下部分。
⊙算术运算:+ - * / ^ %
⊙逻辑运算:|| && !
⊙关系运算:> < >= <= == !=
⊙位运算:~ << >> & ^ |
⊙增量和减量运算:++ --
⊙复合赋值运算:+= -= *= /= %=
⊙指针和地址运算:* &
⊙强制类型转换:(类型)变量
其中,位操作在嵌入式编程中极为重要,对寄存器的操作离不开位操作,本节将详细介绍此部分,其他知识较为基础,在此不做赘述。
程序的控制流程由一系列控制语句完成(C语言共9种语句)。
⊙if ()…else… (条件语句)
⊙for() (循环语句)
⊙while() (循环语句)
⊙do while() (循环语句)
⊙continue (结束本次循环语句)
⊙break (终止switch或循环语句)
⊙switch (分支选择语句)
⊙return (函数返回语句)
⊙goto (跳转语句)
通过循环、判断、分支选择、调用函数、跳转等实现一定的控制功能。由于控制语句比较基础,在此不做赘述。
参与运算的量按二进制位进行运算。C语言提供了以下6种位运算符。
& 按位与
| 按位或
^ 按位异或
~ 求反
<< 左移
>> 右移
1)按位与(&)运算
按位与运算符“&”是双目运算符,其功能是参与运算的两数各对应的二进制位相与。只有对应的两个二进制位均为1时,结果位才为1 ,否则为0。参与运算的数均以补码方式出现。
例如,9&5可写成算式:00001001(9的二进制补码)& 00000101(5的二进制补码)为00000001(1的二进制补码),即9&5=1。
按位与运算通常用来对某些位清零或保留某些位。例如,把 a 的高8位清零,保留低8位,可进行a&255运算(255的二进制数为0000000011111111)。
2)按位或(|)运算
按位或运算符“|”是双目运算符,其功能是参与运算的两数各对应的二进制位相或。只要对应的两个二进制位有一个为1时,其结果位就为1。参与运算的两个数均以补码方式出现。
例如,9|5可写成算式:00001001 | 00000101为00001101(十进制数为13),得9|5=13。
3)按位异或(^)运算
按位异或运算符“^”是双目运算符,其功能是参与运算的两数各对应的二进制位相异或。当两个对应的二进制位相异时,结果为1。参与运算的数仍以补码形式出现。
例如,9^5可写成算式:00001001 ^ 00000101 为00001100(十进制数为12),即9^5=12。
4)求反(~)运算
求反运算符“~”为单目运算符,具有右结合性,其功能是对参与运算数的各二进制位按位求反。
例如,~9的运算为:~(0000000000001001)为1111111111110110。
5)左移(<<)运算
左移运算符“<<”是双目运算符,其功能是把“<< ”左边的运算数的各二进制位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补0。
例如, a <<4指把 a 的各二进制位向左移动4位,如 a =00000011(十进制数为3),左移4位后为00110000(十进制数为48)。
6)右移运算
右移运算符“>>”是双目运算符,其功能是把“>>”左边的运算数的各二进制位全部右移若干位,“>>”右边的数指定移动的位数。
例如,设 a =15, a >>2表示把000001111右移为00000011(十进制数为3)。应该说明的是,对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位补0,而为负数时,符号位补1,最高位补0或补1取决于编译系统的规定。Turbo C和很多系统规定为补1。
3.指针的应用
C语言的高效性、灵活性、可移植性是所有嵌入式编程人员所共识的,其中灵活性是学习者体会最深的一点。C语言指针将C语言的灵活性体现得淋漓尽致。运用指针可以提高程序的运行速度,降低程序的存储空间,也可以有效地表示和实现复杂的数据结构。
1)定义一个指针变量
指针变量其一般形式为:
类型说明符 *变量名
其中,* 表示这是一个指针变量,变量名即为定义的指针变量名;类型说明符表示本指针变量所指向变量的数据类型。
例如,int *p1; 表示 p 1是一个指针变量。它的值是某个整型变量的地址,或者说 p 1指向一个整型变量。至于 p 1究竟指向哪一个整型变量,应由向 p 1赋予的地址来决定。
再如:
应该注意的是,一个指针变量只能指向同类型的变量。例如, p 3只能指向浮点变量,不能时而指向一个浮点变量,时而又指向一个字符变量。
对指针变量赋值规则如下。
指针变量只能赋予地址,不能赋予任何其他数据,否则将引起错误。在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。设有指向整型变量的指针变量 p ,如要把整型变量 a 的地址赋予 p 可以有以下两种方式。
(1)指针变量初始化的方法:
(2)赋值语句的方法:
不允许把一个数赋予指针变量,故下面的赋值是错误的:
被赋值的指针变量前不能再加“*”说明符,如写为*p=&a 也是错误的。假设:
定义两个整型变量 i 、 x ,再定义一个指向整型数的指针变量ip。 i 和 x 中可存放整数,而ip中只能存放整型变量的地址。
可以把 i 的地址赋给ip:ip=&i;
2)通过指针引用数组
一个变量有地址,一个数组包含多个元素,每个数组元素都在内存中占有存储单元,它们都有相应的地址。与指针变量指向变量一样,指针变量也可以指向数组元素(把某一个元素的地址放到一个指针变量中)。数组元素的指针就是数组元素的地址。
可以用一个指针变量指向一个数组元素。例如,
在C语言中,数组名代表数组中首元素(序号为0的元素)的地址,因此下面两个语句等价:
注意:数组名不代表数组中的所有元素,只代表数组首元素的地址。上述“p=a;”的作用是“把 a 数组首元素的地址赋给指针变量 p ”,而不是“把数组 a 各元素的值赋给 p ”。
C语言规定:如果指针变量 p 已指向数组中的一个元素,则 p +1指向同一数组中的下一个元素。
引入指针变量后,就可以用第二种方法来访问数组元素了。
如果 p 的初值为&a[0],则 p + i 和 a + i 就是a[i]的地址,或者说它们指向 a 数组的第 i 个元素。*(p+i)或*(a+i)就是 p + i 或 a + i 所指向的数组元素,即a[i]。例如,*(p+5)或*(a+5)就是a[5]。
指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价。
4.结构体
结构体就是一个可以包含不同数据类型的结构,是一种可以自己定义的数据类型。它的特点和数组主要有两点不同。首先,结构体可以在一个结构中声明不同的数据类型;其次,相同结构的结构体变量是可以相互赋值的。而数组是做不到的,因为数组是单一数据类型的数据集合,它本身不是数据类型(而结构体是),数组名称是常量指针,所以不可以作为左值进行运算,所以数组之间不能通过数组名称相互复制,即使数据类型和数组大小完全相同。
结构体有其显著优势,能够节省内存空间,极大地提高代码效率。对于智能汽车来说,存储资源相对有限,合理运用结构体可以提升代码空间。
定义一个结构体的一般形式为:
成员表列由若干个成员组成,每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:类型说明符 成员名。
成员名的命名应符合标识符的书写规定。例如,
在这个结构体中,结构名为stu,该结构由4个成员组成:第1个成员为num,整型变量;第2个成员为name,字符数组;第3个成员为sex,字符变量;第4个成员为score,实型变量,并对各成员进行了初始化。将101,“Li ping”,‘M’,45按顺序分别赋给了stu结构体变量中的成员num, name数组,sex, score。
应注意在括号后的分号是不可少的。结构定义之后,即可进行变量说明。凡说明为结构stu的变量都由上述4个成员组成。由此可见,结构体是一种复杂的数据类型,是数目固定、类型不同的若干有序变量的集合。
定义了一个结构数组boy,共有5个元素,boy[0]~boy[4]。每个数组元素都具有struct stu的结构形式。对结构数组可以作初始化赋值。
例如,
当对全部元素作初始化赋值时,也可不给出数组长度。
5.位域
位域是指信息在存储时并不需要占用一个完整的字节,而只需要占几个或一个二进制位。例如,在存放一个开关量时,只有0和1两种状态,用一位二进制位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进制位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
与结构体定义相仿,其形式为:
其中,位域列表的形式为:类型说明符 位域名:位域长度。
此结构体里嵌套共用体, R 、 B 为共用体里的两个变量,占用相同的内存单元。data为bs变量,共占两个字节。其中位域 a 占8位,位域 b 占2位,位域 c 占6位。
如果给此内存单元整体赋值:register.R=data;
如果给此内存单元高8位赋值:register.B.a=data;
6.编译预处理
1)宏定义
解析:用一个指定的标志符来代表一个字符串。
2)条件编译
条件编译用高速编译器,某些代码不用编译,经常使用它来避免重复编译。
条件编译一般用于头文件中,以上是智能汽车工程的总头文件。
1.命名基本原则
(1)命名清晰明了,有明确含义,使用完整单词或约定俗成的缩写。通常,较短的单词可通过去掉元音字母形成缩写;较长的单词可取单词的头几个字母形成缩写,即“见名知意”。
(2)命名风格要自始至终保持一致。
(3)命名中若使用特殊约定或缩写,要有注释说明。
(4)为了代码复用,命名中应避免使用与具体项目相关的前缀。
(5)应使用英语命名。
2.预定义
只使用大写字母、下划线和数字。
例如,#define MAX_LENGTH 1
3.宏和常量命名
只使用大写字母、下划线和数字。宏和常量全部用大写字母来命名,词与词之间用下划线分隔。程序中用到的数字均应用有意义的枚举或宏来代替。
4.变量命名
变量命名规则: <<范围>_类型>名称。
变量命名介绍如表3.1.2所示,变量类型介绍如表3.1.3所示。
表3.1.2 变量命名介绍
表3.1.3 变量类型介绍
例如:
局部循环体控制变量优先使用 i 、 j 、 k 等;局部长度变量优先使用len、num等;临时中间变量优先使用temp、tmp等。
5.结构和类型定义
按Camel-Style方式命名,如ThisIsAnExampleStructOrEnumOrTyedef,避免使用下划线。
6.枚举
7.函数命名
按Camel-Style方式命名,如ThisIsAnExampleMethod。
8.文件命名
一个文件包含一类功能或一个模块的所有函数,文件名称应清楚表明其功能或性质。每个.c文件应该有一个同名的.h文件作为头文件。
1.注释基本原则
注释有助于对程序的阅读理解,说明程序在“做什么”,解释代码的目的、功能和所采用的方法。一般情况下源程序有效注释量在30%左右。注释语言必须准确、易懂、简洁。边写代码边注释,修改代码的同时修改相应的注释,不再有用的注释要删除。汇编语言和C语言中都用“//”,取消“;”不使用段的注释用“/* */”(调试时可用)。
2.文件注释
文件注释必须说明文件名、项目名称、函数功能、创建人、创建日期、版本等相关信息。修改文件代码时,应在文件注释中记录修改日期、修改人员,并简要说明此次修改的目的。所有修改记录必须保持完整。文件注释放在文件顶端,用“/*……*/”格式包含。注释文本每行缩进4个空格;每个注释文本的分项名称应对齐。
/***********************************************************
文件名称:
项目名称:
平台信息:
作 者:
版 本:
说 明:
修改记录:
***********************************************************/
3.函数注释
函数头部注释应包括函数名称、函数功能、入口参数、出口参数等内容,如有必要还可增加作者、创建日期、修改记录(备注)等相关项目。函数头部注释放在每个函数的顶端,用“/*……*/”格式包含。其中,函数名称应简写为FunctionName(),不加入口/出口参数等信息。
/***********************************************************
函数名称:
函数功能:
入口参数:
出口参数:
备 注:
***********************************************************/
4.代码注释
代码注释应与被注释的代码紧邻,放在其上方或右方,不可放在下面,如放于上方则须与其上面的代码用空行隔开。一般少量注释应该添加在被注释语句的行尾,一个函数内的多个注释左对齐;较多注释则应加在上方且注释行与被注释的语句左对齐。通常,分支语句(条件分支、循环语句等)必须编写注释。其程序块结束行“}”的右方应加表明该程序块结束的标记“end of …”,尤其在多重嵌套时。
5.变量、常量、宏的注释
同一类型的标识符应集中定义,并在定义的前一行对其共性加以统一注释。对单个标识符的注释应加在定义语句的行尾。全局变量一定要有详细的注释,包括其功能、取值范围、哪些函数或过程存取它,以及存取时的注意事项等。注释用“//…//”的格式。
见上节预编译处理内容,在此不再赘述。
(1)代码的每一级均往右缩进4个空格。
(2)不使用Tab键。
(3)相对独立的程序块之间要加空行。
(4)括号内侧(左括号后面和右括号前面)不加空格,多重括号间不加空格。
例如,SetName(GetFunc());
(5)函数形参之间应该有且只有一个空格(形参逗号后面加空格)。
例如,CallFunction(para1, para2, para3),而CallFunction(para1,para2,para3)不符合要求。
(6)操作符前后均加一个空格,如nSum = nNunm1 + nNum2,而nSum=nNunm1+ nNum2不符合要求。
(7)单目操作符,如“!”“~”“++”“-”“&”(地址运算符)等,后面不加空格,如i++,pName = &name,bRes =!(x < 10)。
(8)if、else if、else、for、while语句无论其执行体是一条语句还是多条语句都必须加花括号,且左右花括号各独占一行。
(9)if、else if、else、for、while中的条件判断,操作符只可能是 = =、!=、&&、||、<、>、<=、>=。例如,
绝对不允许写成:
(10)Switch 语句必须包含Default 分支。例如,
(11)一个函数不要超过80行代码。