总结一下我我的的编程风格及这样作的缘由吧,实际上是为了给实验室写一个统一的C语言编程规范才写的。首先声明,我下面提到的编程规范,是本身给本身定的,不是c语言里面规定的。程序员
一件事情,作成和作好中间可能隔了十万八千里。编程
一样的,代码的质量也极大程度上反映了编程者的水平高低。为了让你们从学习的开始就养成良好的编程习惯,创做出优质的代码,实验室编辑这个文档,做为你们编程的参考,同时也是对之后编程风格的硬性规定。数组
对于一个团队来说,制定统一的编程规范,好处是显而易见的。一般一个项目是由多个成员共同完成,在项目中,常常互相调用组内成员的代码。若是两我的的编程习惯和风格差别显著,那么将会浪费大量时间在读懂代码上。相反,一致而良好的编程规范,会让合做开发变得轻松而高效。异步
众所周知,C语言是面向过程的语言。也就是说,程序员要对程序的每一步有精准的把握,知道每一条程序语句的执行内容及其结果。于是,代码的可读性就显得尤其重要。这里的可读,不只仅是对本身可读,也要对其余人可读。一段只有本身能读懂的代码,能够说价值很低,并且这样的代码随着时间的推移每每本身也读不懂。而可读性强的代码,不只方便移植与交流,更给调试带来了难以估量的便利。函数
读一段好的代码,会有一种读英语文章的流畅感。尽管C语言提供了有限的32个关键字,可是变量、函数等的命名却提供了较大的自由,这也是咱们将代码语句化的基础。试想,若是一段代码有了主谓宾结构,即便不懂编程的人,也能明白代码的功能。而这正是咱们代码编辑者追求的目标。学习
因此,写好一段代码,从把你的代码读者当编程小白开始!测试
1、文件管理spa
每个作技术的人,不管软硬件,计算机里都应该有一个纯英文的盘符,注意我是说英文,而不是pinyin。在这个纯英文的盘符里,固然是存放各类技术相关的软件、程序以及文档。而这些内容的命名也应该是英文的,包括各个子文件夹。其余诸如即时通信软件、游戏文件等应该放在其余盘符内。一方面,这样是对本身英文水平的锻炼;另外一方面,也能避免不少在使用国外软件的时候出现的各类BUG。指针
每个软件,都应该放在一个独立的文件夹中。这样既方便查找,又避免混乱。由于咱们都知道每个软件完成后,都不只仅是一个exe文件那么简单,一般还有各类后缀的文件,而这些咱们都不能删除。若是打开D盘时,映入眼帘的是几万个由不一样软件安装时生成的各类文件,相信给谁都会一脸大写的懵逼。所以,将不一样的软件放在单独的文件夹下是很是有必要的。调试
不一样IDE下编写的程序,也都应该存放在独立的一个文件夹下。文件夹内,不一样的工程也应该分别创建文件夹,并合理而精准命名。这样为往后的查找带来极大的便利。
不少IDE在编写程序文件时,除了要创建Project(工程),还要创建Workspace,即工做空间。工做空间一般是指定一个空间(也就是文件夹),IDE启动时,自动打开该空间下的各个Project。所以,一个Workspace能够存放多个Project。这样咱们就能够利用Workspace管理本身在该IDE下编写的各个Project。前提是你创建了Workspace,而Project存放在这个Workspace下。
每个独立的项目都应该是一个独立的Project。例如,分别练习编写流水灯和数码管的程序时,要分别创建Project,而不能放在一个Project下,除非你的项目同时用到了流水灯和数码管。这样作的好处是你能够Project名称上精确得到其内容信息,而不会出现程序写完过一段时间后无从查找的状况。
2、命名规则
首先说一下总的命名规则:命名必定要用英文。并非由于拼音不能够,而是由于咱们要与国际接轨,要养成良好的英文书写习惯。其次,命名中除了“\/:*?”<>|“等系统不容许的字符外,也不能出现除英文字母、下划线、数字外的其余字符。若是你想命名成flash LED.c,中间的空格要用下划线”_“来代替,写成flash_LED.c。另外,命名中能够出现必要的数字。
一、文件/文件夹命名
文件命名要精确,文件名要准确反映文件内容。写的是
文件命名一概使用小写字母,如keyboard.c。
若有缩写单词,则必须大写,如flash_LED.c、UART.c。其中LED是Light-Emitting Diode(发光二极管)的缩写,UART是Universal Asynchronous Receiver/Transmitter(通用异步收发器,也就是串口)的缩写。对于有约定俗成缩写的单词,就使用缩写词汇。
文件名应使用名词,而不该该使用动词。若是文件内容是数据采集,应该命名为data_collection.c而非data_collect.c。
二、标识符命名
C语言中,能够定义各类标识符做为变量名、数组名、函数名、标号及用户定义对象的名称。ANSI C规定标识符必须由字母和下划线开始,随后能够出现字母、下划线和数字。
1)变量命名
变量命名一概小写,缩写词汇用大写,且所有使用名词,可使用形容词修饰,用“_”表从属关系。由于变量名做为一个变量的名字,就应该是一个名词。
局部循环体控制变量用i,j,k。如for(i=0;i<100;i++)。
指针变量用“p_”开头,后面接指向内容。如指向高度变量的指针,命名为“*p_height”。请读者自行区分指针和指针变量的区别。
局部变量尽可能用一个单词表达清其含义。
全局变量命名时首先写所属模块名称。例如如一个传感器文件sensor.c里面的一个全局变量要表明温度,则命名为sensor_temperature。又例如LCD(液晶显示屏)文件LCD.c中表示LCD状态的全局变量命名为LCD_status。由于全局变量每每跨文件调用,如不写清变量定义位置,当程序庞大,而IDE又不支持一键定位时,查找起来很麻烦。即便IDE支持一键定位,一个清楚明白的命名,能让人瞬间读懂该变量的含义。
2)数组命名
数组命名各单词首字母大写,其余同变量。
读者可能会有疑问,数组名后面会有[]符号,与变量区别明显,为何要用首字母大写的方式。实际上,在数组名做为实参传递数组首地址时,每每会省略[]符号,应该数组名就是数组的首地址。例如:
unsigned char string[]=”abcdefg”;
printf(“%s”,string);
在以上代码中,string是一个8位数组(为何是8位?),在使用printf()函数输出时,只写了数组名,显然这种方式是被容许的。而此时就没有写[],在这种状况下,并不能瞬间知道string是变量仍是数组,而须要参考前面的格式控制符“%s”。在其余函数中,或许没有“%s”这样的格式控制符帮助咱们判断string究竟是数组仍是变量,咱们只有找到函数的声明或定义才能知道答案,严重影响阅读。所以有必要对数组和变量加以区分。
3)函数命名
函数命名各单词首字母大写,写成主谓语形式,主语用名词,谓语用动词,缩写词汇用大写,用“_”表从属关系。主语一般为模块名,而谓语是描述模块的动做。由于函数自己就是用来执行一系列的动做的, 结合函数参数,能够表达通顺的语句。举个简单的例子:延时函数。定义一个ms级延时函数为:
void Delay(unsigned int ms);(这个实际上是声明,函数体不想写了)
调用时写:
Delay(500);
很显然是延时了500ms。而若是再用个宏定义:
#define MS500 500
Delay(MS500);
是否是更一目了然呢?
另外还好比串口发送函数命名UART_TX( ),调用时写成:
UART_TX(time); (一般发送数据Transmit Data简写为TXD)
显然意思是串口发送时间数据。
再好比设置参考值的函数命名为REF_Set( ),调用时写成:
REF_Set(current_voltage);(一般参考值Reference简写为REF)
显然意思是将当前的电压设置为参考值。
主谓格式的命名大大增长了代码的可能性。
固然,函数命名中必要时能够出现宾语。这种状况多出如今函数没有参数的状况下。如一个函数的功能是LCD显示时间,而时间是全局变量,所以这个函数就不须要参数,此时直接定义成void LCD_Display_Time(void)(实际上是声明,由于没写函数体)。
命名时首字母大写不会和数组混淆吗?显然不会,由于函数不管是在定义、声明仍是调用的时候后面都必须跟着”( )”。
4)标号命名
因为在硬件编程中标号能够用循环来代替,因此不多用到。咱们规定标号的命名格式基本同变量,使用所有小写的名词,可是只用一个单词表示便可。由于标号时候的时候或者前面加了goto,或者后面加了“:”,很容易与变量区分开。何况只是一个定位标志,因此一个单词足够了。
5)自定义类型命名
自定义类型命名主要指使用typedef定义的新类型名,以及结构体类型、共用体类型的类型名(而非该类型的变量名)。
自定义的新类型名,只用一个单词,首字母大写。可是定义这种新类型的变量时,命名规则与变量命名规则彻底相同。
请自行体会新类型名与新类型变量的区别。
6)宏定义命名
宏定义命名所有使用大写字母,单词数不限。能够加入数字和下划线,可是数字不能开头 。
因为宏定义的特殊性,对其使用名词或动词不做规定。由于宏定义一个函数时,应该是动词性质,而宏定义一个常数时,应该是名词性质。
3、表达式书写
表达式书写时,最重要的是意义明确。因为C语言不一样运算符有着不一样的结合顺序和优先级,所以很容易形成歧义,即实际运算顺序与设想运算顺序不一样。除了彻底理解并熟记结合顺序与优先级,最简单的方法就是用括号来明确运算顺序——在表达式中,括号的优先级是最高的。
另外,运算符与其操做数之间要空格。如:
a=a+b;
应写成:
a = a + b;
这样作可让表达式显得不那么拥挤而增长可读性,但这不是重点。这样作的重点是帮咱们避免不少不易识别的错误。如:
a=a/*b;
咱们的本意是a除以指针变量b指向的内容,而后将商赋给a。然而残酷的现实是,编译器发现了连起来的“/*”,没错,这是注释符。因此,后面的内容都会被注释掉,直到找到最近的“*/”。
因此咱们应该写成:
a = a / *b; //指针运算符*应该紧跟指针变量b
或者:
a=a/(*b); //不过即使这样写也应该加入空格,便于阅读
有人会说,如今的IDE会用不一样的颜色提示注释内容,因此这样的错误应该不会出现。可是我想说的是,做为一个立志作合格的工程师的你,会容许本身有不严谨的习惯吗?何况自己咱们的文档是为了在C语言语法、词法基础上,制定一个编程规范。
另外,有些老版本的C编译器容许用=+来代替+=的含义,即复合赋值号的两个符号顺序能够是反的。这样的话,若是写出:
a=-1;
本意是将-1赋给a,可是编译器却会理解成:
a = a - 1;
显然意义彻底变了。
有人又会说了,你不是说老版本的C编译器嘛,我不用不就好了吗。然而,咱们要考虑代码的可移植性,就毫不应该容许这样的想法。
所以,在书写表达式的时候,不要吝惜你的空格和括号。
还有一点值得说明的是,复合赋值运算符的两个运算符不能分开。如“+=”不能写成“+ =”。
4、文件编写
一、文件划分
一个简单的程序,只有几行到几十行,放在一个文件内一目了然。可是一个较大的项目中可能会有成千上万行代码,更有大型程序代码数以百万行计。这样规模的代码,存放在一个文件内,其恐怖程度请自行想象。
当一个函数的代码量超过几十行时,就应该考虑有没有可能把其中某些代码提取出来打包成另外一个函数而后调用。一样的,当一个文件的代码量超过几百行时,就应该考虑有没有可能把一些函数分出来放到别的文件中去。这样作都是为了程序的可读性和方便调试,毕竟一个较短的函数功能测试要比一个长函数容易得多。
然而,一个更好的划分文件的依据应该是按模块划分。固然,相应的划分函数的依据应该是按功能划分。也就是说,一个文件存放一个模块的内容,一个函数完成单一的功能。
二、文件内容
在C语言编程时,有两种文件。一种是源文件(source file,后缀为.c),另外一种是头文件(head file,后缀为.h)。
C语言的编译是以c文件为单位的,所以只有h文件时是没法编译的。根据项目规模大小,一个项目能够由单个c文件构成,也能够有多个c文件和h文件共同构成。
C语言编译器在编译时,一般经历如下步骤:
预处理→语法、词法分析→编译→汇编→连接。
预处理阶段,将根据预处理指令来修改c文件内容。其中,预处理指令包括宏定义(#define)、条件编译指令(#ifdef、#ifndef、#endif等)、头文件包含指令(#include)、特殊符号(LINE、FILE等)。对于头文件包含指令来说,其做用是将所包含h文件中的内容替换到包含指令处,固然若是内容中有其余预处理指令,也会作相应处理。
所以,h文件在编译时将插入到c文件中。因而可知,h文件能够出现任何符合c语言语法的内容,可是在实际编程中,咱们显然不会这样作,由于这样作就失去了区分c文件和h文件的意义。
h文件最大的意义是做为对外接口使用,在发布库文件时做用更是明显。也就是说,h文件的内容用来提供供其余文件或函数调用的函数原型、变量等内容。下面具体来规定c文件和h文件中应该出现的内容:
源文件(.c) |
头文件(.h) |
头文件包含指令(#include) |
头文件包含指令(#include) |
|
宏定义(#define) |
全部函数定义(必须有函数体,即{ }) 内部函数声明(static,不能有函数体) |
外部函数声明(extern,不能有函数体) |
外部变量定义(必须赋初值) 静态外部变量定义(static,必须赋初值) |
外部变量声明(extern,不能赋值)
|
|
自定义类型(typedef) |
外部数组定义 静态内部数组定义(static) |
外部数组声明(const) |
条件编译 |
条件编译 |
由上表能够看出,h文件内存放的都是对外可见的变量、函数数组等的声明,宏定义则是对内对外均可以使用,放在这里主要为了修改方便。在定义外部变量、数组和函数时,不须要写extern,由于缺省时默认extern。而声明外部变量、数组和函数时,必须用extern显式声明,这样是为了让代码更直观。
函数说明是必需要写的,写清函数的入口、出口参数及其功能,以及其它说明,对于代码维护和改写能带来极大的方便。
一般,若是h文件中所有是对外接口,而对应c文件中各函数均不调用本文件中的其余内容(变量、函数等),也能够不用包含自身的h文件。
另外,程序编写时,缩进要规范,要能表达所属层次关系。每次缩进4个字符,不能随意缩进。
关于函数体或组合语句使用{}的格式,常见的有两种格式:
int main( ){
}
或者:
int main( )
{
}
本人比较偏向第一种,由于能够节省行数,让程序紧凑。可是这个问题见仁见智,有人以为第一种不如第二种对齐方式井井有条。因此这个就让两种方式并存吧。由于其余问题不涉及审美习惯,只要规定好你们执行就行了,这个毕竟涉及到每一个人的审美不一样。
h文件中必须在开头和末尾写条件编译:
#ifndef __全大写文件名_H__ (或者写成:全大写文件名_H__)
#ifndef __全大写文件名_H__
…(文件内容)
#enif
这样作是为了防止屡次包含,保证在编译时前面已经替换过该头文件,后面将再也不替换,不然有些内容可能重复定义。
下面用代码示例:
<protocol.h>:(每个h文件中必须有√标注的内容)
#include <msp430x14x.h> //头文件包含
#ifndef __PROTOCOL_H__ //条件编译 √
#define __PROTOCOL_H__ //条件编译 √
//#define MONITOR_TERMINAL //条件编译
#define MONITOR_NODE1 //条件编译
//#define MONITOR_NODE2 //条件编译
#define MATCHING_CODE 0x55 //宏定义
#define HOST_ADDRESS 0x40 //宏定义
#define NODE_1 0x41 //宏定义
#define NODE_2 0x42 //宏定义
typedef struct { //自定义类型
float start_bit;
float TXD_data;
float stop_bit;
}TX_Data;
extern unsigned char Tx_Data_Packet[]; //外部数组声明
extern unsigned char Rx_Data_Packet[]; //外部数组声明
extern unsigned char protocol_set_flag; //外部变量声明
extern unsigned char Extract_Data(void); //外部函数声明
#endif //条件编译 √
<protocol.c>:
#include<math.h> //头文件包含,系统库函数用<>
#include"protocol.h" //头文件包含,系统库函数用“”
Static unsigned char easy_delay(void); //内部函数声明
unsigned char protocol_set_flag = 0; //外部变量定义
unsigned char Tx_Data_Packet[6] = {'0','1','2','3','4','5'}; //外部数组定义
unsignedchar Rx_Data_Packet[6] = {'0','1','2','3','4','5'}; //外部数组定义
Static char temp_Packet[6] = {'0','1','2','3','4','5'}; //静态外部数组定义,只能本文件使用
/********************************************************
*名 称:Extract_Data()
*功 能:提取接收到的数据帧
*入口参数:无
*出口参数:1-成功,0-失败
*说 明:
********************************************************/
unsigned char Extract_Data(void){
unsigned char temp = 0;
temp = Rx_FIFO_ReadChar();
if( temp == MATCHING_CODE ){
UART_TX_OPEN();
Rx_Data_Packet[0] = temp;
Rx_Data_Packet[1] = Rx_FIFO_ReadChar(); //来源
Rx_Data_Packet[2] = Rx_FIFO_ReadChar(); //去向
Rx_Data_Packet[3] = Rx_FIFO_ReadChar(); //光照+温度高
Rx_Data_Packet[4] = Rx_FIFO_ReadChar(); //温度低
Rx_Data_Packet[5] = '\0';
return (1);
}
else return (0);
} //外部函数定义,必须在前面写函数说明
/********************************************************
*名 称: easy_delay()
*功 能:简单延时
*入口参数:无
*出口参数:无
*说 明:
********************************************************/
Static unsigned char easy_delay(void){
unsigned int i = 0;
for( i=0; i<1000 ; i++);
} //内部函数定义,必须在前面写函数说明,且在本文件前部声明以便阅读
这两个文件都是从编者曾经写的代码中截取出来的,有些部分是为了演示内容如今添加进去的,源代码中不存在,请你们没必要在乎细节,关键领会两个文件中应该出现的内容,均在后面用注释的方式做了说明。
Notice:
本文中出现的不少字符,为了美观和直观,中英文输入法混用,或者加多个空格。你们在编程时,切记使用英文半角输入法,并且无论你加多少空格或制表符,编译器都按一个处理。