目录
:
一
.编程修养
二
.编程技巧
三
.编程风格
/*******************************************************
一
.编程修养
----C语言程序写作上的三十二个“修养”
*******************************************************/
————————————————————————
01、版权和版本
02、缩进、空格、换行、空行、对齐
03、程序注释
04、函数的
[in][out]参数
05、对系统调用的返回进行判断
06、
if 语句对出错的处理
07、头文件中的
#ifndef
08、在堆上分配内存
09、变量的初始化
10、
h和c文件的使用
11、出错信息的处理
12、常用函数和循环语句中的被计算量
13、函数名和变量名的命名
14、函数的传值和传指针
15、修改别人程序的修养
16、把相同或近乎相同的代码形成函数和宏
17、表达式中的括号
18、函数参数中的
const
19、函数的参数个数
20、函数的返回类型,不要省略
21、
goto语句的使用
22、宏的使用
23、
static的使用
24、函数中的代码尺寸
25、
typedef的使用
26、为常量声明宏
27、不要为宏定义加分号
28、
||和&&的语句执行顺序
29、尽量用
for而不是while做循环
30、请
sizeof类型而不是变量
31、不要忽略
Warning
32、书写
Debug版和Release版的程序
21、
goto语究使劲
22、宏的使用
23、
static的使用
24、函数中的代码尺寸
25、
typedef的使用
26、为常量声明宏
27、不要为宏定义加分号
28、
||和&&的语句执行顺序
29、尽量用
for而不是while做循环
30、请
sizeof类型而不是变量
31、不要忽略
Warning
32、书写
Debug版和Release版的程序
————————————————————————
1.版权和版本
file: in top of file as
/************************************************************************
*
*
filename:
filename.c
*
*
description: ...
*
*
author:
sharp_xiao, (time)2007-04-05
*
*
version number:
1.0
*
*
amend notes:
*
*
************************************************************************/
function: in front function as
/*================================================================
*
* name:
XXX
*
* input:
*
type name [IN] : descripts
* output:
*
type name [OUT] : descripts
*
* description:
*
..............
*
* return:
*
succed:TRUE, failed: FALSE
*
* pop abnormity:
*
* author: sharp_xiao 2007-04-05
*
*
================================================================*/
2. 函数指针
首先要理解以下三个问题:
(1)C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针;
(2)调用函数实际上等同于
"调转指令+参数传递处理+回归位置入栈",本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器;
(3)因为函数调用的本质是跳转到某一个地址单元的
code去执行,所以可以"调用"一个根本就不存在的函数实体;
eg.
typedef void (*lpFunction) ( ); /* 定义一个无参数、无返回类型的 */
/* 函数指针类型 */
lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定义一个函数指针,指向*/
/* CPU启动后所执行第一条指令的位置 */
lpReset(); /* 调用函数 */
3.数组
vs.动态申请
(1)尽可能的选用数组,数组不能越界访问
(真理越过一步就是谬误,数组越过界限就光荣地成全了一个混乱的嵌入式系统);
(2)如果使用动态申请,则申请后一定要判断是否申请成功了,并且
malloc和free应成对出现!即"谁申请,就由谁释放"原则。不满足这个原则,会导致代码的耦合度增大;
eg.
Change:
char * function(void)
{
char *p;
p = (char *)malloc(…);
if(p==NULL)
…
;
…
/* 一系列针对p的操作 */
return p;
}
...
char *q = function();
…
free(q);
To:
char *p=malloc(…);
if(p==NULL)
…
;
function(p);
…
free(p);
p=NULL;
void function(char *p)
{
…
/* 一系列针对p的操作 */
}
4.关键字
const
(1)关键字
const的作用是为给读你代码的人传达非常有用的信息。例如,在函数的形参前添加const关键字意味着这个参数在函数体内不会被修改,属于"输入参数"。在有多个形参的时候,函数的调用者可以凭借参数前是否有const关键字,清晰的辨别哪些是输入参数,哪些是可能的输出参数。
(2)合理地使用关键字
const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改,这样可以减少bug的出现。
(3)const在
C++语言中则包含了更丰富的含义,而在C语言中仅意味着:"只能读的普通变量",可以称其为"不能改变的变量"(这个说法似乎很拗口,但却最准确的表达了C语言中const的本质),在编译阶段需要的常数仍然只能以#define宏定义!故在C语言中如下程序是非法的:
const int SIZE = 10;
char a[SIZE]; /* 非法:编译阶段不能用到变量/只能用常量定义数组长度 */
(4)In C++
class A
{
const int SIZE = 100; // 错误,企图在类声明中初始化
const 数据成员
int array[SIZE]; // 错误,未知的
SIZE
};
const 数据成员的初始化只能在类构造函数的初始化表中进行,例如
class A
{
A(int size); // 构造函数
const int SIZE ;
};
A::A(int size) : SIZE(size) // 构造函数的初始化表
{
}
A a(100); // 对象
a 的SIZE 值为100
A b(200); // 对象
b 的SIZE 值为200
5.关键字
volatile
在变量的定义前加上
volatile关键字可以防止编译器进行优化.
volatile变量可能用于如下几种情况:
(1) 并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类);
(2) 一个中断服务子程序中会访问到的非自动变量
(也就是全局变量);
(3) 多线程应用中被几个任务共享的变量。
eg.
Optimize code:
int a,b,c;
a = inWord(0x100); /*读取I/O空间0x100端口的内容存入a变量*/
b = a;
a = inWord (0x100); /*再次读取I/O空间0x100端口的内容存入a变量*/
c = a;
As:
int a,b,c;
a = inWord(0x100); /*读取I/O空间0x100端口的内容存入a变量*/
b = a;
c = a;
6.书写
Debug版和Release版的程序
#ifdef DEBUG
void TRACE(char* fmt, ...)
{
......
}
#else
#define TRACE(char* fmt, ...)
#endif
7.尽量用
for而不是while做循环
Change:
p = pHead;
p = pHead;
while ( p )
{
...
p = p->next;
}
To:
for ( p=pHead; p; p=p->next )
{
..
}
8.为常量声明宏
Change:
for ( i=0; i<120; i++)
{
....
}
To:
#define MAX_USR_CNT 120
for ( i=0; i
{
....
}
eg.int user[120]; use macro as the same
9.typedef的使用
一般来说,一个
C的工程中一定要做一些这方面的工作,因为你会涉及到跨平台,不同的平
台会有不同的字长,所以利用预编译和
typedef可以让你最有效的维护你的代码,如下所示:
#ifdef SOLARIS2_5
typedef boolean_t BOOL_T;
#else
#else
typedef int BOOL_T;
#endif
typedef short INT16_T;
typedef unsigned short UINT16_T;
typedef int INT32_T;
typedef unsigned int UINT32_T;
#ifdef WIN32
typedef _int64 INT64_T;
#else
typedef long long INT64_T;
#endif
typedef float FLOAT32_T;
typedef char* STRING_T;
typedef unsigned char BYTE_T;
typedef time_t TIME_T;
typedef INT32_T PID_T;
使用
typedef的其它规范是,在结构和函数指针时,也最好用typedef,这也有利于程序的
易读和可维护性
.
10.函数中的代码尺寸
一个函数完成一个具体的功能,一般来说,一个函数中的代码最好不要超过
600行左右,越
少越好,最好的函数一般在
100行以内,300行左右的孙函数就差不多了.
11.函数的参数个数(多了请用结构)
一般来说
6个左右.函数参数是从右至左以栈来传递的.
12、函数名和变量名的命名
————————————
我看到许多程序对变量名和函数名的取名很草率,特别是变量名,什么
a,b,c,aa,bb,cc,
还有什么
flag1,flag2, cnt1, cnt2,这同样是一种没有“修养”的行为。即便加上好的注
释。好的变量名或是函数名,我认为应该有以下的规则:
1) 直观并且可以拼读,可望文知意,不必“解码”。
2) 名字的长度应该即要最短的长度,也要能最大限度的表达其含义。
3) 不要全部大写,也不要全部小写,应该大小写都有,如:
GetLocalHostName 或是
UserAccount。
4) 可以简写,但简写得要让人明白,如:
ErrorCode -> ErrCode,
ServerListener -> ServLisner,
UserAccount -> UsrAcct 等。
5) 为了避免全局函数和变量名字冲突,可以加上一些前缀,一般以模块简称做为前缀
。
6) 全局变量统一加一个前缀或是后缀,让人一看到这个变量就知道是全局的。
7) 用匈牙利命名法命名函数参数,局部变量。但还是要坚持“望文生意”的原则。
8) 与标准库(如:
STL)或开发库(如:MFC)的命名风格保持一致。
13.
———————
版权和版本
———————
对于
C/C++的文件,文件头应该有类似这样的注释:
/************************************************************************
*
*
文件名:
network.c
*
*
文件描述:网络通讯函数集
*
*
创建人:
Hao Chen, 2003年2月3日
*
*
版本号:
1.0
*
*
修改记录:
*
*
************************************************************************/
而对于函数来说,应该也有类似于这样的注释:
/*================================================================
*
* 函 数 名:
XXX
*
* 参
数:
*
*
type name [IN] : descripts
*
* 功能描述
:
*
*
..............
*
* 返 回 值:成功
TRUE,失败FALSE
*
* 抛出异常:
*
* 作
者:ChenHao 2003/4/2
*
*
================================================================*/
/*******************************************************
二
.编程技巧
---(C语言嵌入式系统编程修炼
)
*******************************************************/
1).数据指针
unsigned char *p = (unsigned char *)0xF000FF00;
*p=11;
2).函数指针
首先要理解以下三个问题:
a.C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针;
b.调用函数实际上等同于"调转指令+参数传递处理+回归位置入栈",本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器;
c.因为函数调用的本质是跳转到某一个地址单元的
code去执行,所以可以"调用"一个根本就不存在的函数实体.
//"软重启
"的作用
186 CPU启动后跳转至绝对地址
0xFFFF0(对应C语言指针是0xF000FFF0,0xF000为段地址,0xFFF0为段内偏移)执行,请看下面的代码:
typedef void (*lpFunction) ( ); /* 定义一个无参数、无返回类型的
*/
/* 函数指针类型
*/
lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定义一个函数指针,指向
*/
/* CPU启动后所执行第一条指令的位置
*/
lpReset(); /* 调用函数
*/
3)数组
vs.动态申请
a.尽可能的选用数组,数组不能越界访问;
b.如果使用动态申请,则申请后一定要判断是否申请成功了,并且
malloc和free应成对出现!
4)关键字
const
称其为
"不能改变的变量",在编译阶段需要的常数仍然只能以#define宏定义!
故
:const int SIZE = 10;
char a[SIZE]; /* 非法:编译阶段不能用到变量
*/
5)关键字
volatile
volatile变量可能用于如下几种情况:
a.并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类);
b.一个中断服务子程序中会访问到的非自动变量(也就是全局变量);
c.多线程应用中被几个任务共享的变量。
6)使用宏定义
对于宏,我们需要知道三点:
a.宏定义“像”函数;
b.宏定义不是函数,因而需要括上所有“参数”;
c.宏定义可能产生副作用。
eg1.#define MIN(A,B) ((
A)<= (B) ? (A) : (B) ) //正确
eg2.least = MIN(*p++, b);
==>( (*p++) <= (b) ?(*p++):(b) ) //发生的事情无法预料
7)利用硬件特性
CPU对各种存储器的访问速度,基本上是:
CPU内部
RAM > 外部同步RAM > 外部异步RAM > FLASH/ROM
8)活用位操作
9)浮点变量与零值比较
不可将浮点变量用“
==”或“!=”与任何数字比较。
eg.
if (x == 0.0) // 隐含错误的比较
应转化为
:
if ((x>=-EPSINON) && (x<=EPSINON)) //其中
EPSINON 是允许的误差(即精度)
10) do not use sizeof(pointer). Because sizeof a pointer is just 4.
eg:
{
char *ch;
memset(ch, 0, sizeof(ch)); //there will make some crash issue
}
11)notice memset or initialize the variable when define.
12)指向类型
T的指针并不等价于类型T的数组.
eg. 在一个源文件里定义了一个数组:
char a[6];
则在另外一个文件里进行
extern声明时不能用:
extern char *a;
而只能是
: extern char a[];
在使用
extern时候要严格对应声明时的格式.
--------------------------------------
总结
在性能优化方面永远注意
80-20准备,不要优化程序中开销不大的那80%,这是劳而无功的。
--------------------------------------
/*******************************************************
三
.编程风格
---C语言
*******************************************************/
1.参考
"编程修养"
2.命名规则
1)函数
->[C++: 类名]用大写字母开头的单词组合而成.
2)局部变量和参数
->用小写字母开头的单词组合而成.
3)全局变量
->则使全局变量加前缀g_(表示global).eg.int g_howManyPeople; // 全局变量
为了避免全局函数和变量名字冲突,可以加上一些前缀,一般以模块简称做为前缀
.
4)表态变量
->静态变量加前缀s_(表示static)。eg. static int s_initValue; // 静态变量
5)const常量
->常量全用大写的字母,用下划线分割单词。eg. const int MAX_LENGTH = 100;
6)宏常量
->一般用大写字母;但可以了解为函数功能时,可以函数的命名规则命名.
7)数据成员
->[C++]类的数据成员加前缀m_(表示member),这样可以避免数据成员与成员函数的参数同名.
eg.
void Object::SetValue(int width, int height)
{
m_width = width;
m_height = height;
}
8)标识符应当直观且可以拼读,可望文知意,不必进行“解码”
.标识符最好采用英文单词或其组合,便于记忆和阅读.
9)标识符的长度应当符合“
min-length && max-information”原则.
10)命名规则尽量与所采用的操作系统或开发工具的风格保持一致
.
11)程序中不要出现仅靠大小写区分的相似的标识符。
eg. int x, X, z, Z;
12)程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解
.
13)变量的名字应当使用“名词”或者“形容词+名词”。
eg. { int oldValue, newValue;}
14)全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)
.
[C++: 类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身
]. eg. DrawBox(); // 全局函数
15)用正确的反义词组命名具有互斥意义的变量或相反动作的函数等
.
16)尽量避免名字中出现数字编号,如
Value1,Value2 等,除非逻辑上的确需要编号。
17)为了防止某一软件库中的一些标识符和其它软件库中的冲突,可以为各种标识符加上能反映软件性质的前缀。例如三维图形标准
OpenGL 的所有库函数均以gl 开头,所有常量(或宏定义)均以GL 开头.
3.
1) if(NULL == XXX) //use value == XXX to avoid XXX = value
{
NULL;
//use NULL when do nothing
}
153102701
421126198904103817