大廠 Golang 語法 50 問-

前言

Golang 這門語言想必大家都不陌生,現在也比較火,學習的人也比較多。作爲一款性能還算不錯的語言,現在很多大廠的新項目都選擇了 Golang。
這期針對大家的疑惑,總結了大廠系列 Golang 語法 50 問,供大家面試和學習用,下面看一下提綱。

  1. 使用值爲 nil 的 slice、map 會發生啥

// map 錯誤示例
func main() {
    var m map[string]int
    m["one"] = 1  // error: panic: assignment to entry in nil map
    // m := make(map[string]int)// map 的正確聲明,分配了實際的內存
}    
 
// slice 正確示例
func main() {
 var s []int
 s = append(s, 1)
}
  1. 訪問 map 中的 key,需要注意啥

當訪問 map 中不存在的 key 時,Go 則會返回元素對應數據類型的零值,比如 nil、'' 、false 和 0,取值操作總有值返回,故不能通過取出來的值,來判斷 key 是不是在 map 中。
檢查 key 是否存在可以用 map 直接訪問,檢查返回的第二個參數即可。

// 錯誤的 key 檢測方式
func main() {
 x := map[string]string{"one": "2", "two": "", "three": "3"}
 if v := x["two"]; v == "" {
  fmt.Println("key two is no entry") // 鍵 two 存不存在都會返回的空字符串
 }
}
 
// 正確示例
func main() {
 x := map[string]string{"one": "2", "two": "", "three": "3"}
 if _, ok := x["two"]; !ok {
  fmt.Println("key two is no entry")
 }
}

3.string 類型的值可以修改嗎

不能,嘗試使用索引遍歷字符串,來更新字符串中的個別字符,是不允許的。

string 類型的值是隻讀的二進制 byte slice,如果真要修改字符串中的字符,將 string 轉爲 []byte 修改後,再轉爲 string 即可。

// 修改字符串的錯誤示例
func main() {
 x := "text"
 x[0] = "T"  // error: cannot assign to x[0]
 fmt.Println(x)
}
 
 
// 修改示例
func main() {
 x := "text"
 xBytes := []byte(x)
 xBytes[0] = 'T' // 注意此時的 T 是 rune 類型
 x = string(xBytes)
 fmt.Println(x) // Text
}

4.switch 中如何強制執行下一個 case 代碼塊

switch 語句中的 case 代碼塊會默認帶上 break,但可以使用 fallthrough 來強制執行下一個 case 代碼塊。

func main() {
 isSpace := func(char byte) bool {
  switch char {
  case ' ': // 空格符會直接 break,返回 false // 和其他語言不一樣
  // fallthrough // 返回 true
  case '\t':
   return true
  }
  return false
 }
 fmt.Println(isSpace('\t')) // true
 fmt.Println(isSpace(' ')) // false
}
  1. 你是如何關閉 HTTP 的響應體的

直接在處理 HTTP 響應錯誤的代碼塊中,直接關閉非 nil 的響應體;手動調用 defer 來關閉響應體。

// 正確示例
func main() {
 resp, err := http.Get("http://www.baidu.com")
 
    // 關閉 resp.Body 的正確姿勢
    if resp != nil {
  defer resp.Body.Close()
 }
 
 checkError(err)
 defer resp.Body.Close()
 
 body, err := ioutil.ReadAll(resp.Body)
 checkError(err)
 
 fmt.Println(string(body))
}
  1. 你是否主動關閉過 http 連接,爲啥要這樣做

有關閉,不關閉會程序可能會消耗完 socket 描述符。有如下 2 種關閉方式:

func main() {
 tr := http.Transport{DisableKeepAlives: true}
 client := http.Client{Transport: &tr}
 
 resp, err := client.Get("https://golang.google.cn/")
 if resp != nil {
  defer resp.Body.Close()
 }
 checkError(err)
 
 fmt.Println(resp.StatusCode) // 200
 
 body, err := ioutil.ReadAll(resp.Body)
 checkError(err)
 
 fmt.Println(len(string(body)))
}
  1. 解析 JSON 數據時,默認將數值當做哪種類型

在 encode/decode JSON 數據時,Go 默認會將數值當做 float64 處理。

func main() {
     var data = []byte(`{"status": 200}`)
     var result map[string]interface{}

     if err := json.Unmarshal(data, &result); err != nil {
     log.Fatalln(err)
}

解析出來的 200 是 float 類型。

  1. 如何從 panic 中恢復

在一個 defer 延遲執行的函數中調用 recover ,它便能捕捉 / 中斷 panic。

// 錯誤的 recover 調用示例
func main() {
 recover() // 什麼都不會捕捉
 panic("not good") // 發生 panic,主程序退出
 recover() // 不會被執行
 println("ok")
}
 
// 正確的 recover 調用示例
func main() {
 defer func() {
  fmt.Println("recovered: ", recover())
 }()
 panic("not good")
}
  1. 簡短聲明的變量需要注意啥

10.range 迭代 map 是有序的嗎

無序的。Go 的運行時是有意打亂迭代順序的,所以你得到的迭代結果可能不一致。但也並不總會打亂,得到連續相同的 5 個迭代結果也是可能的。

11.recover 的執行時機

無,recover 必須在 defer 函數中運行。recover 捕獲的是祖父級調用時的異常,直接調用時無效。

func main() {
    recover()
    panic(1)
}

直接 defer 調用也是無效。

func main() {
    defer recover()
    panic(1)
}

defer 調用時多層嵌套依然無效。

func main() {
    defer func() {
        func() { recover() }()
    }()
    panic(1)
}

必須在 defer 函數中直接調用纔有效。

func main() {
    defer func() {
        recover()
    }()
    panic(1)
}
  1. 閉包錯誤引用同一個變量問題怎麼處理

在每輪迭代中生成一個局部變量 i 。如果沒有 i := i 這行,將會打印同一個變量。

func main() {
    for i := 0; i < 5; i++ {
        i := i
        defer func() {
            println(i)
        }()
    }
}

或者是通過函數參數傳入 i 。

func main() {
    for i := 0; i < 5; i++ {
        defer func(i int) {
            println(i)
        }(i)
    }
}
  1. 在循環內部執行 defer 語句會發生啥

defer 在函數退出時才能執行,在 for 執行 defer 會導致資源延遲釋放。

func main() {
    for i := 0; i < 5; i++ {
        func() {
            f, err := os.Open("/path/to/file")
            if err != nil {
                log.Fatal(err)
            }
            defer f.Close()
        }()
    }
}

func 是一個局部函數,在局部函數里面執行 defer 將不會有問題。

  1. 說出一個避免 Goroutine 泄露的措施

可以通過 context 包來避免內存泄漏。

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    ch := func(ctx context.Context) <-chan int {
        ch := make(chan int)
        go func() {
            for i := 0; ; i++ {
                select {
                case <- ctx.Done():
                    return
                case ch <- i:
                }
            }
        } ()
        return ch
    }(ctx)

    for v := range ch {
        fmt.Println(v)
        if v == 5 {
            cancel()
            break
        }
    }
}

下面的 for 循環停止取數據時,就用 cancel 函數,讓另一個協程停止寫數據。如果下面 for 已停止讀取數據,上面 for 循環還在寫入,就會造成內存泄漏。

  1. 如何跳出 for select 循環

通常在 for 循環中,使用 break 可以跳出循環,但是注意在 go 語言中,for select 配合時,break 並不能跳出循環。

func testSelectFor2(chExit chan bool){
 EXIT:
    for  {
        select {
        case v, ok := <-chExit:
            if !ok {
                fmt.Println("close channel 2", v)
                break EXIT//goto EXIT2
            }
 
            fmt.Println("ch2 val =", v)
        }
    }
 
    //EXIT2:
    fmt.Println("exit testSelectFor2")
}
  1. 如何在切片中查找

go 中使用 sort.searchXXX 方法,在排序好的切片中查找指定的方法,但是其返回是對應的查找元素不存在時,待插入的位置下標 (元素插入在返回下標前)。
可以通過封裝如下函數,達到目的。

func IsExist(s []string, t string) (int, bool) {
    iIndex := sort.SearchStrings(s, t)
    bExist := iIndex!=len(s) && s[iIndex]==t
 
    return iIndex, bExist
}
  1. 如何初始化帶嵌套結構的結構體

go 的哲學是組合優於繼承,使用 struct 嵌套即可完成組合,內嵌的結構體屬性就像外層結構的屬性即可,可以直接調用。
注意初始化外層結構體時,必須指定內嵌結構體名稱的結構體初始化,如下看到 s1 方式報錯,s2 方式正確。

type stPeople struct {
    Gender bool
    Name string
}
 
type stStudent struct {
    stPeople
    Class int
}
 
//嘗試4 嵌套結構的初始化表達式
//var s1 = stStudent{false, "JimWen", 3}
var s2 = stStudent{stPeople{false, "JimWen"}, 3}
fmt.Println(s2.Gender, s2.Name, s2.Class)
  1. 切片和數組的區別

數組是具有固定長度,且擁有零個或者多個,相同數據類型元素的序列。數組的長度是數組類型的一部分,所以 [3]int 和 [4]int 是兩種不同的數組類型。

數組需要指定大小,不指定也會根據初始化的自動推算出大小,不可改變;數組是值傳遞。

數組是內置類型,是一組同類型數據的集合,它是值類型,通過從 0 開始的下標索引訪問元素值。在初始化後長度是固定的,無法修改其長度。
當作爲方法的參數傳入時將複製一份數組而不是引用同一指針。數組的長度也是其類型的一部分,通過內置函數 len(array) 獲取其長度。

數組定義:

var array [10]int
var array =[5]int{1,2,3,4,5}

切片表示一個擁有相同類型元素的可變長度的序列。切片是一種輕量級的數據結構,它有三個屬性:指針、長度和容量。

切片不需要指定大小;切片是地址傳遞;切片可以通過數組來初始化,也可以通過內置函數 make() 初始化 。初始化時 len=cap, 在追加元素時如果容量 cap 不足時將按 len 的 2 倍擴容。

切片定義:

19.new 和 make 的區別

new 的作用是初始化一個指向類型的指針 (*T) 。

new 函數是內建函數,函數定義:func new(Type) *Type。

使用 new 函數來分配空間。傳遞給 new 函數的是一個類型,不是一個值。返回值是指向這個新分配的零值的指針。

make 的作用是爲 slice,map 或 chan 初始化並返回引用 (T)。

make 函數是內建函數,函數定義:func make(Type, size IntegerType) Type;第一個參數是一個類型,第二個參數是長度;返回值是一個類型。

make(T, args) 函數的目的與 new(T) 不同。它僅僅用於創建 Slice, Map 和 Channel,並且返回類型是 T(不是 T*)的一個初始化的(不是零值)的實例。

20.Printf()、Sprintf()、Fprintf() 函數的區別用法是什麼

都是把格式好的字符串輸出,只是輸出的目標不一樣。

Printf(),是把格式字符串輸出到標準輸出(一般是屏幕,可以重定向)。Printf() 是和標準輸出文件 (stdout) 關聯的,Fprintf  則沒有這個限制。

Sprintf(),是把格式字符串輸出到指定字符串中,所以參數比 printf 多一個 char*。那就是目標字符串地址。

Fprintf(),是把格式字符串輸出到指定文件設備中,所以參數比 printf 多一個文件指針 FILE*。主要用於文件操作。Fprintf() 是格式化輸出到一個 stream,通常是到文件。

  1. 說說 go 語言中的 for 循環

for 循環支持 continue 和 break 來控制循環,但是它提供了一個更高級的 break,可以選擇中斷哪一個循環 for 循環不支持以逗號爲間隔的多個賦值語句,必須使用平行賦值的方式來初始化多個變量。

22.Array 類型的值作爲函數參數

在 C/C++ 中,數組(名)是指針。將數組作爲參數傳進函數時,相當於傳遞了數組內存地址的引用,在函數內部會改變該數組的值。
在 Go 中,數組是值。作爲參數傳進函數時,傳遞的是數組的原始值拷貝,此時在函數內部是無法更新該數組的。

// 數組使用值拷貝傳參
func main() {
 x := [3]int{1,2,3}
 
 func(arr [3]int) {
  arr[0] = 7
  fmt.Println(arr) // [7 2 3]
 }(x)
 fmt.Println(x)   // [1 2 3] // 並不是你以爲的 [7 2 3]
}

想改變數組,直接傳遞指向這個數組的指針類型。

// 傳址會修改原數據
func main() {
 x := [3]int{1,2,3}
 
 func(arr *[3]int) {
  (*arr)[0] = 7 
  fmt.Println(arr) // &[7 2 3]
 }(&x)
 fmt.Println(x) // [7 2 3]
}

直接使用 slice:即使函數內部得到的是 slice 的值拷貝,但依舊會更新 slice 的原始數據(底層 array)

// 錯誤示例
func main() {
 x := []string{"a", "b", "c"}
 for v := range x {
  fmt.Println(v) // 1 2 3
 }
}
 
 
// 正確示例
func main() {
 x := []string{"a", "b", "c"}
 for _, v := range x { // 使用 _ 丟棄索引
  fmt.Println(v)
 }
}

說。go 語言中的 for 循

  1. 說說 go 語言中的 switch 語句

單個 case 中,可以出現多個結果選項。只有在 case 中明確添加 fallthrough 關鍵字,纔會繼續執行緊跟的下一個 case。

  1. 說說 go 語言中有沒有隱藏的 this 指針

方法施加的對象顯式傳遞,沒有被隱藏起來。

golang 的面向對象表達更直觀,對於面向過程只是換了一種語法形式來表達

方法施加的對象不需要非得是指針,也不用非得叫 this。

25.go 語言中的引用類型包含哪些

數組切片、字典 (map)、通道(channel)、接口(interface)。

26.go 語言中指針運算有哪些

可以通過 “&” 取指針的地址;可以通過 “*” 取指針指向的數據。

  1. 說說 go 語言的 main 函數

main 函數不能帶參數;main 函數不能定義返回值。

main 函數所在的包必須爲 main 包;main 函數中可以使用 flag 包來獲取和解析命令行參數。

27.go 語言觸發異常的場景有哪些

  1. 說說 go 語言的 beego 框架

  1. 說說 go 語言的 goconvey 框架

30.GoStub 的作用是什麼

31.go 語言編程的好處是什麼

  1. 說說 go 語言的 select 機制

  1. 解釋一下 go 語言中的靜態類型聲明

靜態類型聲明是告訴編譯器不需要太多的關注這個變量的細節。

靜態變量的聲明,只是針對於編譯的時候, 在連接程序的時候,編譯器還要對這個變量進行實際的聲明。

34.go 的接口是什麼

在 go 語言中,interface 也就是接口,被用來指定一個對象。接口具有下面的要素:

35.Go 語言裏面的類型斷言是怎麼回事

類型斷言是用來從一個接口裏面讀取數值給一個具體的類型變量。

類型轉換是指轉換兩個不相同的數據類型。

36.go 語言中局部變量和全局變量的缺省值是什麼

全局變量的缺省值是與這個類型相關的零值。

37.go 語言編程的好處是什麼

  1. 解釋一下 go 語言中的靜態類型聲明

靜態類型聲明是告訴編譯器不需要太多的關注這個變量的細節。
靜態變量的聲明,只是針對於編譯的時候, 在連接程序的時候,編譯器還要對這個變量進行實際的聲明。

  1. 模塊化編程是怎麼回事

模塊化編程是指把一個大的程序分解成幾個小的程序。這麼做的目的是爲了減少程序的複雜度,易於維護,並且達到最高的效率。

碼字不易,請不吝點贊,隨手關注,更多精彩,自動送達。

40.Golang 的方法有什麼特別之處

函數的定義聲明沒有接收者。

方法的聲明和函數類似,他們的區別是:方法在定義的時候,會在 func 和方法名之間增加一個參數,這個參數就是接收者,這樣我們定義的這個方法就和接收者綁定在了一起,稱之爲這個接收者的方法。

Go 語言裏有兩種類型的接收者:值接收者和指針接收者。

使用值類型接收者定義的方法,在調用的時候,使用的其實是值接收者的一個副本,所以對該值的任何操作,不會影響原來的類型變量。------- 相當於形式參數。

如果我們使用一個指針作爲接收者,那麼就會其作用了,因爲指針接收者傳遞的是一個指向原值指針的副本,指針的副本,指向的還是原來類型的值,所以修改時,同時也會影響原來類型變量的值。

41.Golang 可變參數

函數方法的參數,可以是任意多個,這種我們稱之爲可以變參數,比如我們常用的 fmt.Println() 這類函數,可以接收一個可變的參數。

可以變參數,可以是任意多個。我們自己也可以定義可以變參數,可變參數的定義,在類型前加上省略號… 即可。

func main() {
 print("1","2","3")
}


func print (a ...interface{}){
 for _,v:=range a{
  fmt.Print(v)
 }
 fmt.Println()
}

例子中我們自己定義了一個接受可變參數的函數,效果和 fmt.Println() 一樣。

可變參數本質上是一個數組,所以我們向使用數組一樣使用它,比如例子中的 for range 循環。

42.Golang Slice 的底層實現

切片是基於數組實現的,它的底層是數組,它自己本身非常小,可以理解爲對底層數組的抽象。因爲基於數組實現,所以它的底層的內存是連續分配的,效率非常高,還可以通過索引獲得數據,可以迭代以及垃圾回收優化。

切片本身並不是動態數組或者數組指針。它內部實現的數據結構通過指針引用底層數組,設定相關屬性將數據讀寫操作限定在指定的區域內。切片本身是一個只讀對象,其工作機制類似數組指針的一種封裝。

切片對象非常小,是因爲它是隻有 3 個字段的數據結構:

這 3 個字段,就是 Go 語言操作底層數組的元數據。

43.Golang Slice 的擴容機制,有什麼注意點


Go 中切片擴容的策略是這樣的:

首先判斷,如果新申請容量大於 2 倍的舊容量,最終容量就是新申請的容量。

否則判斷,如果舊切片的長度小於 1024,則最終容量就是舊容量的兩倍。

否則判斷,如果舊切片長度大於等於 1024,則最終容量從舊容量開始循環增加原來的  1/4 , 直到最終容量大於等於新申請的容量。

如果最終容量計算值溢出,則最終容量就是新申請容量。

情況一:

原數組還有容量可以擴容(實際容量沒有填充完),這種情況下,擴容以後的數組還是指向原來的數組,對一個切片的操作可能影響多個指針指向相同地址的 Slice。

情況二:

原來數組的容量已經達到了最大值,再想擴容, Go 默認會先開一片內存區域,把原來的值拷貝過來,然後再執行 append() 操作。這種情況絲毫不影響原數組。

要複製一個 Slice,最好使用 Copy 函數。

44.Golang Map 底層實現

Golang 中 map 的底層實現是一個散列表,因此實現 map 的過程實際上就是實現散表的過程。
在這個散列表中,主要出現的結構體有兩個,一個叫 hmap(a header for a go map),一個叫 bmap(a bucket for a Go map,通常叫其 bucket)。
hmap 如下所示:

圖中有很多字段,但是便於理解 map 的架構,你只需要關心的只有一個,就是標紅的字段:buckets 數組。Golang 的 map 中用於存儲的結構是 bucket 數組。而 bucket(即 bmap) 的結構是怎樣的呢?

bucket:

相比於 hmap,bucket 的結構顯得簡單一些,標橙的字段依然是 “核心”,我們使用的 map 中的 key 和 value 就存儲在這裏。
"高位哈希值" 數組記錄的是當前 bucket 中 key 相關的 "索引",稍後會詳細敘述。還有一個字段是一個指向擴容後的 bucket 的指針,使得 bucket 會形成一個鏈表結構。

整體的結構應該是這樣的:

Golang 把求得的哈希值按照用途一分爲二:高位和低位。低位用於尋找當前 key 屬於 hmap 中的哪個 bucket,而高位用於尋找 bucket 中的哪個 key。

需要特別指出的一點是:map 中的 key/value 值都是存到同一個數組中的。這樣做的好處是:在 key 和 value 的長度不同的時候,可以消除 padding 帶來的空間浪費。

Map 的擴容:

當 Go 的 map 長度增長到大於加載因子所需的 map 長度時,Go 語言就會將產生一個新的 bucket 數組,然後把舊的 bucket 數組移到一個屬性字段 oldbucket 中。

注意:並不是立刻把舊的數組中的元素轉義到新的 bucket 當中,而是,只有當訪問到具體的某個 bucket 的時候,會把 bucket 中的數據轉移到新的 bucket 中。

  1. JSON 標準庫對 nil slice 和 空 slice 的處理是一致的嗎

首先 JSON 標準庫對 nil slice 和 空 slice 的處理是不一致。

通常錯誤的用法,會報數組越界的錯誤,因爲只是聲明瞭 slice,卻沒有給實例化的對象。

var slice []int
slice[1] = 0

此時 slice 的值是 nil,這種情況可以用於需要返回 slice 的函數,當函數出現異常的時候,保證函數依然會有 nil 的返回值。

empty slice 是指 slice 不爲 nil,但是 slice 沒有值,slice 的底層的空間是空的,此時的定義如下:

slice := make([]int,0

當我們查詢或者處理一個空的列表的時候,這非常有用,它會告訴我們返回的是一個列表,但是列表內沒有任何值。

總之,nil slice 和 empty slice 是不同的東西, 需要我們加以區分的。

46.Golang 的內存模型,爲什麼小對象多了會造成 gc 壓力

通常小對象過多會導致 GC 三色法消耗過多的 GPU。優化思路是,減少對象分配。

47.Data Race 問題怎麼解決?能不能不加鎖解決這個問題

同步訪問共享數據是處理數據競爭的一種有效的方法。
golang 在 1.1 之後引入了競爭檢測機制,可以使用 go run -race 或者 go build -race 來進行靜態檢測。其在內部的實現是, 開啓多個協程執行同一個命令, 並且記錄下每個變量的狀態。

競爭檢測器基於 C/C++ 的 ThreadSanitizer 運行時庫,該庫在 Google 內部代碼基地和 Chromium 找到許多錯誤。這個技術在 2012 年九月集成到 Go 中,從那時開始,它已經在標準庫中檢測到 42 個競爭條件。現在,它已經是我們持續構建過程的一部分,當競爭條件出現時,它會繼續捕捉到這些錯誤。

競爭檢測器已經完全集成到 Go 工具鏈中,僅僅添加 - race 標誌到命令行就使用了檢測器。

$ go test -race mypkg    // 測試包
$ go run -race mysrc.go  // 編譯和運行程序 $ go build -race mycmd 
// 構建程序 $ go install -race mypkg // 安裝程序

要想解決數據競爭的問題可以使用互斥鎖 sync.Mutex, 解決數據競爭 (Data race), 也可以使用管道解決, 使用管道的效率要比互斥鎖高。

  1. 在 range 迭代 slice 時,你怎麼修改值的

在 range 迭代中,得到的值其實是元素的一份值拷貝,更新拷貝並不會更改原來的元素,即是拷貝的地址並不是原有元素的地址。

func main() {
 data := []int{1, 2, 3}
 for _, v := range data {
  v *= 10  // data 中原有元素是不會被修改的
 }
 fmt.Println("data: ", data) // data:  [1 2 3]
}

如果要修改原有元素的值,應該使用索引直接訪問。

func main() {
 data := []int{1, 2, 3}
 for i, v := range data {
  data[i] = v * 10 
 }
 fmt.Println("data: ", data) // data:  [10 20 30]
}

如果你的集合保存的是指向值的指針,需稍作修改。依舊需要使用索引訪問元素,不過可以使用 range 出來的元素直接更新原有值。

func main() {
 data := []*struct{ num int }{{1}, {2}, {3},}
 for _, v := range data {
  v.num *= 10 // 直接使用指針更新
 }
 fmt.Println(data[0], data[1], data[2]) // &{10} &{20} &{30}
}

49.nil interface 和 nil interface 的區別

雖然 interface 看起來像指針類型,但它不是。interface 類型的變量只有在類型和值均爲 nil 時才爲 nil

如果你的 interface 變量的值是跟隨其他變量變化的,與 nil 比較相等時小心。

如果你的函數返回值類型是 interface,更要小心這個坑:

func main() {
   var data *byte
   var in interface{}

   fmt.Println(data, data == nil) // <nil> true
   fmt.Println(in, in == nil) // <nil> true

   in = data
   fmt.Println(in, in == nil) // <nil> false // data 值爲 nil,但 in 值不爲 nil
}

// 正確示例
func main() {
  doIt := func(arg int) interface{} {
  var result *struct{} = nil

  if arg > 0 {
  result = &struct{}{}
  } else {
  return nil // 明確指明返回 nil
  }

  return result
  }

 
  if res := doIt(-1); res != nil {
  fmt.Println("Good result: ", res)
  } else {
  fmt.Println("Bad result: ", res) // Bad result: <nil>
  }
}

50.select 可以用於什麼

常用語 gorotine 的完美退出。

golang 的 select 就是監聽 IO 操作,當 IO 操作發生時,觸發相應的動作

每個 case 語句裏必須是一個 IO 操作,確切的說,應該是一個面向 channel 的 IO 操作。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/922aAcGEfH-5ZWoHxPSwEw