运算符的优先级和结合性有明确的规定,但是,除少数例外情况外,表达式的求值次序没有定义,甚至某些有副作用的子表达式也没有定义。
也就是说运算符的定义保证了其操作数按某一特定的顺序求值,否则具体实现可以自由选择任意求值顺序,甚至可以交换求值次序。但是每个运算符
将其操作数生成的值结合起来的方式与表达式的语法分析方式是兼容的。(该规则废除了原先的一个规则:当表达式中的运算符在数学上满足交换律
和结合律时,可以对表达式重新排序)
以下按优先级从高到底:
1.指针生成;
2.初等表达式;
3.后缀表达式;
4.一元运算符(+ - * & ! ~ sizeof);
5.强制类型转换;
6.乘法类运算符(* % /);
7.加法类运算符( + - );
8.移位运算符(<< >>);
9.关系运算符(< > <= >= );
10.相等类运算符( == !=);
11.按位与运算符(&);
12.按位异或运算符( ^);
13.按位或运算符(|);
14.逻辑与运算符(&&);
15.逻辑或运算符(||);
16.条件运算符(a?b:c);
17.赋值运算符(= += -= *= /= %= <<= >>= &= |= ^=);
18.逗号运算符(,);
求值顺序由左向右,并且左表达式的值被丢弃
19.常量表达式;
注:
运算符&&遵循从左到右的结合性:首先计算第一个操作数,包括所有可能的副作用;如果为0,则整个表达式的值为0;否则,计算右操作数,如果为0,则整个表达式
的值为0,否则为1
运算符|| 同&& 先计算第一个值如能判断结果则后面的值不再计算,否则计算第二个操作数
如 a=1; b=0; if(a > b || (a=2)>0)的值为 1,且此时 a 的值为1,而不是 2
其他简单例子:
1)a<b==c<d; 只要a<b和c<d具有相同的值,其值总为1
2)a=1; b=2; c=1; a*b+c?1:0 的值为 1. 条件运算符优先级较低 所以相当于(a*b+c)?1:0 而非1*2+1=3
3)b=2,c=7,d=5; a1=(++b,c--,d+3); a2=++b,c--,d+3;
对于给a1赋值的代码,有三个表达式,用逗号分开,所以最终的值应该是最后一个表达式的值,也就是(d+3)的值,为8,所以a1的值为8
对于给a2赋值的代码,也是有三个表达式,这时的三个表达式为a2=++b、c--、d+3,(这是因为赋值运算符比逗号运算符优先级高)
注:a2行代码不能编译通过,仅作举例说明问题、
所以最终表达式的值虽然也为8,但a2=4(a1行代码运算完时,b=3,a2行代码运行时,b的值为4)。
作者说:对于运算符的优先级不确定时,请使用括号。不仅不会出错,也有利于代码的易读性。
附图:
运算符的优先级和结合性
注:一元运算符 正号+ 符号- 取址& 指针* 比二元运算符 + - *的优先级高。
另注:
C语言中没有指定有意运算符中多个操作数的计算顺序,如 a = f() + g(); 中,f()可以在g()之前计算,也可以在g()
之后计算。因此,如果函数f或g改变了另一函数的使用的变量,那么a的结果可能会依赖于两个函数的计算顺序。
ANSI C 明确规定所有对参数的副作用都必须在函数调用之前生效。
类似的,c中也没有指定个参数的求值顺序。因此,形如
printf("%d %d ",++n,power(2,n)); 的语句是错误的。在不同的编译器中可能产生不同的结果。应改为:
++n; printf("%d %d ",n,power(2,n));
同样形如 a[i] = i++;数组下标是引用旧值还是引用新值?在不同的编译器中可能产生不同的结果。、
所以形如 i=1; (i++)+(i++)+(i++)+(i++)这样的代码是千万要不得的。(顺便说一下,在clion中运行结果是10.。。不推荐浪费时间在这类问题上!)