這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Slice 和 Array 維度是一維
層級:新手入門級
Go 看上去支援多維的 Array 和 Slice,但是其實不然。儘管可以建立 Array 的 Array,也可以建立 Slice 的 Slice。對於依賴多維 Array 的計算密集型的程式,無論是從效能還是複雜程度,Go 都不是最佳選擇。
當然,如果你選擇建立嵌套的 Array 與嵌套的 Slice,那麼你就得自己負責進行索引、進行下表檢查、以及 Array 增長時的記憶體配置。嵌套 Slice 分為兩種,Slice 中嵌套獨立的 Slice,或者 Slice 中嵌套共用資料的 Slice。
使用嵌套的獨立 Slice 建立多維的 Array 需要兩步。第一步,建立外圍 Slice,然後分配每個內部的 Slice。內部的 Slice 是獨立的,可以對每個單獨的內部 Slice 進行縮放。
package mainfunc main() { x := 2 y := 4 table := make([][]int,x) for i:= range table { table[i] = make([]int,y) }}
使用嵌套、共用資料的 Slice 建立多維 Array 需要三步。第一,建立資料“容器”,第二部,建立外圍 Slice,第三部,對內部的 Slice 進行初始化。
package mainimport "fmt"func main() { h, w := 2, 4 raw := make([]int,h*w) for i := range raw { raw[i] = i } fmt.Println(raw,&raw[4]) //prints: [0 1 2 3 4 5 6 7] <ptr_addr_x> table := make([][]int,h) for i:= range table { table[i] = raw[i*w:i*w + w] } fmt.Println(table,&table[1][0]) //prints: [[0 1 2 3] [4 5 6 7]] <ptr_addr_x>}
Go 語言也有對於支援多維 Array 和 Slice 的提案,不過不要期待太多。Go 語言官方將這些需求分在“低優先順序”組中。
試圖訪問不存在的 Map 索引值
層級:新手入門級
並不能在所有情況下都能通過判斷 map 的記錄值是不是 nil 判斷記錄是否存在。在 Go 語言中,對於“零值”是 nil 的資料類型可以這樣判斷,但是其他的資料類型不可以。簡而言之,這種做法並不可靠(例如布爾變數的“零值”是 false)。最可靠的做法是檢查 map 記錄的第二傳回值。
錯誤碼:
package mainimport "fmt"func main() { x := map[string]string{"one":"a","two":"","three":"c"} if v := x["two"]; v == "" { //incorrect fmt.Println("no entry") }}
修正代碼:
package mainimport "fmt"func main() { x := map[string]string{"one":"a","two":"","three":"c"} if _,ok := x["two"]; !ok { fmt.Println("no entry") }}
String 不可變
層級:新手入門級
對於 String 中單個字元的操作會導致編譯失敗。String 是帶有一些附加屬性的唯讀位元組片(Byte Slices)。所以如果想要對 String 操作的話,應當使用位元組片操作,而不是將它轉換為 String 類型。
錯誤資訊:
package mainimport "fmt"func main() { x := "text" x[0] = 'T' fmt.Println(x)}
錯誤資訊:
/tmp/sandbox305565531/main.go:7: cannot assign to x[0]
修正代碼:
package mainimport "fmt"func main() { x := "text" xbytes := []byte(x) xbytes[0] = 'T' fmt.Println(string(xbytes)) //prints Text}
注意這裡的操作並不就是最正確的操作,因為有些字元可能會儲存在多個位元組中。如果你的開發情景有這種情況的話,需要先把 String 轉換為 rune 格式。即便是 rune,一個字元也可能會儲存在多個 rune 中,因此 Go String 也表現為位元組序列(String Sequences)。Rune 一般理解為“字元”,“一個字元也可能會儲存在多個 rune 中”的情況我們將會在下面提到。
String 與 Byte Slice 的轉換
層級:新手入門級
當將 String 類型和 Byte Slice 類型互相轉化的時候,得到的新資料都是原資料的拷貝,而不是原資料。類型轉化和切片重組(Resliciing)不一樣,切片重組後的變數仍然指向原變數,而類型轉換後的變數指向原變數的拷貝。
Go 語言已經對 []byte 和 String 類型的互相轉化做了最佳化,並且還會繼續最佳化。
The first optimization avoids extra allocations when []byte keys are used to lookup entries in map[string] collections: m[string(key)].
一個最佳化是
The second optimization avoids extra allocations in for range clauses where strings are converted to []byte: for i,v := range []byte(str) {...}.
String 與下標
層級:新手入門級
和其他語言不同,String 的下表傳回值是 Byte 類型的值,而不是字元類型。
package mainimport "fmt"func main() { x := "text" fmt.Println(x[0]) //print 116 fmt.Printf("%T",x[0]) //prints uint8}
如果需要在 UTF8 類型的 String 中取出指定字元,那麼需要用到 unicode/utf8 與實驗性的 utf8string 包。utf8string 包包含 AT() 方法,可以取出字元,也可以將 String 轉換為 Rune SLice。
String並不一定是UTF8格式
層級:新手入門級
String 類型不一定是 UTF8 格式,String 中也可以包含自訂的文字/位元組。只有需要將字串顯示出來的時候才需要用 UTF8 格式,其他情況下可以隨便用轉義來表示任一字元。
可以使用 unicode/utf8 包中體重的 ValidString() 方法判斷是否是 UTF8 類型的文本。
package mainimport ( "fmt" "unicode/utf8")func main() { data1 := "ABC" fmt.Println(utf8.ValidString(data1)) //prints: true data2 := "A\xfeC" fmt.Println(utf8.ValidString(data2)) //prints: false}
String 長度
層級:新手入門級
Python 開發人員的代碼:
data = u'' print(len(data)) #prints: 1
轉換成類似的 Go 代碼如下:
package mainimport "fmt"func main() { data := "" fmt.Println(len(data)) //prints: 3}
Go 的 len() 方法和 Python 的並不相同,和 Python 的 len 方法等價的 Go 方法是 RuneCountInString。
package mainimport ( "fmt" "unicode/utf8")func main() { data := "" fmt.Println(utf8.RuneCountInString(data)) //prints: 1}
當然有些情況(例如法語)的情況,RuneCountInString 也並不能完全返回字元數目,因為有些字元是使用多個字元的方式進行儲存的。
package mainimport ( "fmt" "unicode/utf8")func main() { data := "é" fmt.Println(len(data)) //prints: 3 fmt.Println(utf8.RuneCountInString(data)) //prints: 2}
多行的 Slice Arry 和 Map 賦值中缺少逗號
Missing Comma In Multi-Line Slice, Array, and Map Literals
層級:新手入門級
錯誤資訊:
package mainfunc main() { x := []int{ 1, 2 //error } _ = x}
Compile Errors:
/tmp/sandbox367520156/main.go:6: syntax error: need trailing comma before newline in composite literal /tmp/sandbox367520156/main.go:8: non-declaration statement outside function body /tmp/sandbox367520156/main.go:9: syntax error: unexpected }
修正代碼:
package mainfunc main() { x := []int{ 1, 2, } x = x y := []int{3,4,} //no error y = y}
當然,如果把這些東西寫在一行中,可以不加最後的逗號。
log.Fatal 與 log.Panic 在後台悄悄做了一些事情
層級:新手入門級
日誌庫提供了不同層級的日誌記錄,如果使用 Fatal 和 Panic 層級的日誌,那麼記錄完這條日誌後,應用程式便會退出而不會繼續執行。
package mainimport "log"func main() { log.Fatalln("Fatal Level: log entry") //app exits here log.Println("Normal Level: log entry")}
內建的資料結構操作是無鎖的
層級:新手入門級
雖然 Go 語言天生高並發,但是 Go 並沒有考慮到資料安全。為了實現“原子化”的資料操作,開發人員需要自己對資料操作進行加鎖。當然, goroutine 和 channel 以及 sync 都是手動加鎖的好方案。