代码编程规范-扩展(表达式和基本语句)
扫描二维码
随时随地手机看文章
前言
这篇重点介绍一下代码编程规范的扩展要求-表达式和基本语句规范要求
要求
【规范1】赋值语句不要写在if等语句中,或者作为函数的参数使用
因为if语句中,会根据条件依次判断,如果前一个条件已经可以判定整个条件,则后续条件语句不会再运行,所以可能导致期望的部分赋值没有得到运行
如:if (test == 15 || HandleLogicFun()) {...},此时若test = 15,则函数HandleLogicFun就不会执行
【规范2】用括号明确表达式的操作顺序,避免过分依赖默认优先级
使用括号强调所使用的操作符,防止因默认的优先级与设计思想不符而导致程序出错;
同时使得代码更为清晰可读,然而过多的括号会分散代码使其降低了可读性。
一般代码行的运算符比较多就需要,如果很简单必要性不大,反而降低了美观性
复合表达式
【规范1】不要编写太复杂的符合表达式
太复杂的符合表达式不利于代码阅读
如i = a >= b && c < d && c + f <= g + h,过于复杂
【规范2】不要有多用途的符合表达式
如d = (a = b + c) + r该表达式既求a值又求d值,应该拆分两个独立的语句:
a = b + c; d = a + r;
if 语句
【规范1】布尔变量与零值比较
不可将布尔变量直接与TRUE、FALSE或者1、0进行比较,虽然基本不会有什么大问题,但是会影响阅读性
根据布尔类型的语义,零值为“假” (记为FALSE) ,任何非零值都是“真” (记为TRUE) 。TRUE的值究竟是什么并没有统一的标准,例如 Visual C++ 将TRUE定义为1,而 Visual Basic 则将TRUE定义为-1;因此对于布尔变量,它与零值比较的标准if语句如下:if (flag) // 表示 flag 为真 { } if (!flag) // 表示 flag 为假 { }
【规范2】整型变量与零值比较
应当将整型变量用==或!=直接与0比较。
对于整型变量,它与零值比较的标准if语句如下:if (flag == 0) { } if (flag != 0) { }
【规范3】浮点变量与零值比较
不可将浮点变量用==或!=与任何数字比较。
千万要留意,无论是float还是double类型的变量,都有精度限制。所以一定要避免将浮点变量用==或!=与数字比较,应该设法转化成>=或<=形式。
假设浮点变量的名字为x,应当将if (x == 0.0)// 隐含错误的比较转化为if ((x >= -EPSINON) && (x <= EPSINON)) { ... }
【规范4】指针变量与零值比较
应当将指针变量用==或!=与NULL比较,虽然和0比较基本不会有什么大问题,但是会影响阅读性,误以为该变量是其它类型
指针变量的零值是“空” (记为NULL) 。尽管NULL的值与0相同,但是两者意义不同。假设指针变量的名字为p,它与零值比较的标准if语句如下:if (p == NULL) // p 与 NULL 显式比较,强调 p 是指针变量 { } if (p != NULL) { }
【规范5】若if有else if分支,则必须有else分支
虽然没有else分支,但是在以后的代码维护中能清楚表明自己考虑了这种情况,但是目前不需要做任何处理
if (...) { ... } else if (...) { ... } else { // TODO }其中表明注释 “TODO” 说明表明自己考虑了这种情况,但是目前不需要做任何处理
【规范6】对于if ("变量" == "常量")通常建议写成if ("常量" == "变量")
好处时能避免粗心大意写成if ("变量" = "常量"),而编译可能不会报错,最终代码运行时就会出现异常,而if ("常量" == "变量")这种写法若少了=,根据常量不能被赋值的规则,编译时就会报错。
当然这种写法可能不美观,如果强迫症,那建议养成习惯后可以再恢复if ("变量" = "常量")这种写法,因为写该语句时都会下意识想到该规则,从而避免少写=,也能避免粗心引起的该问题。if (5 == test) { } if (NULL == pTest) { }
for/while/do 循环语句
循环语句有for语句,while语句,do语句,其中for语句是使用频率最高的,以下规范1、2介绍如何提高for循环语句的效率,其根本是降低循环体的复杂性。
【规范1】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数
其中第二种就比第一种的运行效率要高,光从C代码角度看,区别不大,这个需要从汇编的角度看才能明显看出,具体可以自行尝试看执行时间(循环次数足够)看或者网上百度两种方式的对比,这里不再描述,网上比较详细;不过我建议是直接看两种编译后的汇编语句,这样感触最深。
// 第一种 for (i = 0; i < 100; i++) { for (j = 0; j < 10; j++) { ... } } // 第二种 for (i = 0; i < 10; i++) { for (j = 0; j < 100; j++) { ... } }
【规范2】循环中存在判断语句等,根据实际情况选择
如以下代码中判断表达式在循环体中,第二种就效率来说比第一种高,但是就代码简洁性来看,第一种更好,那么如何取舍呢?
循环次数较少,可以采用第一种,原因是在循环次数较少的情况下,第二种的效率提高不明显
底层驱动开发时,采用第一种往往会极大地影响效率,所以普遍采用第二种(之前开发LCD驱动时,画点时第二种比第一种在速度上明显提高)
// 第一种 for (i = 0; i < 10; i++) { if (...) { ... } else { ... } } // 第二种 if (...) { for (i = 0; i < 10; i++) { ... } } else { for (i = 0; i < 10; i++) { ... } }
【规范3】不能再for循环体内修改循环变量,防止for循环失去控制
下列代码容易失去控制
for (i = 0; i < 10; i++) { if (...) { i += 2; } else { ...; } }
【规范4】建议for循环控制变量的取值采用“半开半闭区间”原则
以下两种方式功能一样,但是第一种的写法更加直观
// 第一种 for (i = 0; i < N; i++) { ... } // 第二种 for (i = 0; i <= N - 1; i++) { ... }
【规范5】空循环也应该使用{}或者continue,而不是一个简单的分号
这样做的目的是直观地看出是一个空循环体
for (i = 0; i < N; i++) { } while (flag) { condition; }
switch 语句
【规范1】每一个switch语句最后都要写default分支,即使什么也不做
这个和else if的else分支规则一样,目的是在以后的代码维护中能清楚表明自己考虑了这种情况,但是目前不需要做任何处理
【规范2】每个case语句的结尾别忘记加break,否则导致多个分支重叠,除非是有意使多个分支重叠
建议case和break成对编写,不要写了其他语句再写break,防止遗忘,即使有意重叠,也应该这样做,写完后再删除break
goto语句
【规范1】尽量少用,但是我建议禁用更好,对于程序的阅读有很大的阻碍,如果必要可以采用do{...}while(0)的方式替代
一般情况goto能解决退出函数时需要做的处理,如打开文件中间出现错误都需要关闭文件,不需要再退出的地方都写
int ReadFileDate(char *pBuf, uint16_t lenth) { if (NULL == pBuf || 0 == lenth) { return; } if (open(file) == -1) { goto EOF; } if (read(file, pBuf, lenth) == -1) { goto EOF; } ... // 其他处理,可能也存在错误需要退出处理(因为已经打开文件了) EOF: close(file); }
这种方式如果禁用goto,则基本每一个退出函数的地方都需要close反而麻烦,此时可以通过do{...}while(0)的方式替代
int ReadFileDate(char *pBuf, uint16_t lenth) { if (NULL == pBuf || 0 == lenth) { return; } do { if (open(file) == -1) { break; } if (read(file, pBuf, lenth) == -1) { break; } ... // 其他处理,可能也存在错误需要退出处理(因为已经打开文件了) } while (0); close(file); }





