[Go] The Little Go Book 概論

程式語言:Go
The Little Go Book 官網
Go 官網
Go 規格
線上 Go 編譯工具

簡介:Go 的基本語法應用

Terminal 基本指令 (Env Path 需有 C:\Go\bin)
go             # 顯示可用指令
go build       # 編譯執行檔於當前目錄
go clean       # 將 build 產生的檔案都刪除 (install 的不會刪)
go doc         # 顯示 help 文件,例: go doc fmt
                   # godoc -http=:6060 可到 http://localhost:6060 檢視文件。
go env         # 顯示環境變數
                   # GOROOT: Go 的安裝位置
                   # GOPATH: Go 的工作目錄
go bug         # 回報 bug
go fix         # 修正移植到新版本 GO 的錯誤 code
go fmt         # 排版代碼
go generate    # 用於批量執行命令
go get         # 安裝套件,可直接使用 github 網址
go install     # 產生執行檔於 $GOPATH/bin,或是安裝 package 於 $GOPATH/pkg
go list        # 列出所有套件。例 go list ./... (... 表示在目錄下所有檔案與目錄)
go run         # 直接執行 Go code,go run -x main.go 可提供詳細資訊
go test        # 自動讀取名為 *_test.go 的文件,並產生可運行測試的執行檔
go tool        # 執行特定工具,例:fix or vet
go version     # 版本
go vet         # 静態檢查,並回報可能的 bug
# 可使用瀏覽器連接 http://localhost:5000 或 http://主機IP:5000 查詢文件
godoc -http=:5000

# 可檢查 test 覆蓋率
go test -coverprofile=c.out
go tool cover -html=c.out

# 觀察性能的瓶頸,線上的時間表示以下所有區塊運行的時間
go test -bench . -cpuprofile cpu.out
go tool pprof cpu.out
web # 用瀏覽器觀看圖,需安裝 Graphviz

# race 檢測
go run -race main.go

# trace 程式碼
# 產生 trace file 三種方法
runtime/trace.Start
net/http/pprof package
go test -trace
# 其中之一的用法
go test -trace trace.out pkg
# 用瀏覽器觀看資料
go tool trace trace.out
官方介紹一整套用於診斷 Go 程序中的邏輯和性能問題的 API 和工具

基本概念
  • Go 是需要編譯、靜態型別、具有類似於 C 的語法與擁有 Garbage Collection 等特性的語言
  • Go 語言裡,程式的進入點是在 main package 裡面的 main function
  • import 關鍵字可以用來引用相關套件
    1. package main
    2.  
    3. import (
    4. "fmt"
    5. )
    6.  
    7. func main() {
    8. fmt.Println("123")
    9. }
    Import declaration          Local name of Sin
    
    import   "lib/math"         math.Sin
    import m "lib/math"         m.Sin
    import . "lib/math"         Sin
    import _ "lib/math"         only for init
    • 如果 import 了套件, 卻沒有使用,會出現錯誤
  • function 外的每個語法塊都必須以關鍵字開始( var 、 func 、等等)
  • Go 沒有物件或繼承,也沒有許多物件導向的概念,比如說多形或重載

變數與宣告
  • function 內如果宣告了變數,卻沒有使用,會出現錯誤
  • 宣告變數若沒提供初始值,會有 default 值
    • int: 0
    • bool: false
    • string: ""
  • 各種宣告方法如下
    1. package main
    2.  
    3. var ag int = 3 // 可行,但很多餘,可拿掉 int,由值推論
    4. var bg int // 宣告成 int
    5. var cg = 10 // 自行推論 type
    6. var dg, eg int // dg 跟 eg 都是 int
    7. // fg := 0 // 此方法無法宣告 global variable
    8. var ( // 記得要不同行,不然會錯
    9. gg bool = false // 可行,但很多餘,可拿掉 bool,由值推論
    10. hg int
    11. ig = "hello"
    12. jg = '字' // rune
    13. kg = `\n原始字串,可用在 regexp
    14. 可多行,但會自動去掉換行的\r
    15. 保證在所有平台上的值都是一樣的,例:Windows系統會把回車和換行一起放入文本文件中`
    16. )
    17.  
    18. func main() {
    19. println(ag, bg, cg, dg, eg, gg, hg, ig, jg, kg)
    20.  
    21. var al int = 3 // 可行,但很多餘,可拿掉 int,由值推論
    22. var bl int // 宣告成 int
    23. var cl = 10 // 自行推論 type
    24. var dl, el int // dl 跟 el 都是 int
    25. fl := 0
    26. gl, hl, il := 0, true, "tacolin" // 這樣就可以不同型別寫在同一行
    27. var ( // 記得要不同行,不然會錯
    28. jl bool = false // 可行,但很多餘,可拿掉 bool,由值推論
    29. kl int
    30. ll = "hello"
    31. )
    32.  
    33. println(al, bl, cl, dl, el, fl, gl, hl, il, jl, kl, ll)
    34. }
  • 要記住 := 同時用於宣告變數以及為它賦值,而變數不能被宣告兩次
    但這種情況是允許的,因為至少有一個新變數,但 power 的 type 仍無法被改變
    而若是在不同的 scope 中,則會被視為重新宣告新變數
    1. package main
    2.  
    3. func main() {
    4. power := 1000
    5. name, power := "Goku", 9000
    6. println(name, power)
    7.  
    8. if true {
    9. power := 2000
    10. println(power)
    11. }
    12. println(power)
    13. }
  • _ 空白識別符號
    • 特別用在返回值不需要被實際指派的時候使用
      可以不管返回的類型,重複的使用 _ 識別符號,無需事先宣告
  • 基本類型
    • bool
    • string
    • int, int8, int16, int32, int64
    • uint uint8, uint16, uint32, uint64, uintptr
    • byte // uint8 的別名
    • rune // int32 的別名,也代表一個 Unicode 碼
    • float32, float64, complex64, complex128
  • const
    • 宣告如下
      1. package main
      2.  
      3. //Pi 3.14
      4. const Pi = 3.14
      5.  
      6. func main() {
      7. println("Happy", Pi, "Day")
      8.  
      9. const World = "世界"
      10. println("Hello", World)
      11.  
      12. const Truth bool = true
      13. println("Go rules?", Truth)
      14. }
    • 數值常數是高精度的值
      一個未指定類型的常數將由上下文來決定其類型
      1. package main
      2.  
      3. func needInt(x int) int { return x/20 + 1 }
      4. func needFloat(x float64) float64 {
      5. return x * 0.1
      6. }
      7.  
      8. func main() {
      9. const (
      10. Big = 1 << 100
      11. Small = Big >> 99
      12. )
      13.  
      14. println(needInt(Small))
      15. println(needFloat(Small))
      16. // overflow
      17. // println(needInt(Big))
      18. println(needFloat(Big))
      19. }
  • pointer
    • 宣告方法
      1. package main
      2.  
      3. func main() {
      4. a := 1
      5. println(&a)
      6. change(&a)
      7. println(a)
      8. }
      9.  
      10. func change(b *int) {
      11. *b++
      12. }

function 宣告
  • function 是一級型別 (first-class function)
  • 可以用在任何地方 - 當作一個欄位型別、參數、回傳值
  • 宣告方法
    1. package main
    2.  
    3. // 輸入一個變數,無回傳變數
    4. func log(message string) {
    5. }
    6.  
    7. // 輸入兩個變數,回傳一個變數
    8. func add(a int, b int) int {
    9. return a + b
    10. }
    11.  
    12. // 輸入兩個變數,可合併簡寫,回傳一個變數
    13. func minus(a, b int) int {
    14. return a - b
    15. }
    16.  
    17. // 輸入一個變數,回傳兩個變數
    18. func power(name string) (int, bool) {
    19. return 1, true
    20. }
    21.  
    22. // 無輸入變數,回傳兩個變數,可事先宣告
    23. func positin() (x, y int) {
    24. x = 1
    25. y = 2
    26. return
    27. }
    28.  
    29. func main() {
    30. // _ 空白識別符號是特別用在返回值不需要被實際指派的時候使用
    31. // 可以不管返回的類型,重複的使用 _ 識別符號
    32. // 但無需宣告其 type
    33. _ = add(1, 2)
    34. power, _ := power("goku")
    35. println(power)
    36. }
  • function 內不能再有 function,除非綁定在變數上,也就是 closure
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. func adder() func(int) int {
    6. sum := 0
    7. f := func(x int) int {
    8. sum += x
    9. return sum
    10. }
    11. return f
    12. }
    13.  
    14. func main() {
    15. pos, neg := adder(), adder()
    16. for i := 0; i < 10; i++ {
    17. fmt.Println(pos(i), neg(-2*i))
    18. }
    19. }
  • function 傳遞參數方式
    • 除了 Map, slices
    • 其餘皆是 copy 方式,甚至是 array,若希望更改值,請用 pointer
    • 但使用 slice 的 append 時,若 cap 不夠時,會被重新分配新的底層 array

control
  • for
    1. package main
    2. func main() {
    3. sum := 0
    4. for i := 0; i < 10; i++ {
    5. sum += i
    6. }
    7. println(sum)
    8.  
    9. sum = 1
    10. // for ; sum < 1000; { 前後可為空,但 go fmt 後會被移除,類似 while
    11. for sum < 1000 {
    12. sum += sum
    13. }
    14. println(sum)
    15.  
    16. sum = 1
    17. // 無窮迴圈
    18. for {
    19. sum += 1
    20. if sum == 100 {
    21. break
    22. }
    23. }
    24. println(sum)
    25.  
    26. scores := [4]int{9001, 9333, 212, 33}
    27. // for in array
    28. for index, value := range scores {
    29. println(index, value)
    30. }
    31.  
    32. // count
    33. n := 0
    34. for range "Hello, 世界" {
    35. n++
    36. }
    37. println(n)
    38. }
  • if
    1. package main
    2.  
    3. func main() {
    4. i := 0
    5. if i == 0 {
    6. println(i)
    7. }
    8.  
    9. // 初始化 i,此時的 i 作用域僅在 if else 範圍之內
    10. if i := 2; i == 2 {
    11. println(i) // 2
    12. }
    13. println(i) // 0
    14.  
    15. if i > 0 {
    16. } else {
    17. println(i)
    18. }
    19. }
  • switch
    1. package main
    2.  
    3. func main() {
    4. i := 2
    5. switch i {
    6. case 1:
    7. // 執行下一個
    8. fallthrough
    9. case 2:
    10. println("two")
    11. case 3, 4: // 3 or 4
    12. println("three, four")
    13. default:
    14. println("number")
    15. }
    16.  
    17. switch {
    18. case i == 1 || i == 2:
    19. println("one or two")
    20. case i == 4:
    21. println("four")
    22. case i > 5 && i < 7:
    23. println("six")
    24. default:
    25. println("number")
    26. }
    27. }

struct
  • 宣告方法
    1. package main
    2.  
    3. import (
    4. "fmt"
    5. )
    6.  
    7. // Saiyan struct
    8. type Saiyan struct {
    9. Name string
    10. Power int
    11. }
    12.  
    13. // 事先宣告值
    14. var a = Saiyan{
    15. Name: "a",
    16. Power: 9000,
    17. }
    18.  
    19. func main() {
    20. // 後來指定值
    21. b := Saiyan{}
    22. b.Name = "b"
    23. b.Power = 1000
    24.  
    25. // 依順序指定
    26. c := Saiyan{"c", 8000}
    27.  
    28. // default 值
    29. d := Saiyan{}
    30.  
    31. // 指標宣告
    32. e := new(Saiyan)
    33. // 結果同上
    34. f := &Saiyan{}
    35.  
    36. fmt.Println(a, b, c, d, e, f)
    37. }
  • method
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. type Saiyan struct {
    6. Name string
    7. Power int
    8. }
    9.  
    10. func (s *Saiyan) Super() {
    11. s.Power += 10000
    12. }
    13.  
    14. func (s Saiyan) Lower() {
    15. s.Power -= 100
    16. }
    17.  
    18. func main() {
    19. a := Saiyan{"a", 9001}
    20. a.Super()
    21. a.Lower() // 不會改變值
    22.  
    23. // 結果同上
    24. b := &Saiyan{"b", 9001}
    25. b.Super()
    26. b.Lower() // 不會改變值
    27.  
    28. fmt.Println(a.Power, b.Power) // 將列印出 19001
    29. }
    可以利用 type 對基本類型增加 method
    1. package main
    2.  
    3. import (
    4. "fmt"
    5. "math"
    6. )
    7.  
    8. type MyFloat float64
    9.  
    10. func (f MyFloat) Abs() float64 {
    11. if f < 0 {
    12. return float64(-f)
    13. }
    14. return float64(f)
    15. }
    16.  
    17. func main() {
    18. f := MyFloat(-math.Sqrt2)
    19. fmt.Println(f.Abs())
    20. }
  • 包含同類型
    1. package main
    2.  
    3. import (
    4. "fmt"
    5. )
    6.  
    7. // Saiyan struct
    8. type Saiyan struct {
    9. Name string
    10. Power int
    11. Father *Saiyan //include 自己必須用指標才可以
    12. }
    13.  
    14. func main() {
    15. a := &Saiyan{
    16. Name: "Gohan",
    17. Power: 1000,
    18. Father: &Saiyan{
    19. Name: "Goku",
    20. Power: 9001,
    21. Father: nil,
    22. },
    23. }
    24. fmt.Println(a)
    25. }
  • Composition
    • 因為沒有提供明確的欄位名稱,所以可以透過組合隱性的存取這個欄位和 method
      但 Go 的編譯器仍會給他一個欄位名稱,但在上層若有同樣的屬性,例:Name,會以上層為主
      1. package main
      2.  
      3. import (
      4. "fmt"
      5. )
      6.  
      7. type Person struct {
      8. Name string
      9. }
      10.  
      11. func (p *Person) Introduce() {
      12. fmt.Printf("Hi, I'm %s\n", p.Name)
      13. }
      14.  
      15. type Saiyan struct {
      16. *Person
      17. //Name string // 會覆寫掉 Person 的 Name
      18. Power int
      19. }
      20.  
      21. func main() {
      22. a := &Saiyan{
      23. Person: &Person{"a"},
      24. Power: 9001,
      25. }
      26. a.Introduce()
      27. fmt.Println(a.Name)
      28. fmt.Println(a.Person.Name)
      29. }
  • 可視性
    • 可被外部引用
      • 宣告類型或 method 時以大寫開頭
    • 不可被外部引用,同一個套件中的程式碼才能夠存取這些欄位
      • 宣告類型或 method 時以小寫開頭

Map、Array、Slice
  • Array
    • 宣告後即固定大小
    • 宣告方法
      1. package main
      2.  
      3. func main() {
      4. var a [10]int
      5. a[0] = 339
      6. println(len(a)) // 10
      7.  
      8. b := [4]int{9001, 9333, 212, 33}
      9. for index, value := range b {
      10. println(index, value)
      11. }
      12. }
  • Slice
    • 宣告後仍可變動大小
    • 類似 python 的 list
    • 宣告方法
      1. package main
      2.  
      3. import "fmt"
      4.  
      5. func main() {
      6. // 已有初始值
      7. a := []string{"leto", "jessica", "paul"}
      8. fmt.Println(a)
      9.  
      10. // 宣告長度是 10 ,容量是 10 的 slice
      11. b := make([]bool, 10)
      12. fmt.Println(b)
      13.  
      14. // 宣告長度是 0 ,容量是 0 的 slice,等同 nil,只能用 append 新增
      15. var c []string
      16. fmt.Println(c)
      17.  
      18. // 宣告長度是 0 ,容量是 20 的 slice
      19. d := make([]int, 0, 20)
      20. fmt.Println(d)
      21. }
      22.  
    • 賦值方法
      1. package main
      2.  
      3. import "fmt"
      4.  
      5. func main() {
      6. // 建立一個長度是 0 ,容量是 10 的 slice
      7. a := make([]int, 0, 10)
      8. // c[7] = 9033 無法這麼做,因長度為 0
      9. a = append(a, 5)
      10. fmt.Println(len(a), a) // 1 [5]
      11. a = a[0:8] // 為了將值放在 c[7],需先擴展,前提是容量夠
      12. a[7] = 9033
      13. fmt.Println(a) // [5 0 0 0 0 0 0 9033]
      14.  
      15. b := make([]int, 5)
      16. b = append(b, 9332) // 因長度已有 5,所以是 append 到第六個
      17. fmt.Println(b) // [0 0 0 0 0 9332]
      18. }
    • 每次擴展的大小
      1. package main
      2.  
      3. import "fmt"
      4.  
      5. func main() {
      6. scores := make([]int, 0, 5)
      7. c := cap(scores)
      8. fmt.Println(c)
      9.  
      10. for i := 0; i < 25; i++ {
      11. scores = append(scores, i)
      12.  
      13. // 如果容量改變了
      14. // Go 為了容納新的資料,會增加陣列的長度
      15. // window64 是每次兩倍,所以是 5, 10, 20, 40
      16. // window32 不見得是每次兩倍,而是 5, 12, 24, 48
      17. if cap(scores) != c {
      18. c = cap(scores)
      19. fmt.Println(c)
      20. }
      21. }
      22. }
    • 值會被改變,可用 copy 預防
      1. package main
      2.  
      3. import "fmt"
      4.  
      5. func main() {
      6. scores := []int{1, 2, 3, 4, 5}
      7. slice := scores[2:4]
      8. slice[0] = 999
      9. fmt.Println(scores) // [1 2 999 4 5]
      10. }
    • Go 不支援負索引值
  • Map
    • 類似 python 的 dictionary
    • 宣告方法
      1. package main
      2.  
      3. import (
      4. "fmt"
      5. )
      6.  
      7. func main() {
      8. a := make(map[string]int)
      9. a["goku"] = 9001
      10. fmt.Println(len(a), a) // 1 map[goku:9001]
      11.  
      12. //利用雙賦值,檢查是否存在
      13. power, exists := a["vegeta"]
      14. fmt.Println(power, exists) // 0 false
      15.  
      16. // 刪除,沒有任何的回傳,可以呼叫一個不存在的 key
      17. delete(a, "goku")
      18. delete(a, "noexist")
      19. fmt.Println(len(a), a) // 0 map[]
      20.  
      21. // 預先定義初始大小,有助於提升效能
      22. b := make(map[string]int, 100)
      23. fmt.Println(len(b), b) // 0 map[]
      24.  
      25. c := map[string]int{
      26. "goku": 9001,
      27. "gohan": 2044,
      28. }
      29. // 無順序性
      30. for key, value := range c {
      31. fmt.Println(key, value)
      32. }
      33.  
      34. // nil,此時無法賦值,需先用 make
      35. var e map[string]int
      36. fmt.Println(e == nil) // true
      37. e = make(map[string]int)
      38. }
    • 無法直接對 map 中的 struct 做出修改
      因 map 中的 value 是不可尋址的
      原本 struct 的資料可能在地址A,但當 map 擴展時地址A就不是原來的資料,所以go不允许寫入數據
      兩種解法:用 struct pointer or 重新賦值
      但在 Go2 可能會可行
      1. package main
      2.  
      3. import (
      4. "fmt"
      5. )
      6.  
      7. type s struct {
      8. a int
      9. }
      10.  
      11. func main() {
      12. m1 := make(map[string]s)
      13. m1["k"] = s{a: 5}
      14. fmt.Printf("%v\n", m1)
      15.  
      16. //重新賦值
      17. tmp := m1["k"]
      18. tmp.a = 8
      19. m1["k"] = tmp
      20. fmt.Printf("%v\n", m1)
      21.  
      22. //cannot assign to struct field m["k"].a in map
      23. //m["k"].a = 8
      24.  
      25. //struct pointer
      26. m2 := make(map[string]*s)
      27. m2["k"] = &s{a: 5}
      28. fmt.Printf("%v\n", m2["k"])
      29.  
      30. m2["k"].a = 8
      31. fmt.Printf("%v\n", m2["k"])
      32. }
      33.  

package
  • 位置:$GOPATH/src
  • package 與 folder 建議同名,可以不同名,但一個資料夾只能有一個 pakcage(不包含 sub-folder)
    import 使用的是路徑,而使用時需用宣告的 package 名字
    import xxx/xxxx/folderName
    
    packageName.function()
  • 檔案不見得需同名,而且可以多個
  • 範例
    • 架構
      $GOPATH/src
          - shopping
            pricecheck.go
            - db
              models.go
              func.go
          - main
            main.go
      
    • 程式碼
      • main.go
        1. package main
        2.  
        3. import (
        4. "fmt"
        5. "shopping"
        6. )
        7.  
        8. func main() {
        9. fmt.Println(shopping.PriceCheck(4343))
        10. }
      • pricecheck.go
        1. package shopping
        2.  
        3. import (
        4. "shopping/db"
        5. )
        6.  
        7. func PriceCheck(itemId int) (float64, bool) {
        8. item := db.LoadItem(itemId)
        9. if item == nil {
        10. return 0, false
        11. }
        12. return item.Price, true
        13. }
      • models.go
        1. package db
        2.  
        3. type Item struct {
        4. Price float64
        5. }
      • func.go
        1. package db
        2.  
        3. func LoadItem(id int) *Item {
        4. return &Item{
        5. Price: 9.001,
        6. }
        7. }
    • 避免循環引用,可使用 interface
      • 共享套件的重要規則就是,不應該從會引用自己的套件引用任何東西
        例:不該引用 shopping 套件或其他子套件中的任何東西
    • 可視性
      • 可被外部引用
        • 宣告類型或 function 時以大寫開頭
      • 不可被外部引用,同一個套件中的程式碼才能夠存取這些欄位
        • 宣告類型或 function 時以小寫開頭

interface
  • 用於多種變化的接口
  • 宣告方法
    1. package main
    2.  
    3. import (
    4. "fmt"
    5. )
    6.  
    7. func whatType(i interface{}) {
    8. // 運用 switch 決定做什麼事
    9. switch i.(type) {
    10. case int:
    11. fmt.Println("int")
    12. case bool:
    13. fmt.Println("bool")
    14. default:
    15. fmt.Println("ohters")
    16. }
    17. }
    18.  
    19. func add(a interface{}, b interface{}) interface{} {
    20. // 若已知 type,可直接轉態
    21. return a.(int) + b.(int)
    22. }
    23.  
    24. func main() {
    25. a, b := 1, 3
    26. whatType(a)
    27. fmt.Println(add(a, b))
    28. }
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. type Logger interface {
    6. Log(message string)
    7. }
    8.  
    9. type SqlLogger struct{}
    10.  
    11. func (SqlLogger) Log(m string) {
    12. fmt.Println("SqlLogger", m)
    13. }
    14.  
    15. type ConsoleLogger struct{}
    16.  
    17. func (ConsoleLogger) Log(m string) {
    18. fmt.Println("ConsoleLogger", m)
    19. }
    20.  
    21. type FileLogger struct{}
    22.  
    23. func (FileLogger) Log(m string) {
    24. fmt.Println("FileLogger", m)
    25. }
    26.  
    27. type Server struct {
    28. logger Logger
    29. }
    30.  
    31. func process(logger Logger) {
    32. logger.Log("hello!")
    33. }
    34.  
    35. func main() {
    36. a := Server{
    37. logger: SqlLogger{},
    38. }
    39. a.logger.Log("123")
    40. process(a.logger)
    41.  
    42. b := Server{
    43. logger: ConsoleLogger{},
    44. }
    45. b.logger.Log("123")
    46. process(b.logger)
    47.  
    48. c := Server{
    49. logger: FileLogger{},
    50. }
    51. c.logger.Log("123")
    52. process(c.logger)
    53. }
  • 在 Go 中,interface 的連結是隱性的
    • 如範例,如果結構有一個 function Log,且參數是 string,並且沒有回傳值,便會當作是一個 Logger
  • struct 的型態會影響 interface 的判斷
    The Go Playground
    1. package main
    2.  
    3. type Interface interface {
    4. method()
    5. }
    6.  
    7. type S1 struct{}
    8. func (s S1) method() {}
    9.  
    10. type S2 struct{}
    11. func (s *S2) method() {}
    12.  
    13. type S3 struct{}
    14. func (s S3) methodV() {}
    15. func (s *S3) methodP() {}
    16.  
    17. func main() {
    18. s1 := S1{}
    19. _ = Interface(s1)
    20.  
    21. s1p := &S1{}
    22. _ = Interface(s1p)
    23. s2 := S2{}
    24. _ = Interface(s2) // S2 does not implement Interface (method method has pointer receiver)
    25.  
    26. s2p := &S2{}
    27. _ = Interface(s2p)
    28.  
    29. // 但若只是單純的呼叫 method,Go 將會自動轉換
    30. // 因 Go 可以單純將變數依其型參轉換,進而當作參數傳入 method
    31. // 在這裡並不需要判斷是什麼類型,擁有什麼樣的 method
    32. s3 := S3{}
    33. s3.methodV()
    34. s3.methodP()
    35. s3p := &S3{}
    36. s3p.methodV()
    37. s3p.methodP()
    38. // 因直接呼叫 S3{} 是不可尋址,它只是個暫時性的位置
    39. // 所以無法呼叫對應的 pointer method
    40. S3{}.methodV()
    41. S3{}.methodP() // cannot call pointer method on S3 literal
    42. (&S3{}).methodV()
    43. (&S3{}).methodP()
    44. }
    原因在於
    指向 value 的 pointer 可以是很多種類型,所以無法反推
    但 pointer 指向的 value 卻是唯一的,所以可以確定
    The Go Playground
    1. package main
    2.  
    3. import (
    4. "fmt"
    5. )
    6.  
    7. type Interface interface {
    8. test()
    9. }
    10.  
    11. type Interface2 interface {
    12. test()
    13. test2()
    14. }
    15.  
    16. type S1 struct{}
    17. func (s *S1) test() {}
    18. func (s *S1) test2() {}
    19.  
    20. func main() {
    21. s1p := &S1{}
    22. fmt.Printf("%T\n", s1p)
    23. //*main.S1
    24.  
    25. s1i := Interface(&S1{})
    26. fmt.Printf("%T\n", s1i)
    27. //*main.S1
    28.  
    29. // 因指向值的 pointer 可能為複數類型,所以 Go 無法反推其 pointer method
    30. // 如下
    31. // 究竟指向 s 的 pointer 是 Interface 還是 S1 呢?
    32. // 這決定是否合乎 Interface2 的接口,因 Interface 並無 test2()
    33. s := S1{}
    34. Interface2(s) // S1 does not implement Interface2 (test method has pointer receiver)
    35. }
    或者說,事實上 interface 並不是 addressable 的
    所以當呼叫 pointer 的 method 是會出錯的
    因為無法傳地址過去給 method
    試想以下情況
    建立一個 A 並把它存在 I
    1. type I interface{}
    2. type A int
    3. type B string
    4.  
    5. func main() {
    6. var a A = 5
    7. var i I = a
    8. fmt.Printf("i is of type %T\n", i)
    9.  
    10. var aPtr *A
    11. aPtr = &(i.(A)) // 這是不被允許的語法,但在此假設如果能拿到 address
    12.  
    13. // 然後建立一個 B 放在 i 中
    14. var b B = "hello"
    15. i = b
    16. fmt.Printf("i is of type %T, aPtr is of type %T\n", i, aPtr)
    17.  
    18. // 理論上 output
    19. // i is of type main.A
    20. // i is of type main.B, aPtr is of type *main.A
    21. // 但在此出現矛盾了,aPtr 的 type 是 *main.A,但它卻指向了 main.B ??
    22. // interface 是動態決定類型,所以 interface 是不能 addressable 的
    23. }

Error
  • Go 主要是透過返回值來處理錯誤,並沒有一般語言的異常處理
  • Go 有 panic 和 recover function,不過它們很少使用
    panic 類似於拋出異常,recover 類似於 catch
  • 內建 error 格式
    1. type error interface {
    2. Error() string
    3. }
  • 使用方法
    1. package main
    2.  
    3. import (
    4. "errors"
    5. "fmt"
    6. "time"
    7. )
    8.  
    9. func process(count int) error {
    10. if count < 1 {
    11. // 使用內建的 error
    12. return errors.New("Invalid count")
    13. }
    14. return nil
    15. }
    16.  
    17. // 自定錯誤,只要符合內建的 error 格式
    18. type MyError struct {
    19. When time.Time
    20. What string
    21. }
    22.  
    23. func (e *MyError) Error() string {
    24. return fmt.Sprintf("at %v, %s",
    25. e.When, e.What)
    26. }
    27.  
    28. func run() error {
    29. return &MyError{
    30. time.Now(),
    31. "it didn't work",
    32. }
    33. }
    34.  
    35. func main() {
    36. fmt.Println(process(0)) // 內建錯誤
    37. fmt.Println(run()) // 自定錯誤
    38. }

Defer
  • function 返回前執行
  • 呼叫與執行 defer 採取的是 LIFO,function 中最後一個 defer 將優先執行
  • 變數的傳遞,會在呼叫 defer 的時候便直接傳入,後續變數有更正也不會修改
  • 使用方法,可用在讀檔後的關閉檔案,以免忘記
    1. package main
    2.  
    3. import (
    4. "fmt"
    5. "os"
    6. )
    7.  
    8. func main() {
    9. file, err := os.Open("a_file_to_read")
    10. if err != nil {
    11. fmt.Println(err)
    12. return
    13. }
    14. defer file.Close()
    15. // 讀檔
    16. }
    1. package main
    2.  
    3. import (
    4. "errors"
    5. "fmt"
    6. )
    7.  
    8. func useAClosure() error {
    9. var err error
    10. defer func() {
    11. fmt.Printf("useAClosure: err has value %v\n", err)
    12. }()
    13.  
    14. err = errors.New("Error 1")
    15. fmt.Println("Finish func useAClosure")
    16.  
    17. return err
    18. }
    19.  
    20. func deferPrintf() error {
    21. var err error
    22. // 呼叫的時候會把參數帶過去,所以 err 是 nil
    23. defer fmt.Printf("deferPrintf: err has value %v\n", err)
    24.  
    25. err = errors.New("Error 2")
    26.  
    27. // 注意這裡是 LIFO,所以呼叫會是 43210
    28. for i := 0; i < 5; i++ {
    29. defer fmt.Printf("%d ", i)
    30. }
    31. fmt.Println("Finish func deferPrintf")
    32. return err
    33. }
    34.  
    35. func main() {
    36. useAClosure()
    37. deferPrintf()
    38. }

Concurrency (並行)
  • Goroutine
    • goroutine 有點類似於執行緒,但它是由 Go 自己來調度安排的,而不是由作業系統
    • 主程式不會等到所有 goroutine 執行完才結束
    • 使用方法
      1. package main
      2.  
      3. import (
      4. "fmt"
      5. "time"
      6. )
      7.  
      8. func main() {
      9. fmt.Println("start")
      10. go process()
      11. go process()
      12. go process()
      13. time.Sleep(time.Millisecond * 10) // 這是不好的方式,請別這麼做!
      14. fmt.Println("done")
      15. }
      16.  
      17. func process() {
      18. fmt.Println("processing")
      19. }
    • 同步
      • 在並行化的程式中,唯一安全的方式是讀取變數
      • 可以有很多程式去讀一個變數,但寫入變數必須是同步的
      • 別因為加了鎖,反而從開在一個十線道的,突然轉往一個單行道
      • 小心 dead lock
        例如,當 goroutineA 有鎖 A,但它想要存取鎖 B,而 goroutineB 擁有鎖 B,但它想要存取鎖 A,結果互相鎖住了
      • 簡單 lock 範例,但不建議,因只是個單行道,並無改進效率
        1. package main
        2.  
        3. import (
        4. "fmt"
        5. "sync"
        6. "time"
        7. )
        8.  
        9. var (
        10. counter = 0
        11. lock sync.Mutex
        12. )
        13.  
        14. func main() {
        15. for i := 0; i < 20; i++ {
        16. go incr()
        17. }
        18. time.Sleep(time.Millisecond * 10) // 不夠精準,不好的寫法
        19. }
        20.  
        21. func incr() {
        22. lock.Lock()
        23. defer lock.Unlock()
        24. counter++
        25. fmt.Println(counter)
        26. }
  • Channel
    • 對於簡單的例子,應使用 sync.Mutex 和 sync.RWMutex
    • 一個 Channel 就是不同的 goroutine 之間用來傳遞資料溝通的管道
    • 默認情況下,在另一端準備好之前,發送和接收都會阻塞,除非使用 select
    • 傳送
      CHANNEL <- DATA
    • 接收
      VAR := <-CHANNEL
    • 關閉
      close(CHANNEL)
    • 使用方法
      1. package main
      2.  
      3. import "fmt"
      4.  
      5. func sum(a []int, c chan int) {
      6. sum := 0
      7. for _, v := range a {
      8. sum += v
      9. }
      10. c <- sum // send sum to c
      11. }
      12.  
      13. func main() {
      14. a := []int{7, 2, 8, -9, 4, 0}
      15.  
      16. c := make(chan int)
      17. go sum(a[:len(a)/2], c)
      18. go sum(a[len(a)/2:], c)
      19. x, y := <-c, <-c // receive from c
      20.  
      21. fmt.Println(x, y, x+y)
      22. }
    • 可以指定只能傳送 或 只能接收,宣告時加上箭頭
      1. package main
      2.  
      3. import (
      4. "fmt"
      5. "time"
      6. )
      7.  
      8. func send(c chan<- int) {
      9. c <- 1
      10. }
      11.  
      12. func receive(c <-chan int) {
      13. fmt.Println(<-c)
      14. }
      15.  
      16. func main() {
      17. c := make(chan int)
      18. go send(c)
      19. go receive(c)
      20. time.Sleep(time.Millisecond * 50)
      21. }
    • buffer
      • 改為 buffer 緩衝,不至於一開始就阻塞
        1. package main
        2.  
        3. import (
        4. "fmt"
        5. "math/rand"
        6. "time"
        7. )
        8.  
        9. func main() {
        10. // 建立暫存
        11. c := make(chan int, 100)
        12. for i := 0; i < 5; i++ {
        13. worker := &Worker{id: i}
        14. go worker.process(c)
        15. }
        16.  
        17. for {
        18. c <- rand.Int()
        19. // 因來不及處理,即使有 buffer,還是會達到上限
        20. fmt.Println(len(c))
        21. time.Sleep(time.Millisecond * 50)
        22. }
        23. }
        24.  
        25. type Worker struct {
        26. id int
        27. }
        28.  
        29. func (w *Worker) process(c chan int) {
        30. for {
        31. data := <-c
        32. fmt.Printf("worker %d got %d\n", w.id, data)
        33. // 讓程式來不及處理傳送來的數據
        34. time.Sleep(time.Millisecond * 5000)
        35. }
        36. }
    • Close
      • 利用 close 終止 goroutine
        1. package main
        2.  
        3. import (
        4. "fmt"
        5. "runtime/debug"
        6. )
        7.  
        8. func main() {
        9. jobs := make(chan int, 5)
        10. done := make(chan bool)
        11.  
        12. go func() {
        13. for {
        14. // channel 已被 close 且所有值都已被接收,則 more 變為 false
        15. j, more := <-jobs
        16. if more {
        17. fmt.Println("received job", j)
        18. } else {
        19. fmt.Println("received all jobs")
        20. done <- true
        21. return
        22. }
        23. }
        24. }()
        25.  
        26. for j := 1; j <= 3; j++ {
        27. fmt.Println("sent job", j)
        28. jobs <- j
        29. }
        30. // close channel 不再傳送
        31. close(jobs)
        32. fmt.Println("sent all jobs")
        33.  
        34. <-done
        35.  
        36. // 偵測是否有未關閉的 goroutine
        37. debug.SetTraceback("all")
        38. panic(1)
        39. }
    • 搭配 Select (丟棄資料)
      • 無 channel 可用時
        • 無 default,持續等待直到有一個可用的 channel
        • 有 default ,直接執行 default 下的程式碼
      • 多個 channel 可用時,select 會隨機選擇一個 channel
      • 使用方法
        1. package main
        2.  
        3. import (
        4. "fmt"
        5. "time"
        6. )
        7.  
        8. func after(d time.Duration) chan bool {
        9. c := make(chan bool)
        10. go func() {
        11. time.Sleep(d)
        12. c <- true
        13. }()
        14. return c
        15. }
        16.  
        17. func main() {
        18. c := make(chan int)
        19. for i := 0; i < 5; i++ {
        20. worker := &Worker{id: i}
        21. go worker.process(c)
        22. }
        23. i := 0
        24. for {
        25. select {
        26. case c <- i:
        27. case <-after(time.Millisecond * 100):
        28. // 可用 time.After(time.Millisecond * 100) 取代
        29. // 逾時丟掉資料
        30. fmt.Println("timed out,drop out", i)
        31. }
        32. time.Sleep(time.Millisecond * 50)
        33. i++
        34. }
        35. }
        36.  
        37. type Worker struct {
        38. id int
        39. }
        40.  
        41. func (w *Worker) process(c chan int) {
        42. for {
        43. data := <-c
        44. fmt.Printf("worker %d got %d\n", w.id, data)
        45. // 讓程式來不及處理傳送來的數據
        46. time.Sleep(time.Millisecond * 5000)
        47. }
        48. }

參考

1.3 Go 命令
初學 Golang 30 天(三)變數
[Golang] 相當好用但又要注意的defer
Golang- import 导入包的语法
How to use interfaces in Go Why value stored in an interface is not addressable in Golang
Go Data Structures: Interfaces

留言