标题: 谈论复合的异或赋值操作符混淆 [打印本页] 作者: 王海彬 时间: 2015-9-14 23:58 标题: 谈论复合的异或赋值操作符混淆 下面的程序使用了复合的异或赋值操作符,它所展示的技术是一种编程习俗。那么它会打印出什么呢?
public class CleverSwap{
public static void main(String[] args){
int x = 1984;
int y = 2001;
x^= y^= x^= y;
System.out.println("x= " + x + "; y= " + y);
}
}
就像其名称所暗示的,这个程序应该交换变量x 和y 的值。如果你运行它,就会发现很悲惨,它失败了,打印的是
x = 0; y = 1984。
交换两个变量的最显而易见的方式是使用一个临时变量:
int tmp = x;
x = y;
y = tmp;
很久以前,当中央处理器只有少数寄存器时,人们发现可以通过利用异或操作符(^)的属性(x ^ y ^ x) == y 来避免使用临时变量:
x = x ^ y;
y = y ^ x;
x = y ^ x;
Java 语言规范描述到:操作符的操作数是从左向右求值的。为了求表达式 x ^=expr 的值,x 的值是在计算expr 之前被提取的,并且这两个值的异或结果被赋
给变量x。在CleverSwap 程序中,变量x 的值被提取了两次——每次在表达式中出现时都提取一次——但是两次提取都发生在所有的赋值操作之前。
下面的代码段详细地描述了将互换惯用法分解开之后的行为,并且解释了为什么产生的是我们所看到的输出:
// Java 中x^= y^= x^= y 的实际行为
int tmp1 = x ; // x 在表达式中第一次出现
int tmp2 = y ; // y 的第一次出现
int tmp3 = x ^ y ; // 计算x ^ y
x = tmp3 ; // 最后一个赋值:存储x ^ y 到 x
y = tmp2 ^ tmp3 ; // 第二个赋值:存储最初的x 值到y 中
x = tmp1 ^ y ; // 第一个赋值:存储0 到x 中
在C 和C++中,并没有指定表达式的计算顺序。当编译表达式x ^= expr 时,许多C 和C++编译器都是在计算expr 之后才提取x 的值的,这就使得上述的惯用
法可以正常运转。尽管它可以正常运转,但是它仍然违背了C/C++有关不能在两个连续的序列点之间重复修改变量的规则。因此,这个惯用法的行为在C 和C++中也没有明确定义。
为了看重其价值,我们还是可以写出不用临时变量就可以互换两个变量内容的Java 表达式的。
// 杀鸡用牛刀的做法,千万不要这么做!
y = (x^= (y^= x))^ y ;