<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 語言切片面試真題 8 連問

          共 2768字,需瀏覽 6分鐘

           ·

          2022-04-08 20:18

          前言

          最近沒事在看八股文,總結了幾道??嫉那衅斯晌?,以問答的方式總結出來,希望對正在面試的你們有用~

          本文題目不全,關于切片的面試真題還有哪些?歡迎評論區(qū)補充~

          01. 數(shù)組和切片有什么區(qū)別?

          Go語言中數(shù)組是固定長度的,不能動態(tài)擴容,在編譯期就會確定大小,聲明方式如下:

          var?buffer?[255]int
          buffer?:=?[255]int{0}

          切片是對數(shù)組的抽象,因為數(shù)組的長度是不可變的,在某些場景下使用起來就不是很方便,所以Go語言提供了一種靈活,功能強悍的內置類型切片("動態(tài)數(shù)組"),與數(shù)組相比切片的長度是不固定的,可以追加元素。切片是一種數(shù)據(jù)結構,切片不是數(shù)組,切片描述的是一塊數(shù)組,切片結構如下:

          我們可以直接聲明一個未指定大小的數(shù)組來定義切片,也可以使用make()函數(shù)來創(chuàng)建切片,聲明方式如下:

          var?slice?[]int?//?直接聲明
          slice?:=?[]int{1,2,3,4,5}?//?字面量方式
          slice?:=?make([]int,?5,?10)?//?make創(chuàng)建
          slice?:=?array[1:5]?//?截取下標的方式
          slice?:=?*new([]int)?//?new一個

          切片可以使用append追加元素,當cap不足時進行動態(tài)擴容。

          02. 拷貝大切片一定比拷貝小切片代價大嗎?

          這道題比較有意思,原文地址:Are large slices more expensive than smaller ones?

          這道題本質是考察對切片本質的理解,Go語言中只有值傳遞,所以我們以傳遞切片為例子:

          func?main()??{
          ?param1?:=?make([]int,?100)
          ?param2?:=?make([]int,?100000000)
          ?smallSlice(param1)
          ?largeSlice(param2)
          }

          func?smallSlice(params?[]int)??{
          ?//?....
          }

          func?largeSlice(params?[]int)??{
          ?//?....
          }

          切片param2要比param11000000個數(shù)量級,在進行值拷貝的時候,是否需要更昂貴的操作呢?

          實際上不會,因為切片本質內部結構如下:

          type?SliceHeader?struct?{
          ?Data?uintptr
          ?Len??int
          ?Cap??int
          }

          切片中的第一個字是指向切片底層數(shù)組的指針,這是切片的存儲空間,第二個字段是切片的長度,第三個字段是容量。將一個切片變量分配給另一個變量只會復制三個機器字,大切片跟小切片的區(qū)別無非就是 LenCap的值比小切片的這兩個值大一些,如果發(fā)生拷貝,本質上就是拷貝上面的三個字段。

          03. 切片的深淺拷貝

          深淺拷貝都是進行復制,區(qū)別在于復制出來的新對象與原來的對象在它們發(fā)生改變時,是否會相互影響,本質區(qū)別就是復制出來的對象與原對象是否會指向同一個地址。在Go語言,切片拷貝有三種方式:

          • 使用=操作符拷貝切片,這種就是淺拷貝
          • 使用[:]下標的方式復制切片,這種也是淺拷貝
          • 使用Go語言的內置函數(shù)copy()進行切片拷貝,這種就是深拷貝,

          04. 零切片、空切片、nil切片是什么

          為什么問題中這么多種切片呢?因為在Go語言中切片的創(chuàng)建方式有五種,不同方式創(chuàng)建出來的切片也不一樣;

          • 零切片

          我們把切片內部數(shù)組的元素都是零值或者底層數(shù)組的內容就全是 nil的切片叫做零切片,使用make創(chuàng)建的、長度、容量都不為0的切片就是零值切片:

          slice?:=?make([]int,5)?//?0?0?0?0?0
          slice?:=?make([]*int,5)?//?nil?nil?nil?nil?nil
          • nil切片

          nil切片的長度和容量都為0,并且和nil比較的結果為true,采用直接創(chuàng)建切片的方式、new創(chuàng)建切片的方式都可以創(chuàng)建nil切片:

          var?slice?[]int
          var?slice?=?*new([]int)
          • 空切片

          空切片的長度和容量也都為0,但是和nil的比較結果為false,因為所有的空切片的數(shù)據(jù)指針都指向同一個地址 0xc42003bda0;使用字面量、make可以創(chuàng)建空切片:

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

          空切片指向的 zerobase 內存地址是一個神奇的地址,從 Go 語言的源代碼中可以看到它的定義:

          //?base?address?for?all?0-byte?allocations
          var?zerobase?uintptr

          //?分配對象內存
          func?mallocgc(size?uintptr,?typ?*_type,?needzero?bool)?unsafe.Pointer?{
          ?...
          ?if?size?==?0?{
          ??return?unsafe.Pointer(&zerobase)
          ?}
          ??...
          }

          05. 切片的擴容策略

          這個問題是一個高頻考點,我們通過源碼來解析一下切片的擴容策略,切片的擴容都是調用growslice方法,截取部分重要源代碼:

          //?runtime/slice.go
          // et:表示slice的一個元素;old:表示舊的slice;cap:表示新切片需要的容量;
          func?growslice(et?*_type,?old?slice,?cap?int)?slice?{
          ?if?cap?cap?{
          ??panic(errorString("growslice:?cap?out?of?range"))
          ?}

          ?if?et.size?==?0?{
          ??//?append?should?not?create?a?slice?with?nil?pointer?but?non-zero?len.
          ??//?We?assume?that?append?doesn't?need?to?preserve?old.array?in?this?case.
          ??return?slice{unsafe.Pointer(&zerobase),?old.len,?cap}
          ?}

          ?newcap?:=?old.cap
          ??//?兩倍擴容
          ?doublecap?:=?newcap?+?newcap
          ??//?新切片需要的容量大于兩倍擴容的容量,則直接按照新切片需要的容量擴容
          ?if?cap?>?doublecap?{
          ??newcap?=?cap
          ?}?else?{
          ????//?原?slice?容量小于?1024?的時候,新?slice?容量按2倍擴容
          ??if?old.cap?1024?{
          ???newcap?=?doublecap
          ??}?else?{?//?原 slice 容量超過 1024,新 slice 容量變成原來的1.25倍。
          ???//?Check?0?
          ???//?and?prevent?an?infinite?loop.
          ???for?0?cap
          ?{
          ????newcap?+=?newcap?/?4
          ???}
          ???//?Set?newcap?to?the?requested?cap?when
          ???//?the?newcap?calculation?overflowed.
          ???if?newcap?<=?0?{
          ????newcap?=?cap
          ???}
          ??}
          ?}

          ??//?后半部分還對 newcap 作了一個內存對齊,這個和內存分配策略相關。進行內存對齊之后,新 slice 的容量是要?大于等于?老 slice 容量的 2倍或者1.25倍。
          ?var?overflow?bool
          ?var?lenmem,?newlenmem,?capmem?uintptr
          ?//?Specialize?for?common?values?of?et.size.
          ?//?For?1?we?don't?need?any?division/multiplication.
          ?//?For?sys.PtrSize,?compiler?will?optimize?division/multiplication?into?a?shift?by?a?constant.
          ?//?For?powers?of?2,?use?a?variable?shift.
          ?switch?{
          ?case?et.size?==?1:
          ??lenmem?=?uintptr(old.len)
          ??newlenmem?=?uintptr(cap)
          ??capmem?=?roundupsize(uintptr(newcap))
          ??overflow?=?uintptr(newcap)?>?maxAlloc
          ??newcap?=?int(capmem)
          ?case?et.size?==?sys.PtrSize:
          ??lenmem?=?uintptr(old.len)?*?sys.PtrSize
          ??newlenmem?=?uintptr(cap)?*?sys.PtrSize
          ??capmem?=?roundupsize(uintptr(newcap)?*?sys.PtrSize)
          ??overflow?=?uintptr(newcap)?>?maxAlloc/sys.PtrSize
          ??newcap?=?int(capmem?/?sys.PtrSize)
          ?case?isPowerOfTwo(et.size):
          ??var?shift?uintptr
          ??if?sys.PtrSize?==?8?{
          ???//?Mask?shift?for?better?code?generation.
          ???shift?=?uintptr(sys.Ctz64(uint64(et.size)))?&?63
          ??}?else?{
          ???shift?=?uintptr(sys.Ctz32(uint32(et.size)))?&?31
          ??}
          ??lenmem?=?uintptr(old.len)?<??newlenmem?=?uintptr(cap)?<??capmem?=?roundupsize(uintptr(newcap)?<??overflow?=?uintptr(newcap)?>?(maxAlloc?>>?shift)
          ??newcap?=?int(capmem?>>?shift)
          ?default:
          ??lenmem?=?uintptr(old.len)?*?et.size
          ??newlenmem?=?uintptr(cap)?*?et.size
          ??capmem,?overflow?=?math.MulUintptr(et.size,?uintptr(newcap))
          ??capmem?=?roundupsize(capmem)
          ??newcap?=?int(capmem?/?et.size)
          ?}
          }

          通過源代碼可以總結切片擴容策略:

          切片在擴容時會進行內存對齊,這個和內存分配策略相關。進行內存對齊之后,新 slice 的容量是要 大于等于老 slice 容量的 2倍或者1.25倍,當原 slice 容量小于 1024 的時候,新 slice 容量變成原來的 2 倍;原 slice 容量超過 1024,新 slice 容量變成原來的1.25倍。

          07. 參數(shù)傳遞切片和切片指針有什么區(qū)別?

          我們都知道切片底層就是一個結構體,里面有三個元素:

          type?SliceHeader?struct?{
          ?Data?uintptr
          ?Len??int
          ?Cap??int
          }

          分別表示切片底層數(shù)據(jù)的地址,切片長度,切片容量。

          當切片作為參數(shù)傳遞時,其實就是一個結構體的傳遞,因為Go語言參數(shù)傳遞只有值傳遞,傳遞一個切片就會淺拷貝原切片,但因為底層數(shù)據(jù)的地址沒有變,所以在函數(shù)內對切片的修改,也將會影響到函數(shù)外的切片,舉例:

          func?modifySlice(s?[]string)??{
          ?s[0]?=?"song"
          ?s[1]?=?"Golang"
          ?fmt.Println("out?slice:?",?s)
          }

          func?main()??{
          ?s?:=?[]string{"asong",?"Golang夢工廠"}
          ?modifySlice(s)
          ?fmt.Println("inner?slice:?",?s)
          }
          //?運行結果
          out?slice:??[song?Golang]
          inner?slice:??[song?Golang]

          不過這也有一個特例,先看一個例子:

          func?appendSlice(s?[]string)??{
          ?s?=?append(s,?"快關注?。?)
          ?fmt.Println("out?slice:?",?s)
          }

          func?main()??{
          ?s?:=?[]string{"asong",?"Golang夢工廠"}
          ?appendSlice(s)
          ?fmt.Println("inner?slice:?",?s)
          }
          //?運行結果
          out slice:??[asong Golang夢工廠?快關注!!]
          inner?slice:??[asong?Golang夢工廠]

          因為切片發(fā)生了擴容,函數(shù)外的切片指向了一個新的底層數(shù)組,所以函數(shù)內外不會相互影響,因此可以得出一個結論,當參數(shù)直接傳遞切片時,如果指向底層數(shù)組的指針被覆蓋或者修改(copy、重分配、append觸發(fā)擴容),此時函數(shù)內部對數(shù)據(jù)的修改將不再影響到外部的切片,代表長度的len和容量cap也均不會被修改

          參數(shù)傳遞切片指針就很容易理解了,如果你想修改切片中元素的值,并且更改切片的容量和底層數(shù)組,則應該按指針傳遞。

          08. range遍歷切片有什么要注意的?

          Go語言提供了range關鍵字用于for 循環(huán)中迭代數(shù)組(array)、切片(slice)、通道(channel)或集合(map)的元素,有兩種使用方式:

          for?k,v?:=?range?_?{?}
          for?k?:=?range?_?{?}

          第一種是遍歷下標和對應值,第二種是只遍歷下標,使用range遍歷切片時會先拷貝一份,然后在遍歷拷貝數(shù)據(jù):

          s?:=?[]int{1,?2}
          for?k,?v?:=?range?s?{
          ??
          }
          會被編譯器認為是
          for_temp?:=?s
          len_temp?:=?len(for_temp)
          for?index_temp?:=?0;?index_temp???value_temp?:=?for_temp[index_temp]
          ??_?=?index_temp
          ??value?:=?value_temp
          ??
          }

          不知道這個知識點的情況下很容易踩坑,例如下面這個例子:

          package?main

          import?(
          ?"fmt"
          )

          type?user?struct?{
          ?name?string
          ?age?uint64
          }

          func?main()??{
          ?u?:=?[]user{
          ??{"asong",23},
          ??{"song",19},
          ??{"asong2020",18},
          ?}
          ?for?_,v?:=?range?u{
          ??if?v.age?!=?18{
          ???v.age?=?20
          ??}
          ?}
          ?fmt.Println(u)
          }
          //?運行結果
          [{asong?23}?{song?19}?{asong2020?18}]

          因為使用range遍歷切片u,變量v是拷貝切片中的數(shù)據(jù),修改拷貝數(shù)據(jù)不會對原切片有影響。

          之前寫了一個對for-range踩坑總結,可以讀一下:面試官:go中for-range使用過嗎?這幾個問題你能解釋一下原因嗎?

          總結

          本文總結了8道切片相關的面試真題,切片一直是面試中的重要考點,把本文這幾個知識點弄會,應對面試官就會變的輕松自如。

          關于切片的面試真題還有哪些?歡迎評論區(qū)補充~


          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  日本东京热视频 | 久操热| 五月丁香婷婷成人 | 午夜精品久久久久久久99黑人 | 日韩破解无码片 |