一、运算符
1.1 优先级和结合律
y = 6 * 12 + 5 * 20;
当运算符共享一个运算对象时, 优先级决定了求值顺序。 乘法的优先级最高,所以先进行乘法运算。即,先进行两个乘法运算6 * 12和5 * 20, 再进行加法运算,最后进行赋值运算。
但是, 优先级并未规定到底先进行哪一个乘法。 C 语言把主动权留给语言的实现者, 根据不同的硬件来决定先计算前者还是后者。
当共享一个运算对象时,两边的运算符的优先级相同,结合律决定求值顺序。
例如, 在表达式12 / 3 *2中, 乘和除运算符的优先级相同, 共享运算对象3, 乘和除的结合律方向都是从左往右,因此, 结合律在这种情况起作用。
计算a=1×2+3×4+5×6
C语言通过优先级和结合律明确求值顺序,如纵向所示。对于横向三个乘法的子表达式,C语言并没有明确规定先计算哪个乘法。
小结:
1、表达式中,先看优先级,如果优先级相同再看结合律,最后决定求值顺序。
2、当共享一个运算对象时,用优先级和结合律决定执行顺序,当共享一个运算符时(即双目运算符,除逗号运算符、逻辑与运算符、逻辑或运算符外),编译器随机选择运算对象。
1.2 运算符优先级和结合律一览表
优先级 | 运算符 | 名称或含义 | 使用方式 | 结合方向 | 说明 |
---|---|---|---|---|---|
1 | [] | 数组下标 | 数组名[常量表达式] | 从左往右 | |
() | 圆括号 | (表达式) 函数名(形参表) | |||
. | 成员选择(对象) | 对象.成员名 | |||
-> | 成员选择(指针) | 对象指针->成员名 | |||
++(后缀) | 自增运算符 | 变量++ | |||
--(后缀) | 自减运算符 | 变量-- | |||
2 | - | 负号运算符 | -表达式 | 从右往左 | 单目运算符 |
++(前缀) | 自增运算符 | ++变量名 | 单目运算符 | ||
--(前缀) | 自减运算符 | --变量名 | 单目运算符 | ||
* | 取值运算符 | *指针变量 | 单目运算符 | ||
& | 取地址运算符 | &变量名 | 单目运算符 | ||
! | 逻辑非运算符 | !表达式 | 单目运算符 | ||
~ | 按位取反运算符 | ~表达式 | 单目运算符 | ||
sizeof | 长度运算符 | sizeof(表达式) | |||
3 | (类型) | 强制类型转换 | (数据类型)表达式 | 从右往左 | |
4 | / | 除 | 表达式 / 表达式 | 从左往右 | 双目运算符 |
* | 乘 | 表达式 * 表达式 | 双目运算符 | ||
% | 余数(取模) | 整型表达式 % 整型表达式 | 双目运算符 | ||
5 | + | 加 | 表达式 + 表达式 | 从左往右 | 双目运算符 |
- | 减 | 表达式 - 表达式 | 双目运算符 | ||
6 | << | 左移 | 变量 << 表达式 | 从左往右 | 双目运算符 |
>> | 右移 | 变量 >> 表达式 | 双目运算符 | ||
7 | > | 大于 | 表达式 > 表达式 | 从左往右 | 双目运算符 |
>= | 大于等于 | 表达式 >= 表达式 | 双目运算符 | ||
< | 小于 | 表达式<表达式 | 双目运算符 | ||
<= | 小于等于 | 表达式 <= 表达式 | 双目运算符 | ||
8 | == | 等于 | 表达式 == 表达式 | 从左往右 | 双目运算符 |
!= | 不等于 | 表达式 != 表达式 | 双目运算符 | ||
9 | & | 按位与 | 表达式 & 表达式 | 从左往右 | 双目运算符 |
10 | ^ | 按位异或 | 表达式 ^ 表达式 | 从左往右 | 双目运算符 |
11 | | | 按位或 | 表达式 | 表达式 | 从左往右 | 双目运算符 |
12 | && | 逻辑与 | 表达式 && 表达式 | 从左往右 | 双目运算符 |
13 | || | 逻辑或 | 表达式 || 表达式 | 从左往右 | 双目运算符 |
14 | ?: | 条件运算符 | 表达式1 ? 表达式2 : 表达式3 | 从右往左 | 三目运算符 |
15 | = | 赋值运算符 | 变量 = 表达式 | 从右往左 | |
/= | 除后赋值 | 变量 /= 表达式 | |||
*= | 乘后赋值 | 变量 *= 表达式 | |||
%= | 取模后赋值 | 变量 %= 表达式 | |||
+= | 加后赋值 | 变量 += 表达式 | |||
-= | 减后赋值 | 变量 -= 表达式 | |||
<<= | 左移后赋值 | 变量 <<= 表达式 | |||
>>= | 右移后赋值 | 变量 >>= 表达式 | |||
&= | 按位与后赋值 | 变量 &= 表达式 | |||
^= | 按位异或后赋值 | 变量 ^= 表达式 | |||
|= | 按位或后赋值 | 变量 |= 表达式 | |||
16 | , | 逗号运算符 | 表达式,表达式,… | 从左往右 |
对于优先级:算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符。逻辑运算符中“逻辑非 !”除外。
二、关于++的注意事项
2.1 为什么++的代码效率更高?
递增运算符具有代码紧凑、效率高等优点。递增运算符之所以效率高,是因为它和实际的机器语言指令很相似,所以一条C语句转换成的机器码相对较少。尽管如此, 随着商家推出的C编译器越来越智能, 这一优势可能会消失。 一个智能的编译器可以把x = x + 1当作++x对待。
x=x+1, x+=1, x++哪个效率更高?
说法1:
x=x+1最低,因为它的执行过程如下:
(1)读取右x的地址。
(2)x+1
(3)读取左x的地址。
(4)将右值传给左边的x(编译器并不认为左右x的地址相同)。
x+=1其次,其执行过程如下:
(1)读取右x的地址。
(2)x自增1。
(3)将得到的值传给x(因为x的地址已经读出)。
x++效率最高,其执行过程如下:
(1)读取右x的地址。
(2)x自增1。
说法2:
这3条语句都应该对应两次访问内存操作,一次加法操作,所以效率应该一样。
2.2 ++的优先级
++出现在其作用的变量前面,这是前缀模式; ++出现在其作用的变量后面, 这是后缀模式。
++在使用时,前缀和后缀的优先级是不同的。
后缀++的优先级 > () > 前缀++的优先级
++的错误用法:(x+y)++;
因为递增和递减运算符只能影响一个变量(或者, 更普遍地说, 只能影响一个可修改的左值),而组合x*y本身不是可修改的左值。
2.3 一个语句中用太多递增运算符
情况一:
printf("%10d %10d\n", num, num*num++);
理想中的:打印num, 然后计算num*num得到平方值, 最后
把num递增1。
但事实上, 修改后的程序只能在某些系统上能正常运行。
该程序的问题是:当 printf()获取待打印的值时,可能先对最后一个参数求值, 这样在获取其他参数的值之前就递增了num。
所以, 本应打印:5 25
却打印成:6 25
它甚至可能从右往左执行, 对最右边的num(++作用的num) 使用5, 对
第2个num和最左边的num使用6, 结果打印出:6 30
在C语言中, 编译器可以自行选择先对函数中的哪个参数求值。 这样做提高了编译器的效率, 但是如果在函数的参数中使用了递增运算符, 就会有一些问题。
相似的例子:
printf("%10d %10d\n", num, num++);
printf("%10d %10d\n", num, num=1);
情况二:
ans = num/2 + 5*(1 + num++);
同样, 该语句的问题是: 编译器可能不会按预想的顺序来执行。
你可能认为, 先计算第1项(num/2) , 接着计算第2项(5*(1 + num++))
但是,编译器可能先计算第2项, 递增num, 然后在num/2中使用num递增后的新值。 因此, 无法保证编译器到底先计算哪一项。
情况三:
n = 3;
y = n++ + n++;
可以肯定的是,执行完这两条语句后,n的值会比旧值大2。
但是,y的值不确定。
1、编译器可以使用n的旧值(3)两次,然后把n递增1两次,这使得y的值为6,n的值为5。
2、编译器使用n的旧值(3)一次,立即递增n,再对表达式中的第2个n使用递增后的新值,然后再递增n,这使得 y 的值为 7,n 的值为5。
遵循以下规则, 很容易避免类似的问题:
1、如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增或递减运算符;
2、如果一个变量多次出现在一个表达式中,不要对该变量使用递增或递减运算符。
小结:
(表达式1)+(表达式2)
例如:
int a = 1;
int b = 2;
int c = 3;
int d = 4;
(a + b)+ (c + d);
这种表达式1的优先级和表达式2的优先级相同,编译器对于先计算哪一个是随机的,如果表达式1和表达式2中的内容没有联系,那么无论是哪个先计算,结果都是正确的。
如果表达式1和表达式2存在共同的变量且该变量有赋值操作(++或=),那么不清楚哪个表达式先计算,不清楚什么时候赋值,最终造成计算结果的不确定。
针对这种情况下,有些编译器会报警告:
warning: operation on ‘i’ may be undefined [-Wsequence-point]
三、参考资料
[1]: C Primer Plus 第六版
[2]: x=x+1,x+=1,x++效率的两种说法