Go 語言中的運算子,大致上與 C 語系的語言中提供的運算子差不多,其中 &、* 也用來作為指標(Pointer)運算子。
算術運算子
算術運算子作用於數值,產生與第一個運算元相同型態的結果。+、-、*、/ 四個運算子,可用於整數、浮點數與複數;+ 也用於字串串接;% 餘除運算子,只用於整數,&、|、^、&^ 位元運算子只用於整數,<<、>> 位移運算子只用於整數。
+、-、*、/ 使用上應該沒什麼問題,主要就是注意運算的順序是先乘除後加減,必要時使用括號讓順序清楚,例如:
package main
import "fmt"
func main() {
fmt.Println(1 + 2*3) // 7
fmt.Println(2 + 2 + 8/4) // 6
fmt.Println((2 + 2 + 8) / 4) // 3
fmt.Println(10 % 3) // 1
}
%運算子計算的結果是除法後的餘數,例如上頭 10 % 3 會得到餘數 1。
對於遞增與遞減 1 的操作,Go 可以使用 ++ 與 -- 的操作,不過,++ 與 -- 只能置於變數後方,而且是個陳述,因此,對於 i := 1,你可以在一行陳述中寫 i++ 或 i--,不過,不能寫 fmt.Println(i++),這樣就能避免是要先傳回 i 值再遞增 i,還是先遞增 i 再傳回 1 的問題。
在二進位運算上有 AND、OR、XOR 等運算,底下是 Go 中的一些例子:
package main
import "fmt"
func main() {
fmt.Println("AND運算:")
fmt.Printf("0 AND 0 %5d\n", 0&1)
fmt.Printf("0 AND 1 %5d\n", 0&1)
fmt.Printf("1 AND 0 %5d\n", 1&0)
fmt.Printf("1 AND 1 %5d\n", 1&1)
fmt.Println("\nOR運算:")
fmt.Printf("0 OR 0 %6d\n", 0|0)
fmt.Printf("0 OR 1 %6d\n", 0|1)
fmt.Printf("1 OR 0 %6d\n", 1|0)
fmt.Printf("1 OR 1 %6d\n", 1|1)
fmt.Println("\nXOR運算:")
fmt.Printf("0 XOR 0 %5d\n", 0^0)
fmt.Printf("0 XOR 1 %5d\n", 0^1)
fmt.Printf("1 XOR 0 %5d\n", 1^0)
fmt.Printf("1 XOR 1 %5d\n", 1^1)
fmt.Println("\nAND NOT運算:")
fmt.Printf("0 AND NOT 0 %5d\n", 0&^0)
fmt.Printf("0 AND NOT 1 %5d\n", 0&^1)
fmt.Printf("1 AND NOT 0 %5d\n", 1&^0)
fmt.Printf("1 AND NOT 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
AND NOT運算:
0 AND NOT 0 0
0 AND NOT 1 0
1 AND NOT 0 1
1 AND NOT 1 0
位元運算是逐位元運算,例如 10010001 與 01000001 作 AND 運算,是一個一個位元對應運算,答案就是 00000001。補數運算是將所有位元 0 變 1,1 變 0。例如 00000001 經補數運算就會變為 11111110。Go 的補數運算子是 ^,例如:
package main
import "fmt"
func main() {
number := 0
fmt.Println(^number) // -1
}
上面的程式片段會顯示 -1,因為 number 在記憶體中全部位元都是 0,經補數運算全部位元就都變成 1,這個數在電腦中用整數表示則是 -1。
<< 左移運算子會將所有位元往左移指定位數,左邊被擠出去的位元會被丟棄,而右邊補上 0;>> 右移運算則是相反,會將所有位元往右移指定位數,右邊被擠出去的位元會被丟棄,至於最左邊補上原來的位元,如果左邊原來是 0 就補0,1 就補 1。
package main
import "fmt"
func main() {
number := 1
fmt.Printf("2 的 0 次方: %d\n", number) // 1
fmt.Printf("2 的 1 次方: %d\n", number << 1) // 2
fmt.Printf("2 的 2 次方: %d\n", number << 2) // 4
fmt.Printf("2 的 3 次方: %d\n", number << 3) // 8
}
實際來左移看看就知道為何可以如此作次方運算了:
00000001 -> 1
00000010 -> 2
00000100 -> 4
00001000 -> 8
對於一個算術運算 x = x op y,可以寫成 x op= y,op 是指算術運算子,例如 x = x + y,可以寫成 x += y,這也就是所謂的指定運算子。
比較運算
數學上有大於、等於、小於的比較運算,Go 中也提供了這些運算子,它們有大於(>)、不小於(>=)、小於(<)、不大於(<=)、等於(==)以及不等於(!=),比較條件成立時用 true 表示,比較條件不成立用 false 表示。以下程式片段示範了幾個比較運算的使用:
package main
import "fmt"
func main() {
fmt.Printf("10 > 5 結果 %t\n", 10 > 5) // true
fmt.Printf("10 >= 5 結果 %t\n", 10 >= 5) // true
fmt.Printf("10 < 5 結果 %t\n", 10 < 5) // false
fmt.Printf("10 <= 5 結果 %t\n", 10 <= 5) // false
fmt.Printf("10 == 5 結果 %t\n", 10 == 5) // false
fmt.Printf("10 != 5 結果 %t\n", 10 != 5) // true
}
== 與 != 只能用在 comparable 的運算元上,這有一套嚴格規則,Go 語言中哪些值是可以比較的,可以參考規格書中〈Comparison operators〉的說明。
Go 中沒有 ?: 三元條件運算子。
邏輯運算
在邏輯上有所謂的「且」、「或」與「反相」,在 Go 中提供對應的邏輯運算子(Logical operator),分別為 &&、||及 !。看看以下的程式片段會輸出什麼結果?
package main
import "fmt"
func main() {
number := 75
fmt.Println(number > 70 && number < 80) // true
fmt.Println(number > 80 || number < 75) // false
fmt.Println(!(number > 80 || number < 75)) // true
}
&& 與 || 有捷徑運算(Short-Circuit Evaluation)。因為 && 只要其中一個為假,就可以判定結果為假,所以只要左運算元評估為 false,就會直接傳回 false,不會再去運算右運算元。因為 || 只要其中一個為真,就可以判定結果為真,所以只要左運算元評估為 true,就會直接傳回 true,就不會再去運算右運算元。
來舉個運用捷徑運算的例子,在 Go 中兩個整數相除,若除數為 0 會發生 integer divide by zero 的錯誤,以下運用 && 捷徑運算避免了這個問題:
if(number2 != 0 && number1 / number2 > 1) {
fmt.Println(number1 / number2)
}
在這個程式片段中,變數 number1 與 number2 都是 int 型態,如果 number2 為 0 的話,&& 左邊運算元結果就是 false,直接判斷整個 && 的結果應是 false,不用再去評估右運算元,從而避免了 number1 / number2 而 number2 等於 0 時的除零錯誤。
指標
Go 語言中有指標(Pointer),你可以在宣告變數時於型態前加上 *,這表示建立一個指標,例如:
var i *int
這時 i 是個空指標,也就是值為 nil,上頭等同於 var i *int = nil,目前並沒有儲存任何位址,如果想讓它儲存另一個變數的記憶體位址,可以使用 & 取得變數位址並指定給 i,例如:
package main
import "fmt"
func main() {
var i *int
j := 1
i = &j
fmt.Println(i) // 0x104382e0 之類的值
fmt.Println(*i) // 1
j = 10
fmt.Println(*i) // 10
*i = 20
fmt.Println(j) // 20
}
j 的位置儲存了 1,那麼具體來說,j 的位置到底是在哪?這就是 & 取址運算的目的,&j 具體取得了 j 的位置,然後指定給 i。
如上所示,如果想存取指標位址處的變數儲存的值,可以使用 *,因而,你改變 j 的值,*i 取得的就是改變後的值,透過 *i 改變值,從 j 取得的也會是改變後的值。
其應用的實例之一是使用 fmt.Scanf 取得標準輸入時,例如:
package main
import "fmt"
func main() {
var input int
fmt.Printf("輸入數字")
fmt.Scanf("%d", &input)
fmt.Println(input)
}
這邊使用 &input 取出 input 的記憶體位址值,並傳入 fmt.Scanf 函式,函式中會取得使用者的標準輸入,並儲存至 input 變數的記憶體位址,因而,再度取得 input 的值時,就會是使用者輸入的值。
Go 雖然有指標,不過不能如同 C/C++ 那樣對指標做運算,之後有機會用到指標時,會再做相關說明。

