点击上方蓝色字体了解更多的嵌入式编程实用技能。
如果你觉得该文章对你有帮助,欢迎点赞+关注
这篇重点介绍一下代码编程规范的扩展要求-表达式和基本语句规范要求
【规范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;
【规范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
语句是使用频率最高的,以下规范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;
}
【规范1】每一个switch
语句最后都要写default
分支,即使什么也不做
这个和
else if
的else
分支规则一样,目的是在以后的代码维护中能清楚表明自己考虑了这种情况,但是目前不需要做任何处理
【规范2】每个case
语句的结尾别忘记加break
,否则导致多个分支重叠,除非是有意使多个分支重叠
建议
case
和break
成对编写,不要写了其他语句再写break
,防止遗忘,即使有意重叠,也应该这样做,写完后再删除break
【规范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);
}