1.NULL就是NULL,在许多编译器中它被定义为0。表示指针不指向任何地址。
2.NUL是ASCII码表中的第1个字符,表示的是空字符,其ascii码值为0。虽然两者的值都为0,但表示的意义不一样。
3.一般在函数 入口处使用assert(NULL!=p)对参数 进行检验 ,但是这只是建立在p不是野指针 的情况 下,如果 是野指针 ,这种做法也不无济于事。
4. 使用assert 宏的地方在Release版本中会被 编译 器完全 优化掉。所以assert宏通常只是帮助我们调试代码,用于定位错误。
5. RAII 资源获取即初始化
6. int a[10]={0}; memset(a,0,sizeof(a)); //三个参数依次是被设置 的内存起始地址,第二个是要被 设置 的值 ,第三个是要被 设置 的内存的大小 ,单位 是字节 。
7. for循环中尽量使用半开半闭的区间,即第二个分句不要带等号。而且循环初始化一般从0开始,不要从1开始 。
8.用malloc函数申请0字节的内存,并不会返回NULL,而是返回一个正常 的地址,但是无法使用。对这种情况 也无法 用if(NULL!=p)来检验 。
9.free函数 调用 后一定要把相应 的指针 置 NULL.
10.在函数中不要返回一片已经被释放 的内存 。
函数
1.每个函数 都 必须 有注释,注释包含的内容和次序如下:
/************************************************************************
* Function Name: nucFindThread
* Create Date: 2000/01/07
* Author/Corporation: your name/your company name
*
* Description: Find a proper thread in thread array.
* Ifit’sanewthensearchanempty.
*
* Param: ThreadNo: someParam description
* ThreadStatus: someParam description
*
* Return Code: Return Code description,eg:
ERROR_Fail: not find a thread
ERROR_SUCCEED: found
*
* Global Variable: DISP_wuiSegmentAppID
* File Static Variable: naucThreadNo
* Function Static Variable: None
*
*------------------------------------------------------------------------
* Revision History
* No. Date Revised by Item Description
* V0.5 2008/01/07 your name … …
************************************************************************/
static unsigned char nucFindThread(unsigned char ThreadNo,unsigned char ThreadStatus)
{
…
}
2.原则上少使用全局变量,如果必须使用,应对外提供访问全局变量的函数,避免直接读写全局变量,尤其是在多线程程序中,可以很方便地在函数中为变量读写加锁。
3。复制函数,一般将目的参数放在前面,源参数放后面。
4。如果参数是指针,且仅作输入参数用,则应在类型前加const,以防止该指针在函数体内被意外修改。
如:void str_cpy(char * strDst, const char* strSrc);
5.assert宏与if检查
assert一般是用来检查bug,主要是用来检查致命的非正常的操作(如向PCI设备写东西,但是PCI设备根本就不存在),执行的最终结果是调用abort,即你的进程异常终止,这在软件发布上是致命的,不管怎么说你的程序至少要执行下去啊,别动不动就终止了.
if是常规性的检查,包括参数判断,异常条件等等,判断的结果处理随你,你可以仅仅打印异常信息,可以终止程序.几乎所有assert能干的事,if都可以干.只是有些同学觉得方便就喜欢用assert.
因此,assert尽量少用,有些公司的c编程规范直接规定不能用assert.
下面是对assert的描述:
DESCRIPTION
The
assert
() macro tests the given expression and
if
it is
false
, the
calling process is terminated. A diagnostic message is written to stderr
and the function
abort
(3) is called, effectively terminating the program.
If expression is
true
, the
assert
() macro does nothing.
The
assert
() macro may be removed at compile
time
by defining NDEBUG as a
macro (e.g., by
using
the cc(1) option -DNDEBUG).
EXAMPLES
The assertion:
assert
(1 == 0);
generates a diagnostic message similar to the following:
Assertion failed: (1 == 0), function main, file assertion.c, line
100.
-----------------------
6。函数的功能要单一,不要设计多用途的函数。微软的win32 API就是违反本原则的典型,其函数往往因为参数不一样而呈现不同的功能,导致初学者的疑惑。
7。函数体的规模尽量小,最好不超过80行。
8。使函数满足相同的输入产生相同的输出的要求,当然不些基于概率的算法除外。这就要求在函数中尽量避免使用全局变量、静态变量等带有”记忆“功能的量。
9。尽量不要使用类型和个数不确定的参数。
C标准库函数prinft是采用不确定参数的典型代表,其原型为:
int printf(const char* format[, argument]……);
这种风格的函数在编译时丧失了严格的类型检查。
10。有时候函数不需要返回值,但为了增加灵活性,如支持链式表达,可以附加返回值。如:
char str[10];
int length= strlen(strcpy(str,”hello”);
11.在函数中对于经过各种途径进入函数体内的变量都要检查其有效性。如全局变量、文件句柄等。
12。函数名与返回值类型在语义上不可冲突。反例:C语言标准库函数getchar竟然无耻地返回了int.其原型为:int getchar(void);
13.汇编语言应该被封装并被隔离,最好同时定义成宏。
在需要汇编指令的地方建议以如下方式封装并隔离这些指令:
汇编函数
C函数
宏
对汇编语言进行封装原因:由于不同的CPU识别的汇编语言不完全相同,因此从程序可移植性角度考虑,汇编语言应该封装并隔离,这样在处理由于汇编引起的可移植性问题时,比较容易找到需要修改的地方。
内嵌汇编:在C程序中内嵌汇编提供了直接读/写硬件的能力,但编译器并不检查和分析内嵌汇编可能导致C变量的值改变或一些严重错误。(注:内嵌汇编也是一种引起变量值以意料之外方式改变的途径,此时需要volatile保留字)。
14。声明或定义一个数组时,它的大小应该显式声明,这样在查看代码时比较容易知道其长度。注意这里包括“声明和定义”
15。调用函数时尽量避免隐式类型转换。
16。一些需要强制类型转换的地方,除了最终对结果的强制类型转换,对相关操作数提前进行类型转换,可以避免一些错误,毕竟保持谨慎是明智的做法。
比如 short i=23; short j=xxxx; int k= i+j; 根据本条原则应该写成 int k=(int)i+(int)j;实际上是引入了一些中间变量。
17。建议尽量少用多个返回路径,用一个临时变量存储返回值,在函数结尾再统一给出返回表达式。
18。使用strncpy库函数代替strcpy库函数。
strncpy 是 C语言的库函数之一,来自 C语言标准库,定义于 string.h,char *strncpy(char *dest, const char *src, int n),把src所指向的字符串中以src地址开始的前n个字节复制到dest所指的数组中,并返回dest。
如果n =dest串长度,则dest串没有NULL字符,会导致输出会有乱码。如果不考虑src串复制完整性,可以将dest 最后一字符置为NULL。
一般情况下,使用strncpy时,建议将n置为dest串长度(除非你将多个src串都复制到dest数组,并且从dest尾部反向操作),复制完毕后,为保险起见,将dest串最后一字符置NULL,避免输出乱码问题。当然喽,无论是strcpy还是strncpy,保证dest串容量(能容纳下src串)才是最重要的。
19。指针的数学运算(整数加减运算)只能用在指向同一个数组元素的指针上。对不是指向数组或数组元素的指针,做整数加减上会导致未定义的行为。指针在做减法、>、>=、<、<=等运算符时,只有指针指向同一个数组,结果才是可预知的。
20。传递给库函数的值必须检查其有效性。C标准库中的许多函数根据ISO标准,并不需要检查传递给它们的参数的有效性。所以程序员应该将为所有带有严格输入域的库函数(标准库、第三方库以及自己定义的库)提供适当的输入值检查机制。(???对自己编写的库进行输入值检查是否会显得多此一举,检查参数有效性的责任到底是谁的??可能作为程序员不应该轻易相信别人给你提供的参数,因为当你发布了一个第三方的库,别人没有看懂,就乱用气,参数根本不符合要求,但它不将责任揽到自己头上,而会说什么垃圾东西。所以还是谨慎一点,不在故障发生在调用自己代码的过程中)。
举例:
标准库中log函数、sqrt函数并没有进行有效性检查,所以在传递参数进入前,要自己进行参数有效性的检查。
fmod函数的第二个参数不能为0。
toupper和tolower:当传递给toupper函数的参数不是小写字母时,某些实现能产生非预期的结果(tolower的情况类似)
如果为ctype.h中的字符测试函数传递无效的值时,会给出未定义的行为。
---------有许多方法可以满足本规则的要求,包括:----------------
a).调用函数前检查输入值
b).产生函数的“封装”(wrapped)版本,在该版本中首先检查输入,然后调用原始的函数。注意这种方法对没有原有函数源码但能调用的库函数都有效。
C).自己设计带有内部检查的相同功能的函数。这种做法相比B中的做法难度会比较大,但有可能会带来效率的提升。
D).静态地声明输入参数 永远不会效的值 。
21。不使用任何变量编写strlen函数
“不使用中间变量”是说程序员不能显式的申请内存,即不能有局部变量或者动态内存申请。如果函数自动申请栈内存或者使用寄存器存储变量,或者使用立即数寻址即常量,那么就相当于“不使用中间变量”。从函数原型看,返回值为int,那么在函数内部必定需要一个地方存储这个值,要么是常数要么是寄存器。长度不为1时不能一次就求出来,说明必须有递归调用,这样递归时函数会自动申请栈内存,这样就相当于程序员“不使用中间变量”了。
#include <iostream>
using namespace std;
int mystrlen(const char* str)
{
if(str==NULL)
return 0;
if(*str!='\0')
return 1+mystrlen(++str); //函数调用的开销比递归 大得多,不到万不得已不要用递归 。
else
return 0;
}
void main()
{
char *str="Diaoyu islands belong to china";
cout<<mystrlen(str)<<endl;
}