運算子

May 20, 2022

程式目的簡單地說就是運算,除了運算還是運算,程式語言中提供運算功能的就是運算子(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重新計數,這時可以這麼撰寫:

var 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。對於類別型態宣告的參考名稱來說,兩個參考名稱使用 == 比較時,是比較兩個名稱是否參考至同一物件。

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

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

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

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

邏輯運算

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

var 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) {
        var 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或遞減1是很常見的運算,例如:

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

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

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

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

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

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

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

var i = 0;
var 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。再來看個例子:

var i = 0;
var 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
>»= a >»= b a = a >» b

分享到 LinkedIn 分享到 Facebook 分享到 Twitter