《Java解惑》系列——01表达式之谜——谜题07:互换内容

知识点:

在单个的表达式中不要对相同的变量赋值两次。表达式如果包
含对相同变量的多次赋值,就会引起混乱,并且很少能够执行你希望的操作。


问题:

下面的程序会打印社么??

// 互换内容
// 在单个的表达式中不要对相同的变量赋值两次
public class CleverSwap{
    public static void main(String[] args){
        int x = 1984; // (0x7c0)
        int y = 2001; // (0x7d1)
        x^= y^= x^= y;
        System.out.println("x= " + x + "; y= " + y);
    }
}

// 期望结果:x=2001; y=1984
// 实际结果:x=0; y=1984


结果是不是出乎大家的意料呢?的确如此。
产生这个结果的原因:

就像其名称所暗示的,这个程序应该交换变量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;
这个惯用法曾经在C编程语言中被使用过,并进一步被构建到了C++中,但是它
并不保证在二者中都可以正确运行。但是有一点是肯定的,那就是它在 Java 中
肯定是不能正确运行的。  
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 ;



解决方法:

要避免所谓聪明的编程技巧。它们都是易于产生 bug的,很难以维护,
并且运行速度经常是比它们所替代掉的简单直观的代码要慢。

x = x ^ y;
y = y ^ x;
x = y ^ x;


总结:


这个教训很简单:在单个的表达式中不要对相同的变量赋值两次。表达式如果包
含对相同变量的多次赋值,就会引起混乱,并且很少能够执行你希望的操作。即
使对多个变量进行赋值也很容易出错。更一般地讲,要避免所谓聪明的编程技巧。
它们都是易于产生 bug的,很难以维护,并且运行速度经常是比它们所替代掉的
简单直观的代码要慢。  
语言设计者可能会考虑禁止在一个表达式中对相同的变量多次赋值,但是在一般
的情况下,强制执行这条禁令会因为别名机制的存在而显得很不灵活。例如,请
考虑表达式 x = a[i]++ - a[j]++,它是否递增了相同的变量两次呢?这取决于
在表达式被计算时 i 和j的值,并且编译器通常是无法确定这一点。 





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值