原文鏈接:https://gopl-zh.github.io/ch3/ch3-01.html
Go語(yǔ)言的數(shù)值類(lèi)型包括幾種不同大小的整數(shù)、浮點(diǎn)數(shù)和復(fù)數(shù)。每種數(shù)值類(lèi)型都決定了對(duì)應(yīng)的大小范圍和是否支持正負(fù)符號(hào)。讓我們先從整數(shù)類(lèi)型開(kāi)始介紹。
Go語(yǔ)言同時(shí)提供了有符號(hào)和無(wú)符號(hào)類(lèi)型的整數(shù)運(yùn)算。這里有int8、int16、int32和int64四種截然不同大小的有符號(hào)整數(shù)類(lèi)型,分別對(duì)應(yīng)8、16、32、64bit大小的有符號(hào)整數(shù),與此對(duì)應(yīng)的是uint8、uint16、uint32和uint64四種無(wú)符號(hào)整數(shù)類(lèi)型。
這里還有兩種一般對(duì)應(yīng)特定CPU平臺(tái)機(jī)器字大小的有符號(hào)和無(wú)符號(hào)整數(shù)int和uint;其中int是應(yīng)用最廣泛的數(shù)值類(lèi)型。這兩種類(lèi)型都有同樣的大小,32或64bit,但是我們不能對(duì)此做任何的假設(shè);因?yàn)椴煌木幾g器即使在相同的硬件平臺(tái)上可能產(chǎn)生不同的大小。
Unicode字符rune類(lèi)型是和int32等價(jià)的類(lèi)型,通常用于表示一個(gè)Unicode碼點(diǎn)。這兩個(gè)名稱(chēng)可以互換使用。同樣byte也是uint8類(lèi)型的等價(jià)類(lèi)型,byte類(lèi)型一般用于強(qiáng)調(diào)數(shù)值是一個(gè)原始的數(shù)據(jù)而不是一個(gè)小的整數(shù)。
最后,還有一種無(wú)符號(hào)的整數(shù)類(lèi)型uintptr,沒(méi)有指定具體的bit大小但是足以容納指針。uintptr類(lèi)型只有在底層編程時(shí)才需要,特別是Go語(yǔ)言和C語(yǔ)言函數(shù)庫(kù)或操作系統(tǒng)接口相交互的地方。我們將在第十三章的unsafe包相關(guān)部分看到類(lèi)似的例子。
不管它們的具體大小,int、uint和uintptr是不同類(lèi)型的兄弟類(lèi)型。其中int和int32也是不同的類(lèi)型,即使int的大小也是32bit,在需要將int當(dāng)作int32類(lèi)型的地方需要一個(gè)顯式的類(lèi)型轉(zhuǎn)換操作,反之亦然。
其中有符號(hào)整數(shù)采用2的補(bǔ)碼形式表示,也就是最高bit位用來(lái)表示符號(hào)位,一個(gè)n-bit的有符號(hào)數(shù)的值域是從$-2^{n-1}$到$2^{n-1}-1$。無(wú)符號(hào)整數(shù)的所有bit位都用于表示非負(fù)數(shù),值域是0到$2^n-1$。例如,int8類(lèi)型整數(shù)的值域是從-128到127,而uint8類(lèi)型整數(shù)的值域是從0到255。
下面是Go語(yǔ)言中關(guān)于算術(shù)運(yùn)算、邏輯運(yùn)算和比較運(yùn)算的二元運(yùn)算符,它們按照優(yōu)先級(jí)遞減的順序排列:
* / % << >> & &^
+ - | ^
== != < <= > >=
&&
||
二元運(yùn)算符有五種優(yōu)先級(jí)。在同一個(gè)優(yōu)先級(jí),使用左優(yōu)先結(jié)合規(guī)則,但是使用括號(hào)可以明確優(yōu)先順序,使用括號(hào)也可以用于提升優(yōu)先級(jí),例如mask & (1 << 28)
。
對(duì)于上表中前兩行的運(yùn)算符,例如+運(yùn)算符還有一個(gè)與賦值相結(jié)合的對(duì)應(yīng)運(yùn)算符+=,可以用于簡(jiǎn)化賦值語(yǔ)句。
算術(shù)運(yùn)算符+
、-
、*
和/
可以適用于整數(shù)、浮點(diǎn)數(shù)和復(fù)數(shù),但是取模運(yùn)算符%僅用于整數(shù)間的運(yùn)算。對(duì)于不同編程語(yǔ)言,%取模運(yùn)算的行為可能并不相同。在Go語(yǔ)言中,%取模運(yùn)算符的符號(hào)和被取模數(shù)的符號(hào)總是一致的,因此-5%3
和-5%-3
結(jié)果都是-2。除法運(yùn)算符/
的行為則依賴(lài)于操作數(shù)是否全為整數(shù),比如5.0/4.0
的結(jié)果是1.25,但是5/4的結(jié)果是1,因?yàn)檎麛?shù)除法會(huì)向著0方向截?cái)嘤鄶?shù)。
一個(gè)算術(shù)運(yùn)算的結(jié)果,不管是有符號(hào)或者是無(wú)符號(hào)的,如果需要更多的bit位才能正確表示的話(huà),就說(shuō)明計(jì)算結(jié)果是溢出了。超出的高位的bit位部分將被丟棄。如果原始的數(shù)值是有符號(hào)類(lèi)型,而且最左邊的bit位是1的話(huà),那么最終結(jié)果可能是負(fù)的,例如int8的例子:
var u uint8 = 255
fmt.Println(u, u+1, u*u) // "255 0 1"
var i int8 = 127
fmt.Println(i, i+1, i*i) // "127 -128 1"
兩個(gè)相同的整數(shù)類(lèi)型可以使用下面的二元比較運(yùn)算符進(jìn)行比較;比較表達(dá)式的結(jié)果是布爾類(lèi)型。
== 等于
!= 不等于
< 小于
<= 小于等于
> 大于
>= 大于等于
事實(shí)上,布爾型、數(shù)字類(lèi)型和字符串等基本類(lèi)型都是可比較的,也就是說(shuō)兩個(gè)相同類(lèi)型的值可以用==和!=進(jìn)行比較。此外,整數(shù)、浮點(diǎn)數(shù)和字符串可以根據(jù)比較結(jié)果排序。許多其它類(lèi)型的值可能是不可比較的,因此也就可能是不可排序的。對(duì)于我們遇到的每種類(lèi)型,我們需要保證規(guī)則的一致性。
這里是一元的加法和減法運(yùn)算符:
+ 一元加法(無(wú)效果)
- 負(fù)數(shù)
對(duì)于整數(shù),+x是0+x的簡(jiǎn)寫(xiě),-x則是0-x的簡(jiǎn)寫(xiě);對(duì)于浮點(diǎn)數(shù)和復(fù)數(shù),+x就是x,-x則是x 的負(fù)數(shù)。
Go語(yǔ)言還提供了以下的bit位操作運(yùn)算符,前面4個(gè)操作運(yùn)算符并不區(qū)分是有符號(hào)還是無(wú)符號(hào)數(shù):
& 位運(yùn)算 AND
| 位運(yùn)算 OR
^ 位運(yùn)算 XOR
&^ 位清空(AND NOT)
<< 左移
>> 右移
位操作運(yùn)算符^
作為二元運(yùn)算符時(shí)是按位異或(XOR),當(dāng)用作一元運(yùn)算符時(shí)表示按位取反;也就是說(shuō),它返回一個(gè)每個(gè)bit位都取反的數(shù)。位操作運(yùn)算符&^
用于按位置零(AND NOT):如果對(duì)應(yīng)y中bit位為1的話(huà),表達(dá)式z = x &^ y
結(jié)果z的對(duì)應(yīng)的bit位為0,否則z對(duì)應(yīng)的bit位等于x相應(yīng)的bit位的值。
下面的代碼演示了如何使用位操作解釋uint8類(lèi)型值的8個(gè)獨(dú)立的bit位。它使用了Printf函數(shù)的%b參數(shù)打印二進(jìn)制格式的數(shù)字;其中%08b中08表示打印至少8個(gè)字符寬度,不足的前綴部分用0填充。
var x uint8 = 1<<1 | 1<<5
var y uint8 = 1<<1 | 1<<2
fmt.Printf("%08b\n", x) // "00100010", the set {1, 5}
fmt.Printf("%08b\n", y) // "00000110", the set {1, 2}
fmt.Printf("%08b\n", x&y) // "00000010", the intersection {1}
fmt.Printf("%08b\n", x|y) // "00100110", the union {1, 2, 5}
fmt.Printf("%08b\n", x^y) // "00100100", the symmetric difference {2, 5}
fmt.Printf("%08b\n", x&^y) // "00100000", the difference {5}
for i := uint(0); i < 8; i++ {
if x&(1<<i) != 0 { // membership test
fmt.Println(i) // "1", "5"
}
}
fmt.Printf("%08b\n", x<<1) // "01000100", the set {2, 6}
fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4}
(6.5節(jié)給出了一個(gè)可以遠(yuǎn)大于一個(gè)字節(jié)的整數(shù)集的實(shí)現(xiàn)。)
在x<<n
和x>>n
移位運(yùn)算中,決定了移位操作的bit數(shù)部分必須是無(wú)符號(hào)數(shù);被操作的x可以是有符號(hào)數(shù)或無(wú)符號(hào)數(shù)。算術(shù)上,一個(gè)x<<n
左移運(yùn)算等價(jià)于乘以$2^n$,一個(gè)x>>n
右移運(yùn)算等價(jià)于除以$2^n$。
左移運(yùn)算用零填充右邊空缺的bit位,無(wú)符號(hào)數(shù)的右移運(yùn)算也是用0填充左邊空缺的bit位,但是有符號(hào)數(shù)的右移運(yùn)算會(huì)用符號(hào)位的值填充左邊空缺的bit位。因?yàn)檫@個(gè)原因,最好用無(wú)符號(hào)運(yùn)算,這樣你可以將整數(shù)完全當(dāng)作一個(gè)bit位模式處理。
盡管Go語(yǔ)言提供了無(wú)符號(hào)數(shù)的運(yùn)算,但即使數(shù)值本身不可能出現(xiàn)負(fù)數(shù),我們還是傾向于使用有符號(hào)的int類(lèi)型,就像數(shù)組的長(zhǎng)度那樣,雖然使用uint無(wú)符號(hào)類(lèi)型似乎是一個(gè)更合理的選擇。事實(shí)上,內(nèi)置的len函數(shù)返回一個(gè)有符號(hào)的int,我們可以像下面例子那樣處理逆序循環(huán)。
medals := []string{"gold", "silver", "bronze"}
for i := len(medals) - 1; i >= 0; i-- {
fmt.Println(medals[i]) // "bronze", "silver", "gold"
}
另一個(gè)選擇對(duì)于上面的例子來(lái)說(shuō)將是災(zāi)難性的。如果len函數(shù)返回一個(gè)無(wú)符號(hào)數(shù),那么i也將是無(wú)符號(hào)的uint類(lèi)型,然后條件i >= 0
則永遠(yuǎn)為真。在三次迭代之后,也就是i == 0
時(shí),i--語(yǔ)句將不會(huì)產(chǎn)生-1,而是變成一個(gè)uint類(lèi)型的最大值(可能是$2^64-1$),然后medals[i]表達(dá)式運(yùn)行時(shí)將發(fā)生panic異常(§5.9),也就是試圖訪問(wèn)一個(gè)slice范圍以外的元素。
出于這個(gè)原因,無(wú)符號(hào)數(shù)往往只有在位運(yùn)算或其它特殊的運(yùn)算場(chǎng)景才會(huì)使用,就像bit集合、分析二進(jìn)制文件格式或者是哈希和加密操作等。它們通常并不用于僅僅是表達(dá)非負(fù)數(shù)量的場(chǎng)合。
一般來(lái)說(shuō),需要一個(gè)顯式的轉(zhuǎn)換將一個(gè)值從一種類(lèi)型轉(zhuǎn)化為另一種類(lèi)型,并且算術(shù)和邏輯運(yùn)算的二元操作中必須是相同的類(lèi)型。雖然這偶爾會(huì)導(dǎo)致需要很長(zhǎng)的表達(dá)式,但是它消除了所有和類(lèi)型相關(guān)的問(wèn)題,而且也使得程序容易理解。
在很多場(chǎng)景,會(huì)遇到類(lèi)似下面代碼的常見(jiàn)的錯(cuò)誤:
var apples int32 = 1
var oranges int16 = 2
var compote int = apples + oranges // compile error
當(dāng)嘗試編譯這三個(gè)語(yǔ)句時(shí),將產(chǎn)生一個(gè)錯(cuò)誤信息:
invalid operation: apples + oranges (mismatched types int32 and int16)
這種類(lèi)型不匹配的問(wèn)題可以有幾種不同的方法修復(fù),最常見(jiàn)方法是將它們都顯式轉(zhuǎn)型為一個(gè)常見(jiàn)類(lèi)型:
var compote = int(apples) + int(oranges)
如2.5節(jié)所述,對(duì)于每種類(lèi)型T,如果轉(zhuǎn)換允許的話(huà),類(lèi)型轉(zhuǎn)換操作T(x)將x轉(zhuǎn)換為T(mén)類(lèi)型。許多整數(shù)之間的相互轉(zhuǎn)換并不會(huì)改變數(shù)值;它們只是告訴編譯器如何解釋這個(gè)值。但是對(duì)于將一個(gè)大尺寸的整數(shù)類(lèi)型轉(zhuǎn)為一個(gè)小尺寸的整數(shù)類(lèi)型,或者是將一個(gè)浮點(diǎn)數(shù)轉(zhuǎn)為整數(shù),可能會(huì)改變數(shù)值或丟失精度:
f := 3.141 // a float64
i := int(f)
fmt.Println(f, i) // "3.141 3"
f = 1.99
fmt.Println(int(f)) // "1"
任何大小的整數(shù)字面值都可以用以0開(kāi)始的八進(jìn)制格式書(shū)寫(xiě),例如0666;或用以0x或0X開(kāi)頭的十六進(jìn)制格式書(shū)寫(xiě),例如0xdeadbeef。十六進(jìn)制數(shù)字可以用大寫(xiě)或小寫(xiě)字母。如今八進(jìn)制數(shù)據(jù)通常用于POSIX操作系統(tǒng)上的文件訪問(wèn)權(quán)限標(biāo)志,十六進(jìn)制數(shù)字則更強(qiáng)調(diào)數(shù)字值的bit位模式。
當(dāng)使用fmt包打印一個(gè)數(shù)值時(shí),我們可以用%d、%o或%x參數(shù)控制輸出的進(jìn)制格式,就像下面的例子:
o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666"
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
// Output:
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
請(qǐng)注意fmt的兩個(gè)使用技巧。通常Printf格式化字符串包含多個(gè)%參數(shù)時(shí)將會(huì)包含對(duì)應(yīng)相同數(shù)量的額外操作數(shù),但是%之后的[1]
副詞告訴Printf函數(shù)再次使用第一個(gè)操作數(shù)。第二,%后的#
副詞告訴Printf在用%o、%x或%X輸出時(shí)生成0、0x或0X前綴。
字符面值通過(guò)一對(duì)單引號(hào)直接包含對(duì)應(yīng)字符。最簡(jiǎn)單的例子是ASCII中類(lèi)似'a'寫(xiě)法的字符面值,但是我們也可以通過(guò)轉(zhuǎn)義的數(shù)值來(lái)表示任意的Unicode碼點(diǎn)對(duì)應(yīng)的字符,馬上將會(huì)看到這樣的例子。
字符使用%c
參數(shù)打印,或者是用%q
參數(shù)打印帶單引號(hào)的字符:
ascii := 'a'
unicode := '國(guó)'
newline := '\n'
fmt.Printf("%d %[1]c %[1]q\n", ascii) // "97 a 'a'"
fmt.Printf("%d %[1]c %[1]q\n", unicode) // "22269 國(guó) '國(guó)'"
fmt.Printf("%d %[1]q\n", newline) // "10 '\n'"
![]() | ![]() |
更多建議: