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

          最新大廠 Go 面試題 50 問

          共 4098字,需瀏覽 9分鐘

           ·

          2022-06-11 12:42


          前言

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

          這期針對大家的疑惑,總結(jié)了大廠系列Golang語法50問,供大家面試和學習用,下面看一下提綱。

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

          允許對值為 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?的正確聲明,分配了實際的內(nèi)存
          }????
          ?
          //?slice?正確示例
          func?main()?{
          ?var?s?[]int
          ?s?=?append(s,?1)
          }


          2.訪問 map 中的 key,需要注意啥

          當訪問 map 中不存在的 key 時,Go 則會返回元素對應(yīng)數(shù)據(jù)類型的零值,比如 nil、''?、false 和?0,取值操作總有值返回,故不能通過取出來的值,來判斷 key 是不是在 map 中。

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

          //?錯誤的?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 轉(zhuǎn)為?[]byte 修改后,再轉(zhuǎn)為 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 中如何強制執(zhí)行下一個 case 代碼塊

          switch 語句中的 case 代碼塊會默認帶上 break,但可以使用 fallthrough 來強制執(zhí)行下一個 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.你是如何關(guān)閉 HTTP 的響應(yīng)體的

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

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

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

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

            設(shè)置?Header?請求頭部選項?Connection:?close,然后服務(wù)器返回的響應(yīng)頭部也會有這個選項,此時?HTTP?標準庫會主動斷開連接

            //?主動關(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)建一個自定義配置的 HTTP transport 客戶端,用來取消 HTTP 全局的復用連接。

          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ù)值當做哪種類型

          在 encode/decode JSON 數(shù)據(jù)時,Go 默認會將數(shù)值當做 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 延遲執(zhí)行的函數(shù)中調(diào)用 recover ,它便能捕捉/中斷 panic。

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


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

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

          • struct?的變量字段不能使用?:=?來賦值

          • 不能用簡短聲明方式來單獨為一個變量重復聲明,?:=?左側(cè)至少有一個新變量,才允許多變量的重復聲明

          10.range?迭代 map是有序的嗎

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

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

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

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


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

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


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

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


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

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


          或者是通過函數(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語句會發(fā)生啥

          defer 在函數(shù)退出時才能執(zhí)行,在 for 執(zhí)行 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 是一個局部函數(shù),在局部函數(shù)里面執(zhí)行 defer 將不會有問題。

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

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


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

          通常在for循環(huán)中,使用break可以跳出循環(huán),但是注意在go語言中,for select配合時,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 方法,在排序好的切片中查找指定的方法,但是其返回是對應(yīng)的查找元素不存在時,待插入的位置下標(元素插入在返回下標前)。

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

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

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

          type?stPeople?struct?{
          ????Gender?bool
          ????Name?string
          }
          ?
          type?stStudent?struct?{
          ????stPeople
          ????Class?int
          }
          ?
          //嘗試4?嵌套結(jié)構(gòu)的初始化表達式
          //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ù)組是具有固定長度,且擁有零個或者多個,相同數(shù)據(jù)類型元素的序列。數(shù)組的長度是數(shù)組類型的一部分,所以[3]int 和 [4]int 是兩種不同的數(shù)組類型。

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

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

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

          數(shù)組定義:

          1. var array [10]int


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


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

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

          切片定義:

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

          在 Go 中,數(shù)組是值。作為參數(shù)傳進函數(shù)時,傳遞的是數(shù)組的
          原始值拷貝,此時在函數(shù)內(nèi)部是無法更新該數(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ù)組,直接傳遞指向這個數(shù)組的
          指針類型。

          //?傳址會修改原數(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 的值拷貝,但依舊會更新 slice 的原始數(shù)據(jù)
          (底層 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循

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

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

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

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

          golang 的面向?qū)ο蟊磉_更直觀,對于面向過程只是換了一種語法形式來表達

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

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

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

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

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

          26.說說go語言的main函數(shù)

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

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

          27.go語言觸發(fā)異常的場景有哪些

          • 空指針解析

          • 下標越界

          • 除數(shù)為0

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

          28.說說go語言的beego框架

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

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

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


          29.說說go語言的goconvey框架

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

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

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

          30.GoStub的作用是什么

          • GoStub 可以對全局變量打樁

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

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

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

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

          • 編譯和運行都很。

          • 在語言層級支持并行操作。

          • 有垃圾處理器。

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

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

          32.說說go語言的select機制

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

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

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


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

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

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


          34.go的接口是什么

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

          • 一系列的方法

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

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

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

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

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

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

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


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

          • 編譯和運行都很快。

          • 在語言層級支持并行操作。

          • 有垃圾處理器。

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

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

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

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


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

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

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

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

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


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


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

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

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


          41.Golang可變參數(shù)

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

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

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


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

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

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

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

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


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


          切片對象非常小,是因為它是只有3個字段的數(shù)據(jù)結(jié)構(gòu):

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

          • 切片的長度

          • 切片的容量

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



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

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

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

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

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

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


          情況一:

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


          情況二:

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


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

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

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

          hmap如下所示:



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


          bucket:


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

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


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


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


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



          Map 的擴容:

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

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


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

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

          通常錯誤的用法,會報數(shù)組越界的錯誤,因為只是聲明了slice,卻沒有給實例化的對象。

          var?slice?[]int
          slice[1]?=?0

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

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

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

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

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


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

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


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

          同步訪問共享數(shù)據(jù)是處理數(shù)據(jù)競爭的一種有效的方法。

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

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

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

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


          要想解決數(shù)據(jù)競爭的問題可以使用互斥鎖sync.Mutex,解決數(shù)據(jù)競爭(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]
          }


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

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

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

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

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

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

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

          ???in?=?data
          ???fmt.Println(in,?in?==?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:?
          ??}
          }


          50.select可以用于什么

          常用語gorotine的完美退出。

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

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


          結(jié)尾

          本文總結(jié)了常見的 Go 語法問題,且在工作和面試中都容易出錯的??梢怨┐蠹覍W習和面試用。

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


          ··················?END?··················

          點擊關(guān)注公眾號,免費領(lǐng)學習資料

          ????????????????????????????????????????????????????????????????????????????????????點“贊”和“在看”哦

          瀏覽 18
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  黄色一级免费大片 | 亚洲第一黄 | 欧美色就是色 | 天天舔日日操 | 日本黄色视频在线免费观看 |