gopl-zh.github.com/ch4/ch4-01.md

127 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## 4.1. 數組
數組是一個由固定長度的特定類型元素組成的序列一個數組可以由零個或多個元素組成。因爲數組的長度是固定的因此在Go語言中很少直接使用數組。和數組對應的類型是Slice切片它是可以增長和收縮動態序列slice功能也更靈活但是要理解slice工作原理的話需要先理解數組。
數組的每個元素可以通過索引下標來訪問索引下標的范圍是從0開始到數組長度減1的位置。內置的len函數將返迴數組中元素的個數。
```Go
var a [3]int // array of 3 integers
fmt.Println(a[0]) // print the first element
fmt.Println(a[len(a)-1]) // print the last element, a[2]
// Print the indices and elements.
for i, v := range a {
fmt.Printf("%d %d\n", i, v)
}
// Print the elements only.
for _, v := range a {
fmt.Printf("%d\n", v)
}
```
默認情況下數組的每個元素都被初始化爲元素類型對應的零值對於數字類型來説就是0。我們也可以使用數組字面值語法用一組值來初始化數組
```Go
var q [3]int = [3]int{1, 2, 3}
var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"
```
在數組字面值中,如果在數組的長度位置出現的是“...”省略號則表示數組的長度是根據初始化值的個數來計算。因此上面q數組的定義可以簡化爲
```Go
q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"
```
數組的長度是數組類型的一個組成部分,因此[3]int和[4]int是兩種不同的數組類型。數組的長度必鬚是常量表達式因爲數組的長度需要在編譯階段確定。
```Go
q := [3]int{1, 2, 3}
q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int
```
我們將會發現數組、slice、map和結構體字面值的寫法都很相似。上面的形式是直接提供順序初始化值序列但是也可以指定一個索引和對應值列表的方式初始化就像下面這樣
```Go
type Currency int
const (
USD Currency = iota // 美元
EUR // 歐元
GBP // 英鎊
RMB // 人民幣
)
symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}
fmt.Println(RMB, symbol[RMB]) // "3 ¥"
```
在這種形式的數組字面值形式中,初始化索引的順序是無關緊要的,而且沒用到的索引可以省略,和前面提到的規則一樣,未指定初始值的元素將用零值初始化。例如,
```Go
r := [...]int{99: -1}
```
定義了一個含有100個元素的數組r最後一個元素被初始化爲-1其它元素都是用0初始化。
如果一個數組的元素類型是可以相互比較的,那麽數組類型也是可以相互比較的,這時候我們可以直接通過==比較運算符來比較兩個數組,隻有當兩個數組的所有元素都是相等的時候數組才是相等的。不相等比較運算符!=遵循同樣的規則。
```Go
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int
```
作爲一個眞實的例子crypto/sha256包的Sum256函數對一個任意的字節slice類型的數據生成一個對應的消息摘要。消息摘要有256bit大小因此對應[32]byte數組類型。如果兩個消息摘要是相同的那麽可以認爲兩個消息本身也是相同譯註理論上有HASH碼碰撞的情況但是實際應用可以基本忽略如果消息摘要不同那麽消息本身必然也是不同的。下面的例子用SHA256算法分别生成“x”和“X”兩個信息的摘要
```Go
gopl.io/ch4/sha256
import "crypto/sha256"
func main() {
c1 := sha256.Sum256([]byte("x"))
c2 := sha256.Sum256([]byte("X"))
fmt.Printf("%x\n%x\n%t\n%T\n", c1, c2, c1 == c2, c1)
// Output:
// 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
// 4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015
// false
// [32]uint8
}
```
上面例子中兩個消息雖然隻有一個字符的差異但是生成的消息摘要則幾乎有一半的bit位是不相同的。需要註意Printf函數的%x副詞參數它用於指定以十六進製的格式打印數組或slice全部的元素%t副詞參數是用於打印布爾型數據%T副詞參數是用於顯示一個值對應的數據類型。
當調用一個函數的時候函數的每個調用參數將會被賦值給函數內部的參數變量所以函數參數變量接收的是一個複製的副本併不是原始調用的變量。因爲函數參數傳遞的機製導致傳遞大的數組類型將是低效的併且對數組參數的任何的脩改都是發生在複製的數組上併不能直接脩改調用時原始的數組變量。在這個方面Go語言對待數組的方式和其它很多編程語言不同其它編程語言可能會隱式地將數組作爲引用或指針對象傳入被調用的函數。
當然,我們可以顯式地傳入一個數組指針,那樣的話函數通過指針對數組的任何脩改都可以直接反饋到調用者。下面的函數用於給[32]byte類型的數組清零
```Go
func zero(ptr *[32]byte) {
for i := range ptr {
ptr[i] = 0
}
}
```
其實數組字面值[32]byte{}就可以生成一個32字節的數組。而且每個數組的元素都是零值初始化也就是0。因此我們可以將上面的zero函數寫的更簡潔一點
```Go
func zero(ptr *[32]byte) {
*ptr = [32]byte{}
}
```
雖然通過指針來傳遞數組參數是高效的而且也允許在函數內部脩改數組的值但是數組依然是殭化的類型因爲數組的類型包含了殭化的長度信息。上面的zero函數併不能接收指向[16]byte類型數組的指針而且也沒有任何添加或刪除數組元素的方法。由於這些原因除了像SHA256這類需要處理特定大小數組的特例外數組依然很少用作函數參數相反我們一般使用slice來替代數組。
**練習 4.1** 編寫一個函數計算兩個SHA256哈希碼中不同bit的數目。參考2.6.2節的PopCount函數。)
**練習 4.2** 編寫一個程序默認打印標準輸入的以SHA256哈希碼也可以通過命令行標準參數選擇SHA384或SHA512哈希算法。