第九章、表达式与语句
9.1 运算符的优先级与结合律表
优先级按从高到低排列
类型 | 运算符 | 结合律 |
---|---|---|
数组下标,函数调用,结构成员 | [ ] ( ) -> . | 左结合 |
自增(后缀),自减(后缀) | ++ - - | 左结合 |
自增(前缀),自减(前缀) | ++ - - | 右结合 |
取址,间接寻址 | & * | 右结合 |
一元正号,一元负号,按位求反,逻辑非 | + - ~ ! | 右结合 |
计算内存长度,强制类型转换 | size of () | 右结合 |
乘法类的 | * / % | 左结合 |
加法类 | + - | 左结合 |
按位移位 | << >> | 左结合 |
关系 | < > <= >= | 左结合 |
判等 | == != | 左结合 |
按位与 | & | 左结合 |
按位异或 | ^ | 左结合 |
按位或 | | | 左结合 |
逻辑与 | && | 左结合 |
逻辑或 | || | 左结合 |
条件 | ?: | 右结合 |
赋值 | = += -= *= /= %= &= ^= = <<= >>= | 右结合 |
逗号 | , | 左结合 |
9.2 书写格式和注意事项
规则 9.2.1 一条语句只完成一个功能
说明:复杂的语句阅读起来,难于理解,并容易隐含错误。变量定义时,一行只定义一个变量。
正例:
int iHelp;
int iBase;
int iResult;
iHelp = iBase;
iResult = iHelp + GetValue(&iBase);
反例:
int iBase, iResult;
// 一条语句实现多个功能,iBase 有两种用途。
iResult = iBase + GetValue(&iBase);
9.2.2 在表达式中使用括号,使表达式的运算顺序更清晰
说明:由于将运算符的优先级与结合律熟记是比较困难的,为了防止产生歧义并提高可读性, 即使不加括号时运算顺序不会改变,也应当用括号确定表达式的操作顺序。
正例:
if (((iYear % 4 == 0) && (iYear % 100 != 0)) || (iYear % 400 == 0))
反例:
if (iYear % 4 == 0 && iYear % 100 != 0 || iYear % 400 == 0)
规则 9.2.3 避免表达式中的附加功能,不要编写太复杂的复合表达式
说明:带附加功能的表达式难于阅读和维护,它们常常导致错误。对于一个好的编译器,下 面两种情况效果是一样的。
正例 :
aiVar[1] = aiVar[2] + aiVar[3];
aiVar[4]++;
iResult = aiVar[1] + aiVar[4];
aiVar[3]++;
反例:
iResult = (aiVar[1] = aiVar[2] + aiVar[3]++) + ++aiVar[4] ;
规则 9.2.4 不可将布尔变量和逻辑表达式直接与 TRUE、FALSE 或者 1、0 进行比较
说明:TURE 和 FALSE 的定义值是和语言环境相关的,且可能会被重定义的。
正例:
设 bFlag 是布尔类型的变量
if (bFlag) // 表示 flag 为真
if (!bFlag) // 表示 flag 为假
反例:
设 bFlag 是布尔类型的变量
if (bFlag == TRUE)
if (bFlag == 1)
if (bFlag == FALSE)
if (bFlag == 0)
规则 9.2.5 在条件判断语句中,当整型变量与 0 比较时,不可模仿布尔变量的风格, 应当将整型变量用“==”或“!=”直接与 0 比较
正例:
if (iValue == 0)
if (iValue != 0)
反例:
if (iValue) // 会让人误解 iValue 是布尔变量
if (!iValue)
规则 9.2.6 不可将浮点变量用“==”或“!=”与任何数字比较
说明:无论是 float 还是 double 类型的变量,都有精度限制。所以一定要避免将浮点变量用 “==”或“!=”与数字比较,应该转化成“>=”或“<=”形式。
正例:
if ((fResult >= -EPSINON) && (fResult <= EPSINON))
反例:
// 隐含错误的比较 其中EPSINON是允许的误差(即精度)
if (fResult == 0.0)
规则 9.2.7 应当将指针变量用“==”或“!=”与 NULL 比较
说明:指针变量的零值是“空”(记为 NULL),即使 NULL 的值与 0 相同,但是两者意义不同。
正例:
if (pHead == NULL)// pHead 与 NULL 显式比较,强调 pHead 是指针变量
if (pHead != NULL)
反例1:
if (pHead == 0) // 容易让人误解 pHead 是整型变量
if (pHead != 0)
反例2:
if (pHead) // 容易让人误解 pHead 是布尔变量
if (!pHead)
规则 9.2.8 在 switch 语句中,每一个 case 分支必须使用 break 结尾,最后一个分支必须是 default 分支
说明:避免漏掉 break 语句造成程序错误。同时保持程序简洁。 对于多个分支相同处理的情况可以共用一个 break,但是要用注释加以说明。
正例:
switch (iMessage)
{
case SPAN_ON:
{
[处理语句]
break;
}
case SPAN_OFF:
{
[处理语句]
break;
}
default:
{
[处理语句]
}
}
规则 9.2.9 不可在 for 循环体内修改循环变量,防止 for 循环失去控制
规则 9.2.10 循环嵌套次数不大于 3 次
规则 9.2.11 do while 语句和 while 语句仅使用一个条件
说明:保持程序简洁。如果需要判断的条件较多,建议用临时布尔变量先计算是否满足条件。
正例:
BOOLEAN bCondition;
do {
........
bCondition = ((tAp[iPortNo].bStateAcpActivity != PASSIVE)
|| (tAp[iPortNo].bStateLacpActivity
!= PASSIVE)) && (abLacpEnabled[iPortNo])
&& (abPortEenabled[iPortNo])
} while (bCondition);
建议 9.2.1 switch 语句的分支比较多时,采用数据驱动方式
说明:当 switch 语句中 case 语句比较多时,会降低程序的效率。
正例:
extern void TurnState(void);
extern void SendMessage (void);
...
//函数指针
void (*StateChange[20])() = {TurnState, SendMessage, NULL, TurnState... };
...
if (StateChange[iState])
{
(*StateChange[iState])();
}
建议 9.2.2 如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面
说明:下面两个示例中,反例比正例多执行了 NUM -1 次逻辑判断。并且由于前者总要进行 逻辑判断,使得编译器不能对循环进行优化处理,降低了效率。如果 NUM 非常大,最好采 用正例的写法,可以提高效率。
const int NUM = 100000;
正例:
if (bCondition)
{
for (i = 0; i < NUM; i++)
{
DoSomething();
}
}
else
{
DoOtherthing();
}
反例:
for (i = 0; i < NUM; i++)
{
if (bCondition)
{
DoSomething();
}
else
{
DoOtherthing();
}
}
建议 9.2.3 for 语句的循环控制变量的取值采用“半开半闭区间”写法
说明:这样做更能适应 c 语言数组的特点,c 语言的下标属于一个“半开半闭区间”。
正例:
int aiScore[NUM];
...
for (i = 0; i < NUM; i++)
{
printf(“%d\n”,aiScore[i])
}
反例:
int aiScore[NUM];
...
for (i = 0; i <= NUM-1; i++)
{
printf(“%d\n”,aiScore[i]);
}
相比之下,正例的写法更加直观,尽管两者的功能是相同的。
建议 9.2.4 在进行’=='比较时,将常量或常数放在等号的左边
说明:可以采用这种方式,让编译器去发现错误。
正例:
if (NULL == pTail)
if (0 == iSum)
示例中有意把 p 和 NULL 颠倒。
编译器认为 if (pTail = NULL) 是合法的,但是会指出 if (NULL = pTail)是错误的,因为 NULL 不能被赋值。
规则9.2.12 表达式的值在标准所允许的任何运算次序下都应该是相同的。
说明:除了少数操作符(函数调用操作符()、&&、 |、?:和,(逗号))之外,子表达式所依据的运算次序是未指定的并会随时更改。 ==注意,运算次序的问题不能使用括号来解决,因为这不是优先级的问题。==将复合表达式分开写成若干个简单表达式,明确表达式的运算次序,就可以有效消除非预期的副作用。
1、 自增或自减操作符
示例:
x = b[i] + i++;
//b[i]的运算是先于还是后于i++的运算,表达式会产生不同的结果,把自增运算做为单独的语句,可以避免这个问题。
x = b[i] + i;
i ++;
2、 函数参数
说明:函数参数通常从右到左压栈,但函数参数的计算次序不一定与压栈次序相同。
示例:
x = func ( i++, i);
应该修改代码明确先计算第一个参数:
i++;
x = func (i, i);
3、 函数指针
说明:函数参数和函数自身地址的计算次序未定义。
示例:
p->task_start_fn(p++):
//求函数地址p与计算p++无关,结果是任意值。必须单独计算
p->task_start_fn(p);
p++;
4、 函数调用
示例:
int g_var = 0;
int fun1 ()
{
g_var += 10;
return g_var;
}
int fun2 ()
{
g_var += 100;
return g_var;
}
int x = fun1 () + fun2():
编泽器可能先计算fun1(),也可能先计算fun2(),由于x的结果依赖于函数fun1()/fun2()的计算次序 (fun1()/fun2()被调用时修改和使用了同一个全局变量),则上面的代码存在问题。
应该修改代码明确fun1/ fun2的计算次序:
int x = fun1 ():
x = x + fun2 ();
5、嵌套赋位语句
说明:表达式中嵌套的赋值可以产生附加的副作用。不给这种能导致对运算次序的依赖提供任何机会的最好做法是,不要在表达式中嵌套赋值语句。
示例:
x = y = y = z/3;
x = y = y++;
6、 volatile访问
说明:限定符volatile表示可能被其它途径更改的变量,例如硬件自动更新的寄存器。编译器不会优化对volatile变量的读取。
建议9.2.5 函数调用不要作为另一个函数的参数使用,否则对于代码的调试、阅读都不利。
说明:如下代码不合理,仅用于说明当函数作为参数时,由于参数栈次数不是代码可以控制的,可能造成未知的输出:
int g_var:
int funl ()
{
g_var += 10;
return g_var;
}
int fun2 () {
g_var += 100;
return g_var;
}
int main(int argc, char *argv[], char *envp[])
{
g_var = 1;
printf(*funcl: %d, func2: %d\n*, fun1(), fun2());
g_var = 1;
printf ("func2: %d, tunc 1: %d\n、fun2(), fun1 ());
}
上面的代码,使用断点调试起来也比较麻烦,阅读起来也不舒服,所以不要为了节约代码行,而写这种代码。
规则 9.2.13 赋值语句不要写在if等语句中,或者作为函数的参数使用。
说明:if语句中会根据条件依次判断,如果前一个条件已经可以判定整个条件,则后续条件语句不会再运行,所以可能导致期望的部分赋值没有得到运行。
示例:
int main(int argc, char *argv[], char *envp[])
{
int a = 0;
int b;
if ((a == 0) || ((b = fun1()) > 10))
{
printf("a:%d\n", a);
}
printf("b:%d\n",b);
}
建议9.2.6 用括号明确表达式的操作顺序,避免过分依赖默认优先级。
说明:使用括号强调所使用的操作符,防止因默认的优先级与设计思想不符而导致程序出错:同时使得代码更为淸晰可读,然而过多的括号会分散代码使其降低了可读性。下面是如何使用括号的建议。
1. 一元操作符,不需要使用括号
x =〜a; /* 一元操作符,不需要括号*/
x = -a; /* 一元操作符,不需要括号*/
2. 二元以上操作符,如果涉及多种操作符,则应该使用括号
x = a + b + c; /*操作符相同,不需要括号*/
x = f ( a + b, c ) /*操作符相同,不需要括号*/
if (a && b && c) /*操作符相同,不需要括号*/
x = (a * 3) + c + d: /*操作符不同,滿要括号*/
x=(a==b)? a: ( a b ) ; /*操作符不同,需要括号*/
3 .即使所有操作符都是相同的,如果涉及类型转换或者量级提升,也应该使用括号控制计算的次序以下代码将3个浮点数相加:
/*除了逗号(,),逻辑与(&&),逻辑或(||)之外,C标准没有规定同级操作符是从左还是从右开始计算,以上表达式存在种计算次序:f4 = (fl + f2) + f3或f4 = fl + (f2 + f3),浮点数计算过程中可能四舍五入,量级提升,计算次序的不同会导致f4的结果不同,以上表达式在不同编译器上的计算结果可能不一样,建议增加括号明确计算顺序*/
f4 = fl + f2 + f3;