<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          大廠Golang語(yǔ)法50問(wèn)!

          共 22938字,需瀏覽 46分鐘

           ·

          2021-07-27 10:51


          前言

          Golang 這門語(yǔ)言想必大家都不陌生,現(xiàn)在也比較火,學(xué)習(xí)的人也比較多。作為一款性能還算不錯(cuò)的語(yǔ)言,現(xiàn)在很多大廠的新項(xiàng)目都選擇了Golang。

          這期針對(duì)大家的疑惑,總結(jié)了大廠系列Golang語(yǔ)法50問(wèn),供大家面試和學(xué)習(xí)用,下面看一下提綱。

          1.使用值為 nil 的 slice、map會(huì)發(fā)生啥

          允許對(duì)值為 nil 的 slice 添加元素,但對(duì)值為 nil 的 map 添加元素,則會(huì)造成運(yùn)行時(shí) panic。

          // map 錯(cuò)誤示例
          func main() {
              var m map[string]int
              m["one"] = 1  // error: panic: assignment to entry in nil map
              // m := make(map[string]int)// map 的正確聲明,分配了實(shí)際的內(nèi)存
          }    
           
          // slice 正確示例
          func main() {
           var s []int
           s = append(s, 1)
          }


          2.訪問(wèn) map 中的 key,需要注意啥

          當(dāng)訪問(wèn) map 中不存在的 key 時(shí),Go 則會(huì)返回元素對(duì)應(yīng)數(shù)據(jù)類型的零值,比如 nil、'' 、false 和 0,取值操作總有值返回,故不能通過(guò)取出來(lái)的值,來(lái)判斷 key 是不是在 map 中。

          檢查 key 是否存在可以用 map 直接訪問(wèn),檢查返回的
          第二個(gè)參數(shù)即可。

          // 錯(cuò)誤的 key 檢測(cè)方式
          func main() {
           x := map[string]string{"one""2""two""""three""3"}
           if v := x["two"]; v == "" {
            fmt.Println("key two is no entry"// 鍵 two 存不存在都會(huì)返回的空字符串
           }
          }
           
          // 正確示例
          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 類型的值可以修改嗎

          不能,嘗試使用索引遍歷字符串,來(lái)更新字符串中的個(gè)別字符,是不允許的。

          string 類型的值是只讀的二進(jìn)制 byte slice,如果真要修改字符串中的字符,將 string 轉(zhuǎn)為 []byte 修改后,再轉(zhuǎn)為 string 即可。

          // 修改字符串的錯(cuò)誤示例
          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' // 注意此時(shí)的 T 是 rune 類型
           x = string(xBytes)
           fmt.Println(x) // Text
          }


          4.switch 中如何強(qiáng)制執(zhí)行下一個(gè) case 代碼塊

          switch 語(yǔ)句中的 case 代碼塊會(huì)默認(rèn)帶上 break,但可以使用 fallthrough 來(lái)強(qiáng)制執(zhí)行下一個(gè) case 代碼塊。

          func main() {
           isSpace := func(char byte) bool {
            switch char {
            case ' '// 空格符會(huì)直接 break,返回 false // 和其他語(yǔ)言不一樣
            // fallthrough // 返回 true
            case '\t':
             return true
            }
            return false
           }
           fmt.Println(isSpace('\t')) // true
           fmt.Println(isSpace(' ')) // false
          }


          5.你是如何關(guān)閉 HTTP 的響應(yīng)體的

          直接在處理 HTTP 響應(yīng)錯(cuò)誤的代碼塊中,直接關(guān)閉非 nil 的響應(yīng)體;手動(dòng)調(diào)用 defer 來(lái)關(guān)閉響應(yīng)體。

          // 正確示例
          func main() {
           resp, err := http.Get("http://www.baidu.com")
           
              // 關(guān)閉 resp.Body 的正確姿勢(shì)
              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.你是否主動(dòng)關(guān)閉過(guò)http連接,為啥要這樣做

          有關(guān)閉,不關(guān)閉會(huì)程序可能會(huì)消耗完 socket 描述符。有如下2種關(guān)閉方式:

          • 直接設(shè)置請(qǐng)求變量的 Close 字段值為 true,每次請(qǐng)求結(jié)束后就會(huì)主動(dòng)關(guān)閉連接。

            設(shè)置 Header 請(qǐng)求頭部選項(xiàng) Connection: close,然后服務(wù)器返回的響應(yīng)頭部也會(huì)有這個(gè)選項(xiàng),此時(shí) HTTP 標(biāo)準(zhǔn)庫(kù)會(huì)主動(dòng)斷開連接

            // 主動(dòng)關(guān)閉連接
            func main() {
             req, err := http.NewRequest("GET""http://golang.org", nil)
             checkError(err)
             
             req.Close = true
             //req.Header.Add("Connection", "close") // 等效的關(guān)閉方式
             
             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))
            }

          你可以創(chuàng)建一個(gè)自定義配置的 HTTP transport 客戶端,用來(lái)取消 HTTP 全局的復(fù)用連接。

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


          7.解析 JSON 數(shù)據(jù)時(shí),默認(rèn)將數(shù)值當(dāng)做哪種類型

          在 encode/decode JSON 數(shù)據(jù)時(shí),Go 默認(rèn)會(huì)將數(shù)值當(dāng)做 float64 處理。

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

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

          解析出來(lái)的 200 是 float 類型。

          8.如何從 panic 中恢復(fù)

          在一個(gè) defer 延遲執(zhí)行的函數(shù)中調(diào)用 recover ,它便能捕捉/中斷 panic。

          // 錯(cuò)誤的 recover 調(diào)用示例
          func main() {
           recover() // 什么都不會(huì)捕捉
           panic("not good"// 發(fā)生 panic,主程序退出
           recover() // 不會(huì)被執(zhí)行
           println("ok")
          }
           
          // 正確的 recover 調(diào)用示例
          func main() {
           defer func() {
            fmt.Println("recovered: ", recover())
           }()
           panic("not good")
          }


          9.簡(jiǎn)短聲明的變量需要注意啥

          • 簡(jiǎn)短聲明的變量只能在函數(shù)內(nèi)部使用

          • struct 的變量字段不能使用 := 來(lái)賦值

          • 不能用簡(jiǎn)短聲明方式來(lái)單獨(dú)為一個(gè)變量重復(fù)聲明, := 左側(cè)至少有一個(gè)新變量,才允許多變量的重復(fù)聲明

          10.range 迭代 map是有序的嗎

          無(wú)序的。Go 的運(yùn)行時(shí)是有意打亂迭代順序的,所以你得到的迭代結(jié)果可能不一致。但也并不總會(huì)打亂,得到連續(xù)相同的 5 個(gè)迭代結(jié)果也是可能的。

          11.recover的執(zhí)行時(shí)機(jī)

          無(wú),recover 必須在 defer 函數(shù)中運(yùn)行。recover 捕獲的是祖父級(jí)調(diào)用時(shí)的異常,直接調(diào)用時(shí)無(wú)效。

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


          直接 defer 調(diào)用也是無(wú)效。

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


          defer 調(diào)用時(shí)多層嵌套依然無(wú)效。

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


          必須在 defer 函數(shù)中直接調(diào)用才有效。

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


          12.閉包錯(cuò)誤引用同一個(gè)變量問(wèn)題怎么處理

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

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


          或者是通過(guò)函數(shù)參數(shù)傳入 i 。

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


          13.在循環(huán)內(nèi)部執(zhí)行defer語(yǔ)句會(huì)發(fā)生啥

          defer 在函數(shù)退出時(shí)才能執(zhí)行,在 for 執(zhí)行 defer 會(huì)導(dǎo)致資源延遲釋放

          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 是一個(gè)局部函數(shù),在局部函數(shù)里面執(zhí)行 defer 將不會(huì)有問(wèn)題。

          14.說(shuō)出一個(gè)避免Goroutine泄露的措施

          可以通過(guò) context 包來(lái)避免內(nèi)存泄漏。

          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 循環(huán)停止取數(shù)據(jù)時(shí),就用 cancel 函數(shù),讓另一個(gè)協(xié)程停止寫數(shù)據(jù)。如果下面 for 已停止讀取數(shù)據(jù),上面 for 循環(huán)還在寫入,就會(huì)造成內(nèi)存泄漏。


          15.如何跳出for select 循環(huán)

          通常在for循環(huán)中,使用break可以跳出循環(huán),但是注意在go語(yǔ)言中,for select配合時(shí),break 并不能跳出循環(huán)。

          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 方法,在排序好的切片中查找指定的方法,但是其返回是對(duì)應(yīng)的查找元素不存在時(shí),待插入的位置下標(biāo)(元素插入在返回下標(biāo)前)。

          可以通過(guò)封裝如下函數(shù),達(dá)到目的。

          func IsExist(s []string, t string) (int, bool) {
              iIndex := sort.SearchStrings(s, t)
              bExist := iIndex!=len(s) && s[iIndex]==t
           
              return iIndex, bExist
          }


          17.如何初始化帶嵌套結(jié)構(gòu)的結(jié)構(gòu)體

          go 的哲學(xué)是組合優(yōu)于繼承,使用 struct 嵌套即可完成組合,內(nèi)嵌的結(jié)構(gòu)體屬性就像外層結(jié)構(gòu)的屬性即可,可以直接調(diào)用。

          注意初始化外層結(jié)構(gòu)體時(shí),必須指定內(nèi)
          嵌結(jié)構(gòu)體名稱的結(jié)構(gòu)體初始化,如下看到 s1方式報(bào)錯(cuò),s2 方式正確。

          type stPeople struct {
              Gender bool
              Name string
          }
           
          type stStudent struct {
              stPeople
              Class int
          }
           
          //嘗試4 嵌套結(jié)構(gòu)的初始化表達(dá)式
          //var s1 = stStudent{false, "JimWen", 3}
          var s2 = stStudent{stPeople{false"JimWen"}, 3}
          fmt.Println(s2.Gender, s2.Name, s2.Class)


          18.切片和數(shù)組的區(qū)別

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

          數(shù)組需要指定大小,不指定也會(huì)根據(jù)初始化的自動(dòng)推算出大小,不可改變;數(shù)組是值傳遞。

          數(shù)組是內(nèi)置類型,是一組同類型數(shù)據(jù)的集合,它是值類型,通過(guò)從0開始的下標(biāo)索引訪問(wèn)元素值。在初始化后長(zhǎng)度是固定的,無(wú)法修改其長(zhǎng)度。

          當(dāng)作為方法的參數(shù)傳入時(shí)將復(fù)制一份數(shù)組而不是引用同一指針。數(shù)組的長(zhǎng)度也是其類型的一部分,通過(guò)內(nèi)置函數(shù)len(array)獲取其長(zhǎng)度。

          數(shù)組定義:

          1. var array [10]int


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


          切片表示一個(gè)擁有相同類型元素的可變長(zhǎng)度的序列。切片是一種輕量級(jí)的數(shù)據(jù)結(jié)構(gòu),它有三個(gè)屬性:指針、長(zhǎng)度和容量。

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

          切片定義:

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

          19.new和make的區(qū)別

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

          new 函數(shù)是內(nèi)建函數(shù),函數(shù)定義:func new(Type) *Type。

          使用 new 函數(shù)來(lái)分配空間。傳遞給 new 函數(shù)的是一個(gè)類型,不是一個(gè)值。返回值是指向這個(gè)新分配的零值的指針。

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

          make 函數(shù)是內(nèi)建函數(shù),函數(shù)定義:func make(Type, size IntegerType) Type;第一個(gè)參數(shù)是一個(gè)類型,第二個(gè)參數(shù)是長(zhǎng)度;返回值是一個(gè)類型。

          make(T, args) 函數(shù)的目的與 new(T) 不同。它僅僅用于創(chuàng)建 Slice, Map 和 Channel,并且返回類型是 T(不是T*)的一個(gè)初始化的(不是零值)的實(shí)例。

          20.Printf()、Sprintf()、Fprintf()函數(shù)的區(qū)別用法是什么

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

          Printf(),是把格式字符串輸出到標(biāo)準(zhǔn)輸出(一般是屏幕,可以重定向)。Printf() 是和標(biāo)準(zhǔn)輸出文件 (stdout) 關(guān)聯(lián)的,F(xiàn)printf  則沒有這個(gè)限制。


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

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

          21.說(shuō)說(shuō)go語(yǔ)言中的for循環(huán)

          for 循環(huán)支持 continue 和 break 來(lái)控制循環(huán),但是它提供了一個(gè)更高級(jí)的break,可以選擇中斷哪一個(gè)循環(huán) for 循環(huán)不支持以逗號(hào)為間隔的多個(gè)賦值語(yǔ)句,必須使用平行賦值的方式來(lái)初始化多個(gè)變量。

          22.Array 類型的值作為函數(shù)參數(shù)

          在 C/C++ 中,數(shù)組(名)是指針。將數(shù)組作為參數(shù)傳進(jìn)函數(shù)時(shí),相當(dāng)于傳遞了數(shù)組內(nèi)存地址的引用,在函數(shù)內(nèi)部會(huì)改變?cè)摂?shù)組的值。

          在 Go 中,數(shù)組是值。作為參數(shù)傳進(jìn)函數(shù)時(shí),傳遞的是數(shù)組的
          原始值拷貝,此時(shí)在函數(shù)內(nèi)部是無(wú)法更新該數(shù)組的。

          // 數(shù)組使用值拷貝傳參
          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]
          }


          想改變數(shù)組,直接傳遞指向這個(gè)數(shù)組的
          指針類型。

          // 傳址會(huì)修改原數(shù)據(jù)
          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:即使函數(shù)內(nèi)部得到的是 slice 的值拷貝,但依舊會(huì)更新 slice 的原始數(shù)據(jù)
          (底層 array)

          // 錯(cuò)誤示例
          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)
           }
          }

          說(shuō)。go語(yǔ)言中的for循

          23.說(shuō)說(shuō)go語(yǔ)言中的switch語(yǔ)句

          單個(gè) case 中,可以出現(xiàn)多個(gè)結(jié)果選項(xiàng)。只有在 case 中明確添加 fallthrough關(guān)鍵字,才會(huì)繼續(xù)執(zhí)行緊跟的下一個(gè) case。

          24.說(shuō)說(shuō)go語(yǔ)言中有沒有隱藏的this指針

          方法施加的對(duì)象顯式傳遞,沒有被隱藏起來(lái)。

          golang 的面向?qū)ο蟊磉_(dá)更直觀,對(duì)于面向過(guò)程只是換了一種語(yǔ)法形式來(lái)表達(dá)

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

          25.go語(yǔ)言中的引用類型包含哪些

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

          26.go語(yǔ)言中指針運(yùn)算有哪些

          可以通過(guò)“&”取指針的地址;可以通過(guò)“*”取指針指向的數(shù)據(jù)。

          26.說(shuō)說(shuō)go語(yǔ)言的main函數(shù)

          main 函數(shù)不能帶參數(shù);main 函數(shù)不能定義返回值。

          main 函數(shù)所在的包必須為 main 包;main 函數(shù)中可以使用 flag 包來(lái)獲取和解析命令行參數(shù)。

          27.go語(yǔ)言觸發(fā)異常的場(chǎng)景有哪些

          • 空指針解析

          • 下標(biāo)越界

          • 除數(shù)為0

          • 調(diào)用 panic 函數(shù)

          28.說(shuō)說(shuō)go語(yǔ)言的beego框架

          • beego 是一個(gè) golang 實(shí)現(xiàn)的輕量級(jí)HTTP框架

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

          • 可以使用 bee new 工具生成空工程,然后使用 bee run 命令自動(dòng)熱編譯


          29.說(shuō)說(shuō)go語(yǔ)言的goconvey框架

          • goconvey 是一個(gè)支持 golang 的單元測(cè)試框架

          • goconvey 能夠自動(dòng)監(jiān)控文件修改并啟動(dòng)測(cè)試,并可以將測(cè)試結(jié)果實(shí)時(shí)輸出到web界面

          • goconvey 提供了豐富的斷言簡(jiǎn)化測(cè)試用例的編寫

          30.GoStub的作用是什么

          • GoStub 可以對(duì)全局變量打樁

          • GoStub 可以對(duì)函數(shù)打樁

          • GoStub 不可以對(duì)類的成員方法打樁

          • GoStub 可以打動(dòng)態(tài)樁,比如對(duì)一個(gè)函數(shù)打樁后,多次調(diào)用該函數(shù)會(huì)有不同的行為

          31.go語(yǔ)言編程的好處是什么

          • 編譯和運(yùn)行都很。

          • 在語(yǔ)言層級(jí)支持并行操作。

          • 有垃圾處理器。

          • 內(nèi)置字符串和 maps。

          • 函數(shù)是 go 語(yǔ)言的最基本編程單位。

          32.說(shuō)說(shuō)go語(yǔ)言的select機(jī)制

          • select 機(jī)制用來(lái)處理異步 IO 問(wèn)題

          • select 機(jī)制最大的一條限制就是每個(gè) case 語(yǔ)句里必須是一個(gè) IO 操作

          • golang 在語(yǔ)言級(jí)別支持 select 關(guān)鍵字


          33.解釋一下go語(yǔ)言中的靜態(tài)類型聲明

          靜態(tài)類型聲明是告訴編譯器不需要太多的關(guān)注這個(gè)變量的細(xì)節(jié)。

          靜態(tài)變量的聲明,只是針對(duì)于
          編譯的時(shí)候, 在連接程序的時(shí)候,編譯器還要對(duì)這個(gè)變量進(jìn)行實(shí)際的聲明。


          34.go的接口是什么

          在 go 語(yǔ)言中,interface 也就是接口,被用來(lái)指定一個(gè)對(duì)象。接口具有下面的要素:

          • 一系列的方法

          • 具體應(yīng)用中并用來(lái)表示某個(gè)數(shù)據(jù)類型

          • 在 go 中使用 interface 來(lái)實(shí)現(xiàn)多態(tài)

          35.Go語(yǔ)言里面的類型斷言是怎么回事

          類型斷言是用來(lái)從一個(gè)接口里面讀取數(shù)值給一個(gè)具體的類型變量。

          類型轉(zhuǎn)換是指轉(zhuǎn)換兩個(gè)不相同的數(shù)據(jù)類型。

          36.go語(yǔ)言中局部變量和全局變量的缺省值是什么

          全局變量的缺省值是與這個(gè)類型相關(guān)的零值。


          37.go語(yǔ)言編程的好處是什么

          • 編譯和運(yùn)行都很快。

          • 在語(yǔ)言層級(jí)支持并行操作。

          • 有垃圾處理器。

          • 內(nèi)置字符串和 maps。

          • 函數(shù)是 go 語(yǔ)言的最基本編程單位。

          38.解釋一下go語(yǔ)言中的靜態(tài)類型聲明

          靜態(tài)類型聲明是告訴編譯器不需要太多的關(guān)注這個(gè)變量的細(xì)節(jié)。
          靜態(tài)變量的聲明,只是針對(duì)于編譯的時(shí)候, 在連接程序的時(shí)候,編譯器還要對(duì)這個(gè)變量進(jìn)行實(shí)際的聲明。


          39.模塊化編程是怎么回事

          模塊化編程是指把一個(gè)大的程序分解成幾個(gè)小的程序。這么做的目的是為了減少程序的復(fù)雜度,易于維護(hù),并且達(dá)到最高的效率。

          碼字不易,請(qǐng)不吝點(diǎn)贊,隨手關(guān)注,更多精彩,自動(dòng)送達(dá)。

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

          函數(shù)的定義聲明沒有接收者。


          方法的聲明和函數(shù)類似,他們的區(qū)別是:方法在定義的時(shí)候,會(huì)在func和方法名之間增加一個(gè)參數(shù),這個(gè)參數(shù)就是接收者,這樣我們定義的這個(gè)方法就和接收者綁定在了一起,稱之為這個(gè)接收者的方法。


          Go語(yǔ)言里有兩種類型的接收者:值接收者和指針接收者。

          使用值類型接收者定義的方法,在調(diào)用的時(shí)候,使用的其實(shí)是值接收者的一個(gè)副本,所以對(duì)該值的任何操作,不會(huì)影響原來(lái)的類型變量。-------相當(dāng)于形式參數(shù)。

          如果我們使用一個(gè)指針作為接收者,那么就會(huì)其作用了,因?yàn)橹羔樈邮照邆鬟f的是一個(gè)指向原值指針的副本,指針的副本,指向的還是原來(lái)類型的值,所以修改時(shí),同時(shí)也會(huì)影響原來(lái)類型變量的值。


          41.Golang可變參數(shù)

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

          可以變參數(shù),可以是任意多個(gè)。我們自己也可以定義可以變參數(shù),可變參數(shù)的定義,在類型前加上省略號(hào)…即可。

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


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

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

          可變參數(shù)本質(zhì)上是一個(gè)數(shù)組,所以我們向使用數(shù)組一樣使用它,比如例子中的 for range 循環(huán)。

          42.Golang Slice的底層實(shí)現(xiàn)

          切片是基于數(shù)組實(shí)現(xiàn)的,它的底層是數(shù)組,它自己本身非常小,可以理解為對(duì)底層數(shù)組的抽象。因?yàn)榛跀?shù)組實(shí)現(xiàn),所以它的底層的內(nèi)存是連續(xù)分配的,效率非常高,還可以通過(guò)索引獲得數(shù)據(jù),可以迭代以及垃圾回收優(yōu)化。


          切片本身并不是動(dòng)態(tài)數(shù)組或者數(shù)組指針。它內(nèi)部實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)通過(guò)指針引用底層數(shù)組,設(shè)定相關(guān)屬性將數(shù)據(jù)讀寫操作限定在指定的區(qū)域內(nèi)。切片本身是一個(gè)只讀對(duì)象,其工作機(jī)制類似數(shù)組指針的一種封裝。


          切片對(duì)象非常小,是因?yàn)樗侵挥?個(gè)字段的數(shù)據(jù)結(jié)構(gòu):

          • 指向底層數(shù)組的指針

          • 切片的長(zhǎng)度

          • 切片的容量

          這3個(gè)字段,就是Go語(yǔ)言操作底層數(shù)組的元數(shù)據(jù)。



          43.Golang Slice的擴(kuò)容機(jī)制,有什么注意點(diǎn)

          Go 中切片擴(kuò)容的策略是這樣的:

          首先判斷,如果新申請(qǐng)容量大于 2 倍的舊容量,最終容量就是新申請(qǐng)的容量。

          否則判斷,如果舊切片的長(zhǎng)度小于 1024,則最終容量就是舊容量的兩倍。

          否則判斷,如果舊切片長(zhǎng)度大于等于 1024,則最終容量從舊容量開始循環(huán)增加原來(lái)的  1/4 , 直到最終容量大于等于新申請(qǐng)的容量。

          如果最終容量計(jì)算值溢出,則最終容量就是新申請(qǐng)容量。


          情況一:

          原數(shù)組還有容量可以擴(kuò)容(實(shí)際容量沒有填充完),這種情況下,擴(kuò)容以后的數(shù)組還是指向原來(lái)的數(shù)組,對(duì)一個(gè)切片的操作可能影響多個(gè)指針指向相同地址的Slice。


          情況二:

          原來(lái)數(shù)組的容量已經(jīng)達(dá)到了最大值,再想擴(kuò)容, Go 默認(rèn)會(huì)先開一片內(nèi)存區(qū)域,把原來(lái)的值拷貝過(guò)來(lái),然后再執(zhí)行 append() 操作。這種情況絲毫不影響原數(shù)組。


          要復(fù)制一個(gè)Slice,最好使用Copy函數(shù)。

          44.Golang Map底層實(shí)現(xiàn)

          Golang 中 map 的底層實(shí)現(xiàn)是一個(gè)散列表,因此實(shí)現(xiàn) map 的過(guò)程實(shí)際上就是實(shí)現(xiàn)散表的過(guò)程。
          在這個(gè)散列表中,主要出現(xiàn)的結(jié)構(gòu)體有兩個(gè),一個(gè)叫
          hmap(a header for a go map),一個(gè)叫bmap(a bucket for a Go map,通常叫其bucket)。

          hmap如下所示:



          圖中有很多字段,但是便于理解 map 的架構(gòu),你只需要關(guān)心的只有一個(gè),就是標(biāo)紅的字段:buckets 數(shù)組。Golang 的 map 中用于存儲(chǔ)的結(jié)構(gòu)是 bucket數(shù)組。而 bucket(即bmap)的結(jié)構(gòu)是怎樣的呢?


          bucket:


          相比于 hmap,bucket 的結(jié)構(gòu)顯得簡(jiǎn)單一些,標(biāo)橙的字段依然是“核心”,我們使用的 map 中的 key 和 value 就存儲(chǔ)在這里。

          "高位哈希值"數(shù)組記錄的是當(dāng)前 bucket 中 key 相關(guān)的"索引",稍后會(huì)詳細(xì)敘述。還有一個(gè)字段是一個(gè)指向擴(kuò)容后的 bucket 的指針,使得 bucket 會(huì)形成一個(gè)鏈表結(jié)構(gòu)。


          整體的結(jié)構(gòu)應(yīng)該是這樣的:


          Golang 把求得的哈希值按照用途一分為二:高位和低位。低位用于尋找當(dāng)前 key屬于 hmap 中的哪個(gè) bucket,而高位用于尋找 bucket 中的哪個(gè) key。


          需要特別指出的一點(diǎn)是:map中的key/value值都是存到同一個(gè)數(shù)組中的。這樣做的好處是:在key和value的長(zhǎng)度不同的時(shí)候,可以消除padding帶來(lái)的空間浪費(fèi)。



          Map 的擴(kuò)容:

          當(dāng) Go 的 map 長(zhǎng)度增長(zhǎng)到大于加載因子所需的 map 長(zhǎng)度時(shí),Go 語(yǔ)言就會(huì)將產(chǎn)生一個(gè)新的 bucket 數(shù)組,然后把舊的 bucket 數(shù)組移到一個(gè)屬性字段 oldbucket中。

          注意:并不是立刻把舊的數(shù)組中的元素轉(zhuǎn)義到新的 bucket 當(dāng)中,而是,只有當(dāng)訪問(wèn)到具體的某個(gè) bucket 的時(shí)候,會(huì)把 bucket 中的數(shù)據(jù)轉(zhuǎn)移到新的 bucket 中。


          45. JSON 標(biāo)準(zhǔn)庫(kù)對(duì) nil slice 和 空 slice 的處理是一致的嗎

          首先 JSON 標(biāo)準(zhǔn)庫(kù)對(duì) nil slice 和 空 slice 的處理是不一致。

          通常錯(cuò)誤的用法,會(huì)報(bào)數(shù)組越界的錯(cuò)誤,因?yàn)橹皇锹暶髁藄lice,卻沒有給實(shí)例化的對(duì)象。

          var slice []int
          slice[1] = 0

          此時(shí)slice的值是nil,這種情況可以用于需要返回slice的函數(shù),當(dāng)函數(shù)出現(xiàn)異常的時(shí)候,保證函數(shù)依然會(huì)有nil的返回值。

          empty slice 是指slice不為nil,但是slice沒有值,slice的底層的空間是空的,此時(shí)的定義如下:

          slice := make([]int,0
          slice := []int{}

          當(dāng)我們查詢或者處理一個(gè)空的列表的時(shí)候,這非常有用,它會(huì)告訴我們返回的是一個(gè)列表,但是列表內(nèi)沒有任何值。

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


          46.Golang的內(nèi)存模型,為什么小對(duì)象多了會(huì)造成gc壓力

          通常小對(duì)象過(guò)多會(huì)導(dǎo)致 GC 三色法消耗過(guò)多的GPU。優(yōu)化思路是,減少對(duì)象分配。


          47.Data Race問(wèn)題怎么解決?能不能不加鎖解決這個(gè)問(wèn)題

          同步訪問(wèn)共享數(shù)據(jù)是處理數(shù)據(jù)競(jìng)爭(zhēng)的一種有效的方法。

          golang在 1.1 之后引入了競(jìng)爭(zhēng)檢測(cè)機(jī)制,可以使用 go run -race 或者 go build -race來(lái)進(jìn)行靜態(tài)檢測(cè)。其在內(nèi)部的實(shí)現(xiàn)是,開啟多個(gè)協(xié)程執(zhí)行同一個(gè)命令, 并且記錄下每個(gè)變量的狀態(tài)。

          競(jìng)爭(zhēng)檢測(cè)器基于C/C++的ThreadSanitizer 運(yùn)行時(shí)庫(kù),該庫(kù)在Google內(nèi)部代碼基地和Chromium找到許多錯(cuò)誤。這個(gè)技術(shù)在2012年九月集成到Go中,從那時(shí)開始,它已經(jīng)在標(biāo)準(zhǔn)庫(kù)中檢測(cè)到42個(gè)競(jìng)爭(zhēng)條件。現(xiàn)在,它已經(jīng)是我們持續(xù)構(gòu)建過(guò)程的一部分,當(dāng)競(jìng)爭(zhēng)條件出現(xiàn)時(shí),它會(huì)繼續(xù)捕捉到這些錯(cuò)誤。

          競(jìng)爭(zhēng)檢測(cè)器已經(jīng)完全集成到Go工具鏈中,僅僅添加-race標(biāo)志到命令行就使用了檢測(cè)器。

          $ go test -race mypkg    // 測(cè)試包
          $ go run -race mysrc.go  // 編譯和運(yùn)行程序 $ go build -race mycmd 
          // 構(gòu)建程序 $ go install -race mypkg // 安裝程序


          要想解決數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題可以使用互斥鎖sync.Mutex,解決數(shù)據(jù)競(jìng)爭(zhēng)(Data race),也可以使用管道解決,使用管道的效率要比互斥鎖高。


          48.在 range 迭代 slice 時(shí),你怎么修改值的

          在 range 迭代中,得到的值其實(shí)是元素的一份值拷貝,更新拷貝并不會(huì)更改原來(lái)的元素,即是拷貝的地址并不是原有元素的地址。

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


          如果要修改原有元素的值,應(yīng)該使用索引直接訪問(wèn)。

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


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

          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 的區(qū)別

          雖然 interface 看起來(lái)像指針類型,但它不是。interface 類型的變量只有在類型和值均為 nil 時(shí)才為 nil

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

          如果你的函數(shù)返回值類型是 interface,更要小心這個(gè)坑:

          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 intinterface{} {
            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可以用于什么

          常用語(yǔ)gorotine的完美退出。

          golang 的 select 就是監(jiān)聽 IO 操作,當(dāng) IO 操作發(fā)生時(shí),觸發(fā)相應(yīng)的動(dòng)作

          每個(gè)case語(yǔ)句里必須是一個(gè)IO操作,確切的說(shuō),應(yīng)該是一個(gè)面向channel的IO操作。


          結(jié)尾

          本文總結(jié)了常見的 Go 語(yǔ)法問(wèn)題,且在工作和面試中都容易出錯(cuò)的。可以供大家學(xué)習(xí)和面試用。

          Golang 的開源社區(qū)在不斷的壯大,是一門值得學(xué)習(xí)的語(yǔ)言。我公眾號(hào)有Golang 學(xué)習(xí)的全面資料,可以領(lǐng)取,祝大家學(xué)習(xí)愉快!


          ·················· END ··················

          點(diǎn)擊關(guān)注公眾號(hào),免費(fèi)領(lǐng)學(xué)習(xí)資料

                                                                                              點(diǎn)“贊”和“在看”哦

          瀏覽 83
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色视频在线国产 | 日韩无码第一页 | 午夜激情福利 | 国产一级免费观看视频 | 婷婷丁香五月婷婷 |