当前位置:首页 > > 大橙子疯嵌入式


前言

这篇重点介绍一下代码编程规范的扩展要求-表达式和基本语句规范要求

要求

【规范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】循环中存在判断语句等,根据实际情况选择

如以下代码中判断表达式在循环体中,第二种就效率来说比第一种高,但是就代码简洁性来看,第一种更好,那么如何取舍呢?

  1. 循环次数较少,可以采用第一种,原因是在循环次数较少的情况下,第二种的效率提高不明显

  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);
}

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
关闭