## 3.1. 整型

Go語言的數值類型包括幾種不同大小的整形數、浮點數和複數。每種數值類型都決定了對應的大小范圍和是否支持正負符號。讓我們先從整形數類型開始介紹。

Go語言同時提供了有符號和無符號類型的整數運算。這里有int8、int16、int32和int64四種截然不同大小的有符號整形數類型，分别對應8、16、32、64bit大小的有符號整形數，與此對應的是uint8、uint16、uint32和uint64四種無符號整形數類型。

這里還有兩種一般對應特定CPU平台機器字大小的有符號和無符號整數int和uint；其中int是應用最廣泛的數值類型。這兩種類型都有同樣的大小，32或64bit，但是我們不能對此做任何的假設；因爲不同的編譯器卽使在相同的硬件平台上可能産生不同的大小。

Unicode字符rune類型是和int32等價的類型，通常用於表示一個Unicode碼點。這兩個名稱可以互換使用。同樣byte也是uint8類型的等價類型，byte類型一般用於強調數值是一個原始的數據而不是一個小的整數。

最後，還有一種無符號的整數類型uintptr，沒有指定具體的bit大小但是足以容納指針。uintptr類型隻有在底層編程是才需要，特别是Go語言和C語言函數庫或操作繫統接口相交互的地方。我們將在第十三章的unsafe包相關部分看到類似的例子。

不管它們的具體大小，int、uint和uintptr是不同類型的兄弟類型。其中int和int32也是不同的類型，卽使int的大小也是32bit，在需要將int當作int32類型的地方需要一個顯式的類型轉換操作，反之亦然。

其中有符號整數采用2的補碼形式表示，也就是最高bit位用作表示符號位，一個n-bit的有符號數的值域是從$$-2^{n-1}$$到$$2^{n-1}-1$$。無符號整數的所有bit位都用於表示非負數，值域是0到$$2^n-1$$。例如，int8類型整數的值域是從-128到127，而uint8類型整數的值域是從0到255。

下面是Go語言中關於算術運算、邏輯運算和比較運算的二元運算符，它們按照先級遞減的順序的排列：

```
*      /      %      <<       >>     &       &^
+      -      |      ^
==     !=     <      <=       >      >=
&&
||
```

二元運算符有五種優先級。在同一個優先級，使用左優先結合規則，但是使用括號可以明確優先順序，使用括號也可以用於提陞優先級，例如`mask & (1 << 28)`。

對於上表中前兩行的運算符，例如+運算符還有一個與賦值相結合的對應運算符+=，可以用於簡化賦值語句。

算術運算符+、-、`*`和`/`可以適用與於整數、浮點數和複數，但是取模運算符%僅用於整數間的運算。對於不同編程語言，%取模運算的行爲可能併不相同。在Go語言中，%取模運算符的符號和被取模數的符號總是一致的，因此`-5%3`和`-5%-3`結果都是-2。除法運算符`/`的行爲則依賴於操作數是否爲全爲整數，比如`5.0/4.0`的結果是1.25，但是5/4的結果是1，因爲整數除法會向着0方向截斷餘數。

如果一個算術運算的結果，不管是有符號或者是無符號的，如果需要更多的bit位才能正確表示的話，就説明計算結果是溢出了。超出的高位的bit位部分將被丟棄。如果原始的數值是有符號類型，而且最左邊的bit爲是1的話，那麽最終結果可能是負的，例如int8的例子：

```Go
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"
```

兩個相同的整數類型可以使用下面的二元比較運算符進行比較；比較表達式的結果是布爾類型。

```
==    equal to
!=    not equal to
<     less than
<=    less than or equal to
>     greater than
>=    greater than or equal to
```

事實上，布爾型、數字類型和字符串等基本類型都是可比較的，也就是説兩個相同類型的值可以用==和!=進行比較。此外，整數、浮點數和字符串可以根據比較結果排序。許多其它類型的值可能是不可比較的，因此也就可能是不可排序的。對於我們遇到的每種類型，我們需要保證規則的一致性。

這里是一元的加法和減法運算符：

```
+      一元加法 (無效果)
-      負數
```

對於整數，+x是0+x的簡寫，-x則是0-x的簡寫；對於浮點數和複數，+x就是x，-x則是x 的負數。

Go語言還提供了以下的bit位操作運算符，前面4個操作運算符併不區分是有符號還是無符號數：

```
&      位運算 AND
|      位運算 OR
^      位運算 XOR
&^     位清空 (AND NOT)
<<     左移
>>     右移
```

位操作運算符`^`作爲二元運算符時是按位異或（XOR），當用作一元運算符時表示按位取反；也就是説，它返迴一個每個bit位都取反的數。位操作運算符`&^`用於按位置零（AND NOT）：表達式`z = x &^ y`結果z的bit位爲0，如果對應y中bit位爲1的話，否則對應的bit位等於x相應的bit位的值。

下面的代碼演示了如何使用位操作解釋uint8類型值的8個獨立的bit位。它使用了Printf函數的%b參數打印二進製格式的數字；其中%08b中08表示打印至少8個字符寬度，不足的前綴部分用0填充。

```Go
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節給出了一個可以遠大於一個字節的整數集的實現。）

在`x<<n`和`x>>n`移位運算中，決定了移位操作bit數部分必鬚是無符號數；被操作的x數可以是有符號或無符號數。算術上，一個`x<<n`左移運算等價於乘以$$2^n$$，一個`x>>n`右移運算等價於除以$$2^n$$。

左移運算用零填充右邊空缺的bit位，無符號數的右移運算也是用0填充左邊空缺的bit位，但是有符號數的右移運算會用符號位的值填充左邊空缺的bit位。因爲這個原因，最好用無符號運算，這樣你可以將整數完全當作一個bit位模式處理。

盡管Go語言提供了無符號數和運算，卽使數值本身不可能出現負數我們還是傾向於使用有符號的int類型，就像數組的長度那樣，雖然使用uint無符號類型似乎是一個更合理的選擇。事實上，內置的len函數返迴一個有符號的int，我們可以像下面例子那樣處理逆序循環。

```Go
medals := []string{"gold", "silver", "bronze"}
for i := len(medals) - 1; i >= 0; i-- {
	fmt.Println(medals[i]) // "bronze", "silver", "gold"
}
```

另一個選擇對於上面的例子來説將是災難性的。如果len函數返迴一個無符號數，那麽i也將是無符號的uint類型，然後條件`i >= 0`則永遠爲眞。在三次迭代之後，也就是`i == 0`時，i--語句將不會産生-1，而是變成一個uint類型的最大值（可能是$$2^64-1$$），然後medals[i]表達式將發生運行時panic異常（§5.9），也就是試圖訪問一個slice范圍以外的元素。

出於這個原因，無符號數往往隻有在位運算或其它特殊的運算場景才會使用，就像bit集合、分析二進製文件格式或者是哈希和加密操作等。它們通常併不用於僅僅是表達非負數量的場合。

一般來説，需要一個顯式的轉換將一個值從一種類型轉化位另一種類型，併且算術和邏輯運算的二元操作中必鬚是相同的類型。雖然這偶爾會導致需要很長的表達式，但是它消除了所有和類型相關的問題，而且也使得程序容易理解。

在很多場景，會遇到類似下面的代碼通用的錯誤：

```Go
var apples int32 = 1
var oranges int16 = 2
var compote int = apples + oranges // compile error
```

當嚐試編譯這三個語句時，將産生一個錯誤信息：

```
invalid operation: apples + oranges (mismatched types int32 and int16)
```

這種類型不匹配的問題可以有幾種不同的方法脩複，最常見方法是將它們都顯式轉型爲一個常見類型：

```Go
var compote = int(apples) + int(oranges)
```

如2.5節所述，對於每種類型T，如果轉換允許的話，類型轉換操作T(x)將x轉換爲T類型。許多整形數之間的相互轉換併不會改變數值；它們隻是告訴編譯器如何解釋這個值。但是對於將一個大尺寸的整數類型轉爲一個小尺寸的整數類型，或者是將一個浮點數轉爲整數，可能會改變數值或丟失精度：

```Go
f := 3.141 // a float64
i := int(f)
fmt.Println(f, i) // "3.141 3"
f = 1.99
fmt.Println(int(f)) // "1"
```

浮點數到整數的轉換將丟失任何小數部分，然後向數軸零方向截斷。你應該避免對可能會超出目標類型表示范圍的數值類型轉換，因爲截斷的行爲可能依賴於具體的實現：

```Go
f := 1e100  // a float64
i := int(f) // 結果依賴於具體實現
```

任何大小的整數字面值都可以用以0開始的八進製格式書寫，例如0666；或用以0x或0X開頭的十六進製格式書寫，例如0xdeadbeef。十六進製數字可以用大寫或小寫字母。如今八進製數據通常用於POSIX操作繫統上的文件訪問權限標誌，十六進製數字則更強調數字值的bit位模式。

當使用fmt包打印一個數值時，我們可以用%d、%o或%x參數控製輸出的進製格式，就像下面的例子：

```Go
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
```

請註意fmt的兩個使用技巧。通常Printf格式化字符串包含多個%參數時將會包含對應相同數量的額外操作數，但是%之後的`[1]`副詞告訴Printf函數再次使用第一個操作數。第二，%後的`#`副詞告訴Printf在用%o、%x或%X輸出時生成0、0x或0X前綴。

字符面值通過一對單引號直接包含對應字符。最簡單的例子是ASCII中類似'a'寫法的字符面值，但是我們也可以通過轉義的數值來表示任意的Unicode碼點對應的字符，馬上將會看到這樣的例子。

字符使用`%c`參數打印，或者是用`%q`參數打印帶單引號的字符：

```Go
ascii := 'a'
unicode := '国'
newline := '\n'
fmt.Printf("%d %[1]c %[1]q\n", ascii)   // "97 a 'a'"
fmt.Printf("%d %[1]c %[1]q\n", unicode) // "22269 国 '国'"
fmt.Printf("%d %[1]q\n", newline)       // "10 '\n'"
```


