本文將介紹適用于基本類型值的各種運算操作符。
本文只介紹算術(shù)運算符、位運算符、比較運算符、布爾運算符和字符串銜接運算符。 這些運算符要么是二元的(需要兩個操作數(shù)),要么是一元的(需要一個操作數(shù))。 所有這些運算符運算都只返回一個結(jié)果。操作數(shù)常常也稱為操作值。
本文中的解釋不追求描述的完全準(zhǔn)確性。 比如,當(dāng)我們說一個二元運算符運算需要其涉及的兩個操作數(shù)類型必須一樣的時,這指:
類似的,當(dāng)我們說一個運算符(一元或者二元)運算要求其涉及的某個操作數(shù)的類型必須為某個特定類型時,這指:
在繼續(xù)下面的章節(jié)之前,我們需要知道什么叫常量表達式和關(guān)于常量表達式估值的一個常識。 表達式的概念將在表達式和語句一文中得到解釋。 目前我們只需知道本文中所提及的大多數(shù)運算都屬于表達式。 當(dāng)一個表達式中涉及到的所有操作數(shù)都是常量時,此表達式稱為一個常量表達式。 一個常量表達式的估值是在編譯階段進行的。一個常量表達式的估值結(jié)果依然是一個常量。 如果一個表達式中涉及到的操作數(shù)中至少有一個不為常量,則此表達式稱為非常量表達式。
Go支持五個基本二元算術(shù)運算符:
字面形式 | 名稱 | 對兩個運算數(shù)的要求 |
---|---|---|
+ | 加法 | 兩個運算數(shù)的類型必須相同并且為基本數(shù)值類型。 |
- | 減法 | |
* | 乘法 | |
/ | 除法 | |
% | 余數(shù) | 兩個運算數(shù)的類型必須相同并且為基本整數(shù)數(shù)值類型。 |
Go支持六種位運算符(也屬于算術(shù)運算):
字面形式 | 名稱 | 對兩個操作數(shù)的要求以及機制解釋 |
---|---|---|
|
位與 |
兩個操作數(shù)的類型必須相同并且為基本整數(shù)數(shù)值類型。 機制解釋(下標(biāo)2 表明一個字面量為二進制):
|
|
位或 | |
|
(位)異或 | |
|
清位 | |
<< |
左移位 |
左操作數(shù)必須為一個整數(shù),右操作數(shù)也必須為一個整數(shù)(如果它是一個常數(shù),則它必須非負(fù)),但它們的類型可以不同。 (注意:在Go 1.13之前,右操作數(shù)必須為一個無符號整數(shù)類型的類型確定值或者一個可以表示成 一個負(fù)右操作數(shù)(非常數(shù))將在運行時刻造成一個恐慌。 機制解釋:
注意,在右移運算中,左邊空出來的位(即高位)全部用左操作數(shù)的最高位(即正負(fù)號位)填充。 比如如果左操作數(shù) |
>> |
右移位 |
Go也支持三個一元算術(shù)運算符:
字面形式 | 名稱 | 解釋 |
---|---|---|
+ | 取正數(shù) | +n 等價于0 + n . |
- | 取負(fù)數(shù) | -n 等價于0 - n . |
^ | 位反(或位補) | ^n 等價于m ^ n ,其中m 和n 同類型并且它的二進制表示中所有比特位均為1。 比如如果n 的類型為int8 ,則m 的值為-1 ;如果n 的類型為uint8 ,則m 的值為255 。 |
注意:
~
?表示的。+
?也可用做字符串銜接運算符(見下)。*
?除了可以當(dāng)作乘號運算符,它也可以用做指針解引用運算符; ?&
?除了可以當(dāng)作位與運算符,它也可以用做取地址運算符。 后面的指針一文將詳解內(nèi)存地址和指針類型。>>>
?。math
?標(biāo)準(zhǔn)庫包中的?Pow
?函數(shù)來進行冪運算。 下一篇文章將詳解包和包引入。&^
?是Go中特有的一個運算符。 ?m &^ n
?等價于?m & (^n)
?。一些運算符的使用示例:
func main() {
var (
a, b float32 = 12.0, 3.14
c, d int16 = 15, -6
e uint8 = 7
)
// 這些行編譯沒問題。
_ = 12 + 'A' // 兩個類型不確定操作數(shù)(都為數(shù)值類型)
_ = 12 - a // 12將被當(dāng)做a的類型(float32)使用。
_ = a * b // 兩個同類型的類型確定操作數(shù)。
_ = c % d
_, _ = c + int16(e), uint8(c) + e
_, _, _, _ = a / b, c / d, -100 / -9, 1.23 / 1.2
_, _, _, _ = c | d, c & d, c ^ d, c &^ d
_, _, _, _ = d << e, 123 >> e, e >> 3, 0xF << 0
_, _, _, _ = -b, +c, ^e, ^-1
// 這些行編譯將失敗。
_ = a % b // error: a和b都不是整數(shù)
_ = a | b // error: a和b都不是整數(shù)
_ = c + e // error: c和e的類型不匹配
_ = b >> 5 // error: b不是一個整數(shù)
_ = c >> -5 // error: -5不是一個無符號整數(shù)
_ = e << uint(c) // 編譯沒問題
_ = e << c // 從Go 1.13開始,此行才能編譯過
_ = e << -c // 從Go 1.13開始,此行才能編譯過。
// 將在運行時刻造成恐慌。
_ = e << -1 // error: 右操作數(shù)不能為負(fù)(常數(shù))
}
上一篇文章提到了
對于一個算數(shù)運算的結(jié)果,上述規(guī)則同樣適用。
示例:
// 結(jié)果為非常量
var a, b uint8 = 255, 1
var c = a + b // c==0。a+b是一個非常量表達式,
// 結(jié)果中溢出的高位比特將被截斷舍棄。
var d = a << b // d == 254。同樣,結(jié)果中溢出的
// 高位比特將被截斷舍棄。
// 結(jié)果為類型不確定常量,允許溢出其默認(rèn)類型。
const X = 0x1FFFFFFFF * 0x1FFFFFFFF // 沒問題,盡管X溢出
const R = 'a' + 0x7FFFFFFF // 沒問題,盡管R溢出
// 運算結(jié)果或者轉(zhuǎn)換結(jié)果為類型確定常量
var e = X // error: X溢出int。
var h = R // error: R溢出rune。
const Y = 128 - int8(1) // error: 128溢出int8。
const Z = uint8(255) + 1 // error: 256溢出uint8。
除了移位運算,對于一個二元算術(shù)運算,
complex128
高于float64
高于rune
高于int
)。 結(jié)果的默認(rèn)類型同樣為此設(shè)想類型。 比如,如果一個類型不確定操作數(shù)的默認(rèn)類型為int
,另一個類型不確定操作數(shù)的默認(rèn)類型為rune
,
則前者的類型在運算中也被視為rune
,運算結(jié)果為一個默認(rèn)類型為rune
的類型不確定值。對于移位運算,結(jié)果規(guī)則有點小復(fù)雜。首先移位運算的結(jié)果肯定都是整數(shù)。
rune
或int
),則它的默認(rèn)類型將被視為int
。 此移位運算的結(jié)果也是一個類型不確定值并且它的默認(rèn)類型和左操作數(shù)的默認(rèn)類型一致。一些非移位算術(shù)運算的例子:
func main() {
// 三個類型不確定常量。它們的默認(rèn)類型
// 分別為:int、rune和complex64.
const X, Y, Z = 2, 'A', 3i
var a, b int = X, Y // 兩個類型確定值
// 變量d的類型被推斷為Y的默認(rèn)類型:rune(亦即int32)。
d := X + Y
// 變量e的類型被推斷為a的類型:int。
e := Y - a
// 變量f的類型和a及b的類型一樣:int。
f := a * b
// 變量g的類型被推斷為Z的默認(rèn)類型:complex64。
g := Z * Y
// 2 65 (+0.000000e+000+3.000000e+000i)
println(X, Y, Z)
// 67 63 130 (+0.000000e+000+1.950000e+002i)
println(d, e, f, g)
}
一個移位算術(shù)運算的例子:
const N = 2
// A == 12,它是一個默認(rèn)類型為int的類型不確定值。
const A = 3.0 << N
// B == 12,它是一個類型為int8的類型確定值。
const B = int8(3.0) << N
var m = uint(32)
// 下面的三行是相互等價的。
var x int64 = 1 << m // 1的類型將被設(shè)想為int64,而非int
var y = int64(1 << m) // 同上
var z = int64(1) << m // 同上
// 下面這行編譯不通過。
/*
var _ = 1.23 << m // error: 浮點數(shù)不能被移位
*/
上面提到的移位運算結(jié)果的最后一點類型推斷規(guī)則有點反常。 這條規(guī)則的主要目的是為了防止一些移位運算在32位架構(gòu)和64位架構(gòu)的機器上的運算結(jié)果出現(xiàn)不一致但不一致卻沒有被及時發(fā)現(xiàn)的情況。 比如如果上面一段代碼中第10行(或第9行)的1
的類型被推斷為它的默認(rèn)類型int
, 則在32位架構(gòu)的機器上,x
的取值在運行時刻將被截斷為0,而在64位架構(gòu)的機器上,x
的取值在運行時刻將為232。
因為m
是一個變量,在32位架構(gòu)的機器上,第9行和第10行并不會在編譯時刻報錯。 這將導(dǎo)致Go程序員在不經(jīng)意間寫出沒有料到的和難以覺察的bug。 因此,第9行和第10行中的1
的類型被推斷為int64
(最終的設(shè)想結(jié)果類型),而不是它們的默認(rèn)類型int
。
下面這段代碼展示了對于左操作數(shù)為類型不確定值的移位運算,編譯結(jié)果因右操作數(shù)是否為常量而帶來的不同結(jié)果:
const n = uint(2)
var m = uint(2)
// 這兩行編譯沒問題。
var _ float64 = 1 << n
var _ = float64(1 << n)
// 這兩行編譯失敗。
var _ float64 = 1 << m // error
var _ = float64(1 << m) // error
上面這段代碼最后兩行編譯失敗是因為它們都等價于下面這兩行:
var _ = float64(1) << m
var _ = 1.0 << m // error: shift of type float64
另一個例子:
package main
const n = uint(8)
var m = uint(8)
func main() {
println(a, b) // 2 0
}
var a byte = 1 << n / 128
var b byte = 1 << m / 128
上面這個程序打印出2 0
,因為最后兩行等價于:
var a = byte(int(1) << n / 128)
var b = byte(1) << m / 128
假設(shè)兩個操作數(shù)x
和y
的類型為同一個整數(shù)類型, 則它們通過除法和余數(shù)運算得到的商q
(= x / y
)和余數(shù)r
(= x % y
)滿足x == q*y + r
(|r| < |y|
)。如果余數(shù)r
不為零,則它的符號和被除數(shù)x
相同。商q
的結(jié)果為x / y
向零靠攏截斷。
如果除數(shù)y
是一個常量,則它必須不為0,否則編譯不通過。 如果它是一個整數(shù)型非常量,則在運行時刻將拋出一個恐慌(panic)。 恐慌類似與某些其它語言中的異常(exception)。 我們將在以后的文章中了解到Go中的恐慌和恐慌恢復(fù)機制。 如果除數(shù)y
非整數(shù)型的非常量,則運算結(jié)果為一個無窮大(Inf,當(dāng)被除數(shù)不為0時)或者NaN(not a number,當(dāng)被除數(shù)為0時)。
示例:
println( 5/3, 5%3) // 1 2
println( 5/-3, 5%-3) // -1 2
println(-5/3, -5%3) // -1 -2
println(-5/-3, -5%-3) // 1 -2
println(5.0 / 3.0) // 1.666667
println((1-1i)/(1+1i)) // -1i
var a, b = 1.0, 0.0
println(a/b, b/b) // +Inf NaN
_ = int(a)/int(b) // 編譯沒問題,但在運行時刻將造成恐慌。
// 這兩行編譯不通過。
println(1.0/0.0) // error: 除數(shù)為0
println(0.0/0.0) // error: 除數(shù)為0
對于一個二元算數(shù)運算符op
,語句x = x op y
可以被簡寫為x op= y
。 在這個簡寫的語句中,x
只會被估值一次。
示例:
var a, b int8 = 3, 5
a += b
println(a) // 8
a *= a
println(a) // 64
a /= b
println(a) // 12
a %= b
println(a) // 2
b <<= uint(a)
println(b) // 20
和很多其它流行語言一樣,Go也支持自增(++
)和自減(--
)操作符。 不過和其它語言不一樣的是,自增(aNumber++
)和自減(aNumber--
)操作沒有返回值, 所以它們不能當(dāng)做表達式來使用。 另一個顯著區(qū)別是,在Go中,自增(++
)和自減(--
)操作符只能后置,不能前置。
一個例子:
package main
func main() {
a, b, c := 12, 1.2, 1+2i
a++ // ok. <=> a += 1 <=> a = a + 1
b-- // ok. <=> b -= 1 <=> b = b - 1
c++ // ok.
// 下面這些行編譯不通過。
/*
_ = a++
_ = b--
_ = c++
++a
--b
++c
*/
}
上面已經(jīng)提到了,加法運算符也可用做字符串銜接運算符。
字面形式 | 名稱 | 對兩個操作數(shù)的要求 |
---|---|---|
+ | 字符串銜接 | 兩個操作數(shù)必須為同一類型的字符串值。 |
+=
運算符也適用于字符串銜接。
示例:
println("Go" + "lang") // Golang
var a = "Go"
a += "lang"
println(a) // Golang
如果一個字符串銜接運算中的一個操作值為類型確定的,則結(jié)果字符串是一個類型和此操作數(shù)類型相同的類型確定值。 否則,結(jié)果字符串是一個類型不確定值(肯定是一個常量)。
Go支持兩種布爾二元運算符和一種布爾一元運算符。
字面形式 | 名稱 | 對操作值的要求 |
---|---|---|
&& | 布爾與(二元) | 兩個操作值的類型必須為同一布爾類型。 |
|| | 布爾或(二元) | |
! | 布爾否(一元) | 唯一的一個操作值的類型必須為一個布爾類型。 |
我們可以用下一小節(jié)介紹的不等于操作符!=
來做為布爾異或操作符。
機理解釋:
// x y x && y x || y !x !y
true true true true false false
true false false true false true
false true false true true false
false false false false true true
如果一個布爾運算中的一個操作值為類型確定的,則結(jié)果為一個和此操作值類型相同的類型確定值。 否則,結(jié)果為一個類型不確定布爾值。
Go支持6種比較運算符:
字面形式 | 名稱 | 對兩個操作值的要求 |
---|---|---|
== |
等于 |
如果兩個操作數(shù)都為類型確定的,則它們的類型必須一樣,或者其中一個操作數(shù)可以隱式轉(zhuǎn)換為另一個操作數(shù)的類型。 兩者的類型必須都為可比較類型(將在以后的文章中介紹)。 如果只有一個操作數(shù)是類型確定的,則另一個類型不確定操作數(shù)必須可以隱式轉(zhuǎn)換到類型確定操作數(shù)的類型。 如果兩個操作數(shù)都是類型不確定的,則它們必須同時為兩個類型不確定布爾值、兩個類型不確定字符串值或者兩個類型不確定數(shù)字值。 |
!= |
不等于 | |
< | 小于 | 兩個操作值的類型必須相同并且它們的類型必須為整數(shù)類型、浮點數(shù)類型或者字符串類型。 |
<= | 小于或等于 | |
> | 大于 | |
>= | 大于或等于 |
比較運算的結(jié)果總是一個類型不確定布爾值。 如果一個比較運算中的兩個操作數(shù)都為常量,則結(jié)果布爾值也為一個常量。
以后,如果我們說兩個值可以比較,我們的意思是說這兩個值可以用==
或者!=
運算符來比較。 我們將在以后的文章中,我們將了解到某些類型的值是不能比較的。
注意,并非所有的實數(shù)在內(nèi)存中都可以被精確地表示,所以比較兩個浮點數(shù)或者復(fù)數(shù)的結(jié)果并不是很可靠。 在編程中,我們常常比較兩個浮點數(shù)的差值是否小于一個闕值來檢查兩個浮點數(shù)是否相等。
Go中的操作符運算的優(yōu)先級和其它流行語言有一些差別。 下面列出了本文介紹的操作符的優(yōu)先級。 同一行中的操作符的優(yōu)先級是一樣的。優(yōu)先級逐行遞減。
* / % << >> & &^
+ - | ^
== != < <= > >=
&&
||
一個和其它流行語言明顯的差別是,移位運算<<
和>>
的優(yōu)先級比加減法+
和-
的優(yōu)先級要高。
一個表達式(做為一個子表達式)可以出現(xiàn)在另一個表達式中。 這個子表達式的估值結(jié)果將成為另一個表達式的一個操作數(shù)。 在這樣的復(fù)雜表達式中,對于相同優(yōu)先級的運算,它們將從左到右進行估值。 和很多其它語言一樣,我們也可用一對小括號()
來提升一個子運算的優(yōu)先級。
常量子表達式的順序有可能影響到最終的估值結(jié)果。
下面這個聲明的變量將被初始化為2.2
,而不是2.7
。 優(yōu)先級更高的子表達式3/2
是一個常量表達式,所以它將在編譯階段被估值。 根據(jù)上面介紹的規(guī)則,在運算中,3
和2
都被視為int
,所以3/2
的估值結(jié)果為1
。 在常量表達式1.2 + 1
的運算中,兩個操作數(shù)的類型被視為float64
,所以最終的估值結(jié)果為2.2
。
var x = 1.2 + 3/2
再比如下例,在一個常量聲明中,3/2
先被估值,其結(jié)果為1
,所以最終的估值結(jié)果為0.1
。 在第二個常量聲明中,0.1*3
先被估值,其結(jié)果為0.3
,所以最終的估值結(jié)果為0.15
。
package main
const x = 3/2*0.1
const y = 0.1*3/2
func main() {
println(x) // +1.000000e-001
println(y) // +1.500000e-001
}
Go中還有一些其它操作符。它們將在后續(xù)其它適當(dāng)?shù)奈恼轮薪榻B。
更多建議: