第九章、表达式与语句

第九章、表达式与语句

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;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值