天天看點

c++的uint8不指派_技 術 | 你不知道的Java—06.互換内容

c++的uint8不指派_技 術 | 你不知道的Java—06.互換内容

下面的程式使用了複合的異或指派操作符,它所展示的技術是一種程式設計習俗。那麼它會列印出什麼呢?

c++的uint8不指派_技 術 | 你不知道的Java—06.互換内容

就像其名稱所暗示的,這個程式應該交換變量 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 = a[i]++ - a[j]++,它是否遞增了相同的變量兩次呢?這取決于在表達式被計算時 i 和 j 的值,并且編譯器通常是無法确定這一點。