我們將在這個部分講解有關(guān)布爾型、數(shù)字型和字符型的相關(guān)知識。
表達式是一種特定的類型的值,它可以由其它的值以及運算符組合而成。每個類型都定義了可以和自己結(jié)合的運算符集合,如果你使用了不在這個集合中的運算符,則會在編譯時獲得編譯錯誤。
一元運算符只可以用于一個值的操作(作為后綴),而二元運算符則可以和兩個值或者操作數(shù)結(jié)合(作為中綴)。
只有兩個類型相同的值才可以和二元運算符結(jié)合,另外要注意的是,Go 是強類型語言,因此不會進行隱式轉(zhuǎn)換,任何不同類型之間的轉(zhuǎn)換都必須顯式說明(第 4.2 節(jié))。Go 不存在像 C 和 Java 那樣的運算符重載,表達式的解析順序是從左至右。
你可以在第 4.5.3 節(jié)找到有關(guān)運算符優(yōu)先級的相關(guān)信息,優(yōu)先級越高的運算符在條件相同的情況下將被優(yōu)先執(zhí)行。但是你可以通過使用括號將其中的表達式括起來,以人為地提升某個表達式的運算優(yōu)先級。
一個簡單的例子:var b bool = true
。
布爾型的值只可以是常量 true 或者 false。
兩個類型相同的值可以使用相等 ==
或者不等 !=
運算符來進行比較并獲得一個布爾型的值。
當(dāng)相等運算符兩邊的值是完全相同的值的時候會返回 true,否則返回 false,并且只有在兩個的值的類型相同的情況下才可以使用。
示例:
var aVar = 10
aVar == 5 -> false
aVar == 10 -> true
當(dāng)不等運算符兩邊的值是不同的時候會返回 true,否則返回 false。
示例:
var aVar = 10
aVar != 5 -> true
aVar != 10 -> false
Go 對于值之間的比較有非常嚴(yán)格的限制,只有兩個類型相同的值才可以進行比較,如果值的類型是接口(interface,第 11 章),它們也必須都實現(xiàn)了相同的接口。如果其中一個值是常量,那么另外一個值的類型必須和該常量類型相兼容的。如果以上條件都不滿足,則其中一個值的類型必須在被轉(zhuǎn)換為和另外一個值的類型相同之后才可以進行比較。
布爾型的常量和變量也可以通過和邏輯運算符(非 !
、和 &&
、或 ||
)結(jié)合來產(chǎn)生另外一個布爾值,這樣的邏輯語句就其本身而言,并不是一個完整的 Go 語句。
邏輯值可以被用于條件結(jié)構(gòu)中的條件語句(第 5 章),以便測試某個條件是否滿足。另外,和 &&
、或 ||
與相等 ==
或不等 !=
屬于二元運算符,而非 !
屬于一元運算符。在接下來的內(nèi)容中,我們會使用 T 來代表條件符合的語句,用 F 來代表條件不符合的語句。
Go 語言中包含以下邏輯運算符:
非運算符:!
!T -> false
!F -> true
非運算符用于取得和布爾值相反的結(jié)果。
和運算符:&&
T && T -> true
T && F -> false
F && T -> false
F && F -> false
只有當(dāng)兩邊的值都為 true 的時候,和運算符的結(jié)果才是 true。
或運算符:||
T || T -> true
T || F -> true
F || T -> true
F || F -> false
只有當(dāng)兩邊的值都為 false 的時候,或運算符的結(jié)果才是 false,其中任意一邊的值為 true 就能夠使得該表達式的結(jié)果為 true。
在 Go 語言中,&& 和 || 是具有快捷性質(zhì)的運算符,當(dāng)運算符左邊表達式的值已經(jīng)能夠決定整個表達式的值的時候(&& 左邊的值為 false,|| 左邊的值為 true),運算符右邊的表達式將不會被執(zhí)行。利用這個性質(zhì),如果你有多個條件判斷,應(yīng)當(dāng)將計算過程較為復(fù)雜的表達式放在運算符的右側(cè)以減少不必要的運算。
利用括號同樣可以升級某個表達式的運算優(yōu)先級。
在格式化輸出時,你可以使用 %t
來表示你要輸出的值為布爾型。
布爾值(以及任何結(jié)果為布爾值的表達式)最常用在條件結(jié)構(gòu)的條件語句中,例如:if、for 和 switch 結(jié)構(gòu)(第 5 章)。
對于布爾值的好的命名能夠很好地提升代碼的可讀性,例如以 is
或者 Is
開頭的 isSorted
、isFinished
、isVisible
,使用這樣的命名能夠在閱讀代碼的獲得閱讀正常語句一樣的良好體驗,例如標(biāo)準(zhǔn)庫中的 unicode.IsDigit(ch)
(第 4.5.5 節(jié))。
Go 語言支持整型和浮點型數(shù)字,并且原生支持復(fù)數(shù),其中位的運算采用補碼(詳情參見 二的補碼 頁面)。
Go 也有基于架構(gòu)的類型,例如:int、uint 和 uintptr。
這些類型的長度都是根據(jù)運行程序所在的操作系統(tǒng)類型所決定的:
int
和 uint
在 32 位操作系統(tǒng)上,它們均使用 32 位(4 個字節(jié)),在 64 位操作系統(tǒng)上,它們均使用 64 位(8 個字節(jié))。uintptr
的長度被設(shè)定為足夠存放一個指針即可。Go 語言中沒有 float 類型。
與操作系統(tǒng)架構(gòu)無關(guān)的類型都有固定的大小,并在類型的名稱中就可以看出來:
整數(shù):
無符號整數(shù):
浮點型(IEEE-754 標(biāo)準(zhǔn)):
int 型是計算最快的一種類型。
整型的零值為 0,浮點型的零值為 0.0。
float32 精確到小數(shù)點后 7 位,float64 精確到小數(shù)點后 15 位。由于精確度的緣故,你在使用 ==
或者 !=
來比較浮點數(shù)時應(yīng)當(dāng)非常小心。你最好在正式使用前測試對于精確度要求較高的運算。
你應(yīng)該盡可能地使用 float64,因為 math
包中所有有關(guān)數(shù)學(xué)運算的函數(shù)都會要求接收這個類型。
你可以通過增加前綴 0 來表示 8 進制數(shù)(如:077),增加前綴 0x 來表示 16 進制數(shù)(如:0xFF),以及使用 e 來表示 10 的連乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)。
你可以使用 a := uint64(0)
來同時完成類型轉(zhuǎn)換和賦值操作,這樣 a 的類型就是 uint64。
Go 中不允許不同類型之間的混合使用,但是對于常量的類型限制非常少,因此允許常量之間的混合使用,下面這個程序很好地解釋了這個現(xiàn)象(該程序無法通過編譯):
示例 4.8 type_mixing.go
package main
func main() {
var a int
var b int32
a = 15
b = a + a // 編譯錯誤
b = b + 5 // 因為 5 是常量,所以可以通過編譯
}
如果你嘗試編譯該程序,則將得到編譯錯誤 cannot use a + a (type int) as type int32 in assignment
。
同樣地,int16 也不能夠被隱式轉(zhuǎn)換為 int32。
下面這個程序展示了通過顯式轉(zhuǎn)換來避免這個問題(第 4.2 節(jié))。
示例 4.9 casting.go
package main
import "fmt"
func main() {
var n int16 = 34
var m int32
// compiler error: cannot use n (type int16) as type int32 in assignment
//m = n
m = int32(n)
fmt.Printf("32 bit int is: %d\n", m)
fmt.Printf("16 bit int is: %d\n", n)
}
輸出:
32 bit int is: 34
16 bit int is: 34
格式化說明符
在格式化字符串里,%d
用于格式化整數(shù)(%x
和 %X
用于格式化 16 進制表示的數(shù)字),%g
用于格式化浮點型(%f
輸出浮點數(shù),%e
輸出科學(xué)計數(shù)表示法),%0d
用于規(guī)定輸出定長的整數(shù),其中開頭的數(shù)字 0 是必須的。
%n.mg
用于表示數(shù)字 n 并精確到小數(shù)點后 m 位,除了使用 g 之外,還可以使用 e 或者 f,例如:使用格式化字符串 %5.2e
來輸出 3.4 的結(jié)果為 3.40e+00
。
數(shù)字值轉(zhuǎn)換
當(dāng)進行類似 a32bitInt = int32(a32Float)
的轉(zhuǎn)換時,小數(shù)點后的數(shù)字將被丟棄。這種情況一般發(fā)生當(dāng)從取值范圍較大的類型轉(zhuǎn)換為取值范圍較小的類型時,或者你可以寫一個專門用于處理類型轉(zhuǎn)換的函數(shù)來確保沒有發(fā)生精度的丟失。下面這個例子展示如何安全地從 int 型轉(zhuǎn)換為 int8:
func Uint8FromInt(n int) (uint8, error) {
if 0 <= n && n <= math.MaxUint8 { // conversion is safe
return uint8(n), nil
}
return 0, fmt.Errorf("%d is out of the uint8 range", n)
}
或者安全地從 float64 轉(zhuǎn)換為 int:
func IntFromFloat64(x float64) int {
if math.MinInt32 <= x && x <= math.MaxInt32 { // x lies in the integer range
whole, fraction := math.Modf(x)
if fraction >= 0.5 {
whole++
}
return int(whole)
}
panic(fmt.Sprintf("%g is out of the int32 range", x))
}
不過如果你實際存的數(shù)字超出你要轉(zhuǎn)換到的類型的取值范圍的話,則會引發(fā) panic(第 13.2 節(jié))。
問題 4.1 int 和 int64 是相同的類型嗎?
Go 擁有以下復(fù)數(shù)類型:
complex64 (32 位實數(shù)和虛數(shù))
complex128 (64 位實數(shù)和虛數(shù))
復(fù)數(shù)使用 re+imI
來表示,其中 re
代表實數(shù)部分,im
代表虛數(shù)部分,I 代表根號負(fù) 1。
示例:
var c1 complex64 = 5 + 10i
fmt.Printf("The value is: %v", c1)
// 輸出: 5 + 10i
如果 re
和 im
的類型均為 float32,那么類型為 complex64 的復(fù)數(shù) c 可以通過以下方式來獲得:
c = complex(re, im)
函數(shù) real(c)
和 imag(c)
可以分別獲得相應(yīng)的實數(shù)和虛數(shù)部分。
在使用格式化說明符時,可以使用 %v
來表示復(fù)數(shù),但當(dāng)你希望只表示其中的一個部分的時候需要使用 %f
。
復(fù)數(shù)支持和其它數(shù)字類型一樣的運算。當(dāng)你使用等號 ==
或者不等號 !=
對復(fù)數(shù)進行比較運算時,注意對精確度的把握。cmath
包中包含了一些操作復(fù)數(shù)的公共方法。如果你對內(nèi)存的要求不是特別高,最好使用 complex128 作為計算類型,因為相關(guān)函數(shù)都使用這個類型的參數(shù)。
位運算只能用于整數(shù)類型的變量,且需當(dāng)它們擁有等長位模式時。
%b
是用于表示位的格式化標(biāo)識符。
二元運算符
按位與 &
:
對應(yīng)位置上的值經(jīng)過和運算結(jié)果,具體參見和運算符,第 4.5.1 節(jié),并將 T(true)替換為 1,將 F(false)替換為 0
1 & 1 -> 1
1 & 0 -> 0
0 & 1 -> 0
0 & 0 -> 0
按位或 |
:
對應(yīng)位置上的值經(jīng)過或運算結(jié)果,具體參見或運算符,第 4.5.1 節(jié),并將 T(true)替換為 1,將 F(false)替換為 0
1 | 1 -> 1
1 | 0 -> 1
0 | 1 -> 1
0 | 0 -> 0
按位異或 ^
:
對應(yīng)位置上的值根據(jù)以下規(guī)則組合:
1 ^ 1 -> 0
1 ^ 0 -> 1
0 ^ 1 -> 1
0 ^ 0 -> 0
&^
:將指定位置上的值設(shè)置為 0。一元運算符
按位補足 ^
:
該運算符與異或運算符一同使用,即 m^x
,對于無符號 x 使用“全部位設(shè)置為 1”,對于有符號 x 時使用 m=-1
。例如:
^2 = ^10 = -01 ^ 10 = -11
位左移 <<
:
bitP << n
。bitP
的位向左移動 n 位,右側(cè)空白部分使用 0 填充;如果 n 等于 2,則結(jié)果是 2 的相應(yīng)倍數(shù),即 2 的 n 次方。例如:
1 << 10 // 等于 1 KB
1 << 20 // 等于 1 MB
1 << 30 // 等于 1 GB
位右移 >>
:
bitP >> n
。bitP
的位向右移動 n 位,左側(cè)空白部分使用 0 填充;如果 n 等于 2,則結(jié)果是當(dāng)前值除以 2 的 n 次方。當(dāng)希望把結(jié)果賦值給第一個操作數(shù)時,可以簡寫為 a <<= 2
或者 b ^= a & 0xffffffff
。
位左移常見實現(xiàn)存儲單位的用例
使用位左移與 iota 計數(shù)配合可優(yōu)雅地實現(xiàn)存儲單位的常量枚舉:
type ByteSize float64
const (
_ = iota // 通過賦值給空白標(biāo)識符來忽略值
KB ByteSize = 1<<(10*iota)
MB
GB
TB
PB
EB
ZB
YB
)
在通訊中使用位左移表示標(biāo)識的用例
type BitFlag int
const (
Active BitFlag = 1 << iota // 1 << 0 == 1
Send // 1 << 1 == 2
Receive // 1 << 2 == 4
)
flag := Active | Send // == 3
Go 中擁有以下邏輯運算符:==
、!=
(第 4.5.1 節(jié))、<
、<=
、>
、>=
。
它們之所以被稱為邏輯運算符是因為它們的運算結(jié)果總是為布爾值 bool
。例如:
b3:= 10 > 5 // b3 is true
常見可用于整數(shù)和浮點數(shù)的二元運算符有 +
、-
、*
和 /
。
(相對于一般規(guī)則而言,Go 在進行字符串拼接時允許使用對運算符 +
的重載,但 Go 本身不允許開發(fā)者進行自定義的運算符重載)
/
對于整數(shù)運算而言,結(jié)果依舊為整數(shù),例如:9 / 4 -> 2
。
取余運算符只能作用于整數(shù):9 % 4 -> 1
。
整數(shù)除以 0 可能導(dǎo)致程序崩潰,將會導(dǎo)致運行時的恐慌狀態(tài)(如果除以 0 的行為在編譯時就能被捕捉到,則會引發(fā)編譯錯誤);第 13 章將會詳細(xì)講解如何正確地處理此類情況。
浮點數(shù)除以 0.0 會返回一個無窮盡的結(jié)果,使用 +Inf
表示。
練習(xí) 4.4 嘗試編譯 divby0.go。
你可以將語句 b = b + a
簡寫為 b+=a
,同樣的寫法也可用于 -=
、*=
、/=
、%=
。
對于整數(shù)和浮點數(shù),你可以使用一元運算符 ++
(遞增)和 --
(遞減),但只能用于后綴:
i++ -> i += 1 -> i = i + 1
i-- -> i -= 1 -> i = i - 1
同時,帶有 ++
和 --
的只能作為語句,而非表達式,因此 n = i++
這種寫法是無效的,其它像 f(i++)
或者 a[i]=b[i++]
這些可以用于 C、C++ 和 Java 中的寫法在 Go 中也是不允許的。
在運算時 溢出 不會產(chǎn)生錯誤,Go 會簡單地將超出位數(shù)拋棄。如果你需要范圍無限大的整數(shù)或者有理數(shù)(意味著只被限制于計算機內(nèi)存),你可以使用標(biāo)準(zhǔn)庫中的 big
包,該包提供了類似 big.Int
和 big.Rat
這樣的類型(第 9.4 節(jié))。
一些像游戲或者統(tǒng)計學(xué)類的應(yīng)用需要用到隨機數(shù)。rand
包實現(xiàn)了偽隨機數(shù)的生成。
示例 4.10 random.go 演示了如何生成 10 個非負(fù)隨機數(shù):
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
for i := 0; i < 10; i++ {
a := rand.Int()
fmt.Printf("%d / ", a)
}
for i := 0; i < 5; i++ {
r := rand.Intn(8)
fmt.Printf("%d / ", r)
}
fmt.Println()
timens := int64(time.Now().Nanosecond())
rand.Seed(timens)
for i := 0; i < 10; i++ {
fmt.Printf("%2.2f / ", 100*rand.Float32())
}
}
可能的輸出:
816681689 / 1325201247 / 623951027 / 478285186 / 1654146165 /
1951252986 / 2029250107 / 762911244 / 1372544545 / 591415086 / / 3 / 0 / 6 / 4 / 2 /22.10
/ 65.77 / 65.89 / 16.85 / 75.56 / 46.90 / 55.24 / 55.95 / 25.58 / 70.61 /
函數(shù) rand.Float32
和 rand.Float64
返回介于 [0.0, 1.0) 之間的偽隨機數(shù),其中包括 0.0 但不包括 1.0。函數(shù) rand.Intn
返回介于 [0, n) 之間的偽隨機數(shù)。
你可以使用 Seed(value)
函數(shù)來提供偽隨機數(shù)的生成種子,一般情況下都會使用當(dāng)前時間的納秒級數(shù)字(第 4.8 節(jié))。
有些運算符擁有較高的優(yōu)先級,二元運算符的運算方向均是從左至右。下表列出了所有運算符以及它們的優(yōu)先級,由上至下代表優(yōu)先級由高到低:
優(yōu)先級 運算符
7 ^ !
6 * / % << >> & &^
5 + - | ^
4 == != < <= >= >
3 <-
2 &&
1 ||
當(dāng)然,你可以通過使用括號來臨時提升某個表達式的整體運算優(yōu)先級。
當(dāng)你在使用某個類型時,你可以給它起另一個名字,然后你就可以在你的代碼中使用新的名字(用于簡化名稱或解決名稱沖突)。
在 type TZ int
中,TZ 就是 int 類型的新名稱(用于表示程序中的時區(qū)),然后就可以使用 TZ 來操作 int 類型的數(shù)據(jù)。
示例 4.11 type.go
package main
import "fmt"
type TZ int
func main() {
var a, b TZ = 3, 4
c := a + b
fmt.Printf("c has the value: %d", c) // 輸出:c has the value: 7
}
實際上,類型別名得到的新類型并非和原類型完全相同,新類型不會擁有原類型所附帶的方法(第 10 章);TZ 可以自定義一個方法用來輸出更加人性化的時區(qū)信息。
練習(xí) 4.5 定義一個 string
的類型別名 Rope
,并聲明一個該類型的變量。
嚴(yán)格來說,這并不是 Go 語言的一個類型,字符只是整數(shù)的特殊用例。byte
類型是 uint8
的別名,對于只占用 1 個字節(jié)的傳統(tǒng) ASCII 編碼的字符來說,完全沒有問題。例如:var ch byte = 'A'
;字符使用單引號括起來。
在 ASCII 碼表中,A 的值是 65,而使用 16 進制表示則為 41,所以下面的寫法是等效的:
var ch byte = 65 或 var ch byte = '\x41'
(\x
總是緊跟著長度為 2 的 16 進制數(shù))
另外一種可能的寫法是 \
后面緊跟著長度為 3 的八進制數(shù),例如:\377
。
不過 Go 同樣支持 Unicode(UTF-8),因此字符同樣稱為 Unicode 代碼點或者 runes,并在內(nèi)存中使用 int 來表示。在文檔中,一般使用格式 U+hhhh 來表示,其中 h 表示一個 16 進制數(shù)。其實 rune
也是 Go 當(dāng)中的一個類型,并且是 int32
的別名。
在書寫 Unicode 字符時,需要在 16 進制數(shù)之前加上前綴 \u
或者 \U
。
因為 Unicode 至少占用 2 個字節(jié),所以我們使用 int16
或者 int
類型來表示。如果需要使用到 4 字節(jié),則會加上 \U
前綴;前綴 \u
則總是緊跟著長度為 4 的 16 進制數(shù),前綴 \U
緊跟著長度為 8 的 16 進制數(shù)。
示例 4.12 char.go
var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
輸出:
65 - 946 - 1053236
A - β - r
41 - 3B2 - 101234
U+0041 - U+03B2 - U+101234
格式化說明符 %c
用于表示字符;當(dāng)和字符配合使用時,%v
或 %d
會輸出用于表示該字符的整數(shù);%U
輸出格式為 U+hhhh 的字符串(另一個示例見第 5.4.4 節(jié))。
包 unicode
包含了一些針對測試字符的非常有用的函數(shù)(其中 ch
代表字符):
unicode.IsLetter(ch)
unicode.IsDigit(ch)
unicode.IsSpace(ch)
這些函數(shù)返回一個布爾值。包 utf8
擁有更多與 rune 相關(guān)的函數(shù)。
( 譯者注:關(guān)于類型的相關(guān)講解,可參考視頻教程 《Go編程基礎(chǔ)》 第 3 課:類型與變量 )