運算子


程式目的簡單地說就是運算,除了運算還是運算,程式語言中提供運算功能的就是運算子(Operator)。

算術運算

與算術相關的運算子+、-、*、/,也就是加、減、乘、除這類運算子,另外%,稱為模數運算子或餘除運算子。算術運算子使用上與你學過的加減乘除一樣,也是先乘除後加減。例如以下程式碼片段會在文字模式下顯示7:

System.out.println(1 + 2 * 3);

以下程式碼會顯示的是6:

System.out.println(2 + 2 + 8 / 4);

如果你想要的是2 + 2 + 8加總後,再除以4,請加上括號表示運算先後順序。例如以下程式碼顯示的是3:

System.out.println((2 + 2 + 8) / 4);

%運算子計算的結果是除法後的餘數,例如10 % 3會得到餘數1;一個使用%的例子是數字循環,假設有個立方體要進行360度旋轉,每次要在角度上加1,而360度後必須復歸為0重新計數,這時可以這麼撰寫:

int count = 0;
....
count = (count + 1) % 360;

在運算子的兩邊各留一個空白,這樣比較容易閱讀。


比較、條件運算

數學上有大於、等於、小於的比較運算,Java中也提供了這些運算子,這些運算子稱之為比較運算子(Comparison operator),它們有大於(>)、不小於(>=)、小於(<)、不大於(<=)、等於(==)以及不等於(!=),比較條件成立時以boolean型態true表示,比較條件不成立時以false表示。以下程式片段示範了幾個比較運算的使用:

package cc.openhome;

public class Comparison {
    public static void main(String[] args) {
        System.out.printf("10 >  5 結果 %b%n", 10 > 5); 
        System.out.printf("10 >= 5 結果 %b%n", 10 >= 5); 
        System.out.printf("10 <  5 結果 %b%n", 10 < 5); 
        System.out.printf("10 <= 5 結果 %b%n", 10 <= 5); 
        System.out.printf("10 == 5 結果 %b%n", 10 == 5); 
        System.out.printf("10 != 5 結果 %b%n", 10 != 5);
    }
}

程式的執行如下所示:

10 >  5 結果 true
10 >= 5 結果 true
10 <  5 結果 false
10 <= 5 結果 false
10 == 5 結果 false
10 != 5 結果 true

==是兩個連續的=組成,而不是一個=,一個=是指定運算,這點必須特別注意。例如若變數xy要比較是否相等,應該是寫成x == y,而不是寫成x = y,後者作用是將y的值指定給x,而不是比較運算xy是否相等。對於類別型態宣告的參考名稱來說,兩個參考名稱使用==比較時,是比較兩個名稱是否參考至同一物件,之後介紹物件會再詳細介紹。

Java有個條件運算子(Conditional operator),使用方式如下:

條件式 ? 成立傳回值 : 失敗傳回值

條件運算子傳回值依條件式結果而定,如果條件式結果為true,則傳回:前的值,若為false,則傳回:後的值。例如,若scoreint宣告,儲存了使用者輸入的學生成績,以下程式片段可用來判斷學生是否及格:

System.out.printf("該生是否及格?%c%n", score >= 60 ? '是' : '否');

條件運算子使用適當的話可以少寫幾句程式碼。例如,若numberint宣告,儲存使用者輸入的數字,則以下程式片段可判斷奇數或偶數:

System.out.printf("是否為偶數?%c%n", (number % 2 == 0) ? '是' : '否');

同樣的程式片段,若改用本章稍後要介紹的if..else語法,則要如下撰寫:

if(number % 2 == 0) {
    System.out.println("是否為偶數?是");
}
else {
    System.out.println("是否為偶數?否");
}

邏輯運算
在邏輯上有 所謂的「且」(AND)、「或」(OR)與「反相」(NOT),在 Java中也提供對應的邏輯運算子(Logical operator),分別為&&(AND)、||(OR)及!(NOT)。看看以下的程式片段會輸出什麼結果?

int number = 75;
System.out.println(number > 70 && number < 80);
System.out.println(number > 80 || number < 75);
System.out.println(!(number > 80 || number < 75));

三段陳述句分別會輸出truefalsetrue三種結果,分別表示number大於70且小於80為真、number大於80或小於75為假、number大於80或小於75的相反為真。

&&||有所謂捷徑運算(Short-Circuit Evaluation)。因為AND只要其中一個為假,就可以判定結果為假,所以對&&來說,只要左運算元(Operand)評估為false,就會直接傳回false,不會再去運算右運算元。因為OR只要其中一個為真,就可以判定結果為真,所以對||來說,只要左運算元評估為true,就會直接傳回true,就不會再去運算右運算元。

來舉個運用捷徑運算的例子,在Java中兩個整數相除,若除數為0會發生ArithmeticException,代表除0的錯誤,以下運用&&捷徑運算避免了這個問題:

if(b != 0 && a / b > 5) {
    // 作一些事...
}

在這個程式片段中,變數ab都是int型態,如果b為0的話,&&左邊運算元結果就是false,直接判斷整個&&的結果應是false,不用再去評估右運算元,從而避免了a /bb等於0時的除零錯誤。

位元運算
在數位設計上有AND、OR、NOT、XOR與補數運算,在Java中提供對應的運位元運算子(Bitwise Operator),分別是&(AND)、|(OR)、^(XOR)與~(補數)。如果不會基本位元運算,可以從以下範例瞭解各個位元運算結果:
package cc.openhome;

public class Bitwise {
    public static void main(String[] args) {
        System.out.println("AND運算:"); 
        System.out.printf("0 AND 0 %5d%n", 0 & 0);
        System.out.printf("0 AND 1 %5d%n", 0 & 1); 
        System.out.printf("1 AND 0 %5d%n", 1 & 0); 
        System.out.printf("1 AND 1 %5d%n", 1 & 1); 

        System.out.println("\nOR運算:"); 
        System.out.printf("0 OR 0 %6d%n", 0 | 0); 
        System.out.printf("0 OR 1 %6d%n", 0 | 1); 
        System.out.printf("1 OR 0 %6d%n", 1 | 0); 
        System.out.printf("1 OR 1 %6d%n", 1 | 1); 

        System.out.println("\nXOR運算:"); 
        System.out.printf("0 XOR 0 %5d%n", 0 ^ 0); 
        System.out.printf("0 XOR 1 %5d%n", 0 ^ 1); 
        System.out.printf("1 XOR 0 %5d%n", 1 ^ 0); 
        System.out.printf("1 XOR 1 %5d%n", 1 ^ 1); 
    }
}

執行結果就是各個位元運算的結果:
AND運算:
0 AND 0     0
0 AND 1     0
1 AND 0     0
1 AND 1     1

OR運算:
0 OR 0      0
0 OR 1      1
1 OR 0      1
1 OR 1      1

XOR運算:
0 XOR 0     0
0 XOR 1     1
1 XOR 0     1
1 XOR 1     0

位元運算是逐位元運算,例如10010001與01000001作AND運算,是一個一個位元對應運算,答案就是00000001。補數運算是將所有位元0變1,1變0。例如00000001經補數運算就會變為11111110。例如:

byte number = 0;
System.out.println(~number);

上面的程式片段會顯示-1,因為byte佔記憶體一個位元組,number儲存的0在記憶體中是的位元00000000,經補數運算就變成11111111,這個數在電腦中用整數表示則是-1。

邏輯運算子與位元運算子也是很常被混淆的,像是&&&|||,初學時可得多注意。

在位元運算上,Java還有左移(<<)與右移(>>)兩個運算子,左移運算子會將所有位元往左移指定位數,左邊被擠出去的位元會被丟棄,而右邊補上0;右移運算則是相反,會將所有位元往右移指定位數,右邊被擠出去的位元會被丟棄,至於最左邊補上原來的位元,如果左邊原來是0就補0,1就補1。還有個>>>運算子,這個運算子在右移後,最左邊一定是補0。

使用左移運算來作簡單的2次方運算示範:
package cc.openhome;

public class Shift {
    public static void main(String[] args) {
        int number = 1; 
        System.out.printf( "2 的 0 次方: %d%n", number); 
        System.out.printf( "2 的 1 次方: %d%n", number << 1); 
        System.out.printf( "2 的 2 次方: %d%n", number << 2); 
        System.out.printf( "2 的 3 次方: %d%n", number << 3); 
    }
}

執行結果:

2 的 0 次方: 1
2 的 1 次方: 2
2 的 2 次方: 4
2 的 3 次方: 8

實際來左移看看就知道為何可以如此作次方運算了: 

00000001 -> 1
00000010 -> 2
00000100 -> 4
00001000 -> 8

位元運算你可能不常用,通常應用於影像處理、文字編碼等場合,例如 亂碼 1/2 就有一些位元運算的例子。


遞增、遞減運算

在程式中對變數遞增1或遞減1是很常見的運算,例如:

int i = 0;
i = i + 1;
System.out.println(i);
i = i - 1;
System.out.println(i);

這個程式片段會分別顯示出1與0兩個數,你可以使用遞增、遞減運算子來撰寫程式:

int i = 0;
i++;
System.out.println(i);
i--;
System.out.println(i);

那麼哪個寫法比較好呢?就簡潔度而言,使用++--的寫法比較好,就效率而言,其實沒差,因為如果你寫i = i + 1,編譯器會自動幫你改成i++,同樣地,如果你寫i = i – 1,編譯器會自動幫你改為i--

上面的程式片段還可以再簡潔一些:

int i = 0;
System.out.println(++i);
System.out.println(--i);

可以將++--運算子撰寫在變數的前或後,不過兩種寫法有差別,將++--運算子寫在變數前,表示先將變數值加或減1,然後再傳回變數值,將++--運算子寫在變數後,表示先傳回變數值,然後再對變數加或減1。例如:

int i = 0;
int number = 0;
number = ++i;   // 結果相當於i = i + 1; number = i;
System.out.println(number);
number = --i;    // 結果相當於i = i - 1; number = i;
System.out.println(number);

在這個程式片段中,number的值會前後分別顯示為1與0。再來看個例子:

int i = 0;
int number = 0;
number = i++;    // 相當於number = i; i = i + 1;
System.out.println(number);
number = i--;     // 相當於 number = i; i = i - 1;
System.out.println(number);

在這個程式片段中,number的值會前後分別顯示為0與1。


指定運算

到目前為止只看過一個指定運算子,也就是=這個運算子,事實上指定運算子還有以下幾個:

指定運算子
範例
結果
+= a += b a = a + b
-= a -= b a = a - b
*= a *= b a = a * b
/= a /= b a = a / b
%= a %= b a = a % b
&= a &= b a = a & b
|= a |= b a = a | b
^= a ^= b a = a ^ b
<<= a <<= b a = a << b
>>= a >>= b a = a >> b