go基礎語法50問,來看看你的go基礎合格了嗎?

目錄

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

允許對值為 nil 的 slice 添加元素,但對值為 nil 的 map 添加元素,則會造成運行時 panic。

// 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)
}

2.訪問 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
}

5.你是如何關閉 HTTP 的響應體的

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

// 正確示例
func main() {
 resp, err := http.Get("//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))
}

6.你是否主動關閉過http連接,為啥要這樣做

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

  • 直接設置請求變數的 Close 欄位值為 true,每次請求結束後就會主動關閉連接。設置 Header 請求頭部選項 Connection: close,然後伺服器返回的響應頭部也會有這個選項,此時 HTTP 標準庫會主動斷開連接
// 主動關閉連接
func main() {
 req, err := http.NewRequest("GET", "//golang.org", nil)
 checkError(err)

 req.Close = true
 //req.Header.Add("Connection", "close") // 等效的關閉方式

 resp, err := http.DefaultClient.Do(req)
 if resp != nil {
  defer resp.Body.Close()
 }
 checkError(err)

 body, err := ioutil.ReadAll(resp.Body)
 checkError(err)

 fmt.Println(string(body))
}
  • 你可以創建一個自定義配置的 HTTP transport 客戶端,用來取消 HTTP 全局的復用連接。
func main() {
 tr := http.Transport{DisableKeepAlives: true}
 client := http.Client{Transport: &tr}

 resp, err := client.Get("//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)))
}

7.解析 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 類型。

8.如何從 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")
}

9.簡短聲明的變數需要注意啥

  • 簡短聲明的變數只能在函數內部使用

  • struct 的變數欄位不能使用 := 來賦值

  • 不能用簡短聲明方式來單獨為一個變數重複聲明, := 左側至少有一個新變數,才允許多變數的重複聲明

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)
}

12.閉包錯誤引用同一個變數問題怎麼處理

在每輪迭代中生成一個局部變數 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)
    }
}

13.在循環內部執行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 將不會有問題。

14.說出一個避免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 循環還在寫入,就會造成記憶體泄漏。

15.如何跳出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")
}

16.如何在切片中查找

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
}

17.如何初始化帶嵌套結構的結構體

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)

18.切片和數組的區別

數組是具有固定長度,且擁有零個或者多個,相同數據類型元素的序列。數組的長度是數組類型的一部分,所以[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倍擴容。切片定義:

var slice []type = make([]type, len)

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,通常是到文件。

21.說說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)
 }
}

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

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

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

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

golang 的面向對象表達更直觀,對於面向過程只是換了一種語法形式來表達方法施加的對象不需要非得是指針,也不用非得叫 this。

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

切片(slice)、字典(map)、通道(channel)、介面(interface)。

26.說說go語言的main函數

main 函數不能帶參數;main 函數不能定義返回值。main 函數所在的包必須為 main 包;main 函數中可以使用 flag 包來獲取和解析命令行參數。

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

  • 空指針解析

  • 下標越界

  • 除數為0

  • 調用 panic 函數

28.說說go語言的beego框架

  • beego 是一個 golang 實現的輕量級HTTP框架

  • beego 可以通過注釋路由、正則路由等多種方式完成 url 路由注入

  • 可以使用 bee new 工具生成空工程,然後使用 bee run 命令自動熱編譯

29.說說go語言的goconvey框架

  • goconvey 是一個支援 golang 的單元測試框架

  • goconvey 能夠自動監控文件修改並啟動測試,並可以將測試結果實時輸出到web介面

  • goconvey 提供了豐富的斷言簡化測試用例的編寫

30.GoStub的作用是什麼

  • GoStub 可以對全局變數打樁

  • GoStub 可以對函數打樁

  • GoStub 不可以對類的成員方法打樁

  • GoStub 可以打動態樁,比如對一個函數打樁後,多次調用該函數會有不同的行為

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

  • 編譯和運行都很快。

  • 在語言層級支援並行操作。

  • 有垃圾處理器。

  • 內置字元串和 maps。

  • 函數是 go 語言的最基本編程單位。

32.說說go語言的select機制

  • select 機制用來處理非同步 IO 問題

  • select 機制最大的一條限制就是每個 case 語句里必須是一個 IO 操作

  • golang 在語言級別支援 select 關鍵字

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

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

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

34.go的介面是什麼

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

  • 一系列的方法

  • 具體應用中並用來表示某個數據類型

  • 在 go 中使用 interface 來實現多態

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

類型斷言是用來從一個介面裡面讀取數值給一個具體的類型變數。類型轉換是指轉換兩個不相同的數據類型。

36.go語言中局部變數和全局變數的預設值是什麼

全局變數的預設值是與這個類型相關的零值。

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

  • 編譯和運行都很快。

  • 在語言層級支援並行操作。

  • 有垃圾處理器。

  • 內置字元串和 maps。

  • 函數是 go 語言的最基本編程單位。

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

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

39.模組化編程是怎麼回事

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

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 中。

45. 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)
slice := []int{}

當我們查詢或者處理一個空的列表的時候,這非常有用,它會告訴我們返回的是一個列表,但是列表內沒有任何值。總之,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),也可以使用管道解決,使用管道的效率要比互斥鎖高。

48.在 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可以用於什麼

常用於goroutine的完美退出。

golang 的 select 就是監聽 IO 操作,當 IO 操作發生時,觸發相應的動作每個case語句里必須是一個IO操作,確切的說,應該是一個面向channel的IO操作。

參考資料:公眾號:晨夢思雨

Tags: