购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.1 C语言核心内容与芯片编程规范

3.1.1 C语言核心内容

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)条件编译

条件编译用高速编译器,某些代码不用编译,经常使用它来避免重复编译。

条件编译一般用于头文件中,以上是智能汽车工程的总头文件。

3.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文件作为头文件。

3.1.3 注释

1.注释基本原则

注释有助于对程序的阅读理解,说明程序在“做什么”,解释代码的目的、功能和所采用的方法。一般情况下源程序有效注释量在30%左右。注释语言必须准确、易懂、简洁。边写代码边注释,修改代码的同时修改相应的注释,不再有用的注释要删除。汇编语言和C语言中都用“//”,取消“;”不使用段的注释用“/* */”(调试时可用)。

2.文件注释

文件注释必须说明文件名、项目名称、函数功能、创建人、创建日期、版本等相关信息。修改文件代码时,应在文件注释中记录修改日期、修改人员,并简要说明此次修改的目的。所有修改记录必须保持完整。文件注释放在文件顶端,用“/*……*/”格式包含。注释文本每行缩进4个空格;每个注释文本的分项名称应对齐。

/***********************************************************

文件名称:

项目名称:

平台信息:

作 者:

版 本:

说 明:

修改记录:

***********************************************************/

3.函数注释

函数头部注释应包括函数名称、函数功能、入口参数、出口参数等内容,如有必要还可增加作者、创建日期、修改记录(备注)等相关项目。函数头部注释放在每个函数的顶端,用“/*……*/”格式包含。其中,函数名称应简写为FunctionName(),不加入口/出口参数等信息。

/***********************************************************

函数名称:

函数功能:

入口参数:

出口参数:

备 注:

***********************************************************/

4.代码注释

代码注释应与被注释的代码紧邻,放在其上方或右方,不可放在下面,如放于上方则须与其上面的代码用空行隔开。一般少量注释应该添加在被注释语句的行尾,一个函数内的多个注释左对齐;较多注释则应加在上方且注释行与被注释的语句左对齐。通常,分支语句(条件分支、循环语句等)必须编写注释。其程序块结束行“}”的右方应加表明该程序块结束的标记“end of …”,尤其在多重嵌套时。

5.变量、常量、宏的注释

同一类型的标识符应集中定义,并在定义的前一行对其共性加以统一注释。对单个标识符的注释应加在定义语句的行尾。全局变量一定要有详细的注释,包括其功能、取值范围、哪些函数或过程存取它,以及存取时的注意事项等。注释用“//…//”的格式。

3.1.4 统一类型别名定义

见上节预编译处理内容,在此不再赘述。

3.1.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行代码。 5HhXw+3JBMJUnL3EYL7GN7smrgcQ2IlkJYjSOB3SrquKO3KqPBskz2er9z70Y4+/

点击中间区域
呼出菜单
上一章
目录
下一章
×