运算符是基本的语言元素,虽然接触编程语言的年岁不短了,但有时面对着一些表达式,仍不能立马确认运算顺序。我相信很多人也有我这方面的疑惑,今天就把一些“设计错误”的运算符拉出来溜溜。
& 和 |
看下面的代码片段,变量a
的值是多少呢?
int a = 1 | 0 == 1 & 0;
对于表达式1 | 0 == 1 & 0
,包含三个运算符,已知&
优先级高于|
,并且这些位操作符要么高于==
,要么低于==
,分两种情况讨论:
- 位操作符优先级高于
==
等价于(1 | 0) == (1 & 0)
,计算的结果为0
。 -
==
优先级高于位操作符
等价于1 | ((0 == 1) & 0))
,计算的结果为1
。
将上面的代码运行打印会得到a
的值为1。这说明==
优先级高于位操作符。这或许是我最迷惑的运算符规则了,在我看来位操作符本质上是算术运算符,都是产生一个数,应该跟算术运算符的规则大体一致。故而,我认为第一种更合理。
是不是设计的错误呢,终于在《C专家编程》这本书里找到了答案,即下节。
'And' and 'AND' or 'Or' or 'OR'
来自C语言的创造者 Dennis Ritchie 的解释:
&&
、||
操作符与==
操作符的优先级关系问题是这样产生的。在 C 的早期,&
和&&
合用同一个操作符,|
和||
也是如此(明白吗?)。 它继承了 B 和 BCPL 中的概念“真值上下文”。就是在if
和while
等后面需要一个布尔值的时候,&
和丨
就被翻译成现在的&&
和||
。如果它们在一般的表达式里,就被解释成位操作符,也就是现在的样子。这个机制操作起来没有问题,但理解起来很困难(在真值上下文里,存在“顶层运算符”的概念)。
&
和|
的优先级跟现在一样。最初,在 Alan Snyder 的催促下,我在C语言中加入了&&
和||
操作符。这就成功地把位运算的概念和布尔运算的概念分了开来。然而,我心怀不安,因为我意识到了优先级的问题。例如,在现存的大量程序中,存在诸如这样的表达式:if(a == b & c == d)
。事后回想,如果我们一开始就改变优先级,让
&
的优先级高于==
,在逻辑上可能更清晰一些。但是,从安全的角度出度,只能做到把&
和&&
分开这个程度,无法在两者的优先级之间再插入其他的操作符(否則的话,现有的大量代码都有可能出问題)。
从上面的描述可以看出,最终设计的结果在于代码兼容性上的考量。Java
和 C#
的操作符优先级跟C/C++
大同小异,这可以认为是另一个层次的兼容:程序员的编码习惯。
C++其他不合理的操作符
在《C专家编程》这本书里还列出了其他错误的操作符,这里一并列出:
优先级问题 | 表达式 | 人们可能误以为的结果 | 实际结果 |
---|---|---|---|
.的优先级高于*。->优先级用于消除这个问题 | *p.f | p所指对象的字段f。 (*p).f | 对p取f偏移,作为指针,然后进行间接引用操作。*(p.f) |
[]高于* | int *ap[] | ap是个指向int数组的指针。int(*ap)[] | ap是个元素为int指针的数组。int *(ap[]) |
函数()高于* | int *fp() | fp是个函数指针,所指函数返回int。int (*fp)() | fp是个函数,返回int。int(fp()) |
==和!=高于位操作符 | (val & mask != 0) | (val & mask)!= 0 | val & (mask != 0) |
==和!=高于赋值符 | c=getchar()!=EOF | (c=getchar())!=EOF | c=(getchar()!=EOF) |
算术运算符高于移位运算符 | msb << 4 + lsb | (msb << 4) + lsb | msb << (4 + lsb) |
逗号运算符在所有运算符中优先级最低 | i=1,2 | i=(1,2) | (i=1),2 |
这些不合理的操作符毕竟占了少数,遇到它们完全可以使用括号保证可靠性,同时不会影响代码的可读性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。