GO語言面試系列:(二)常規性Golang面試題解析
最近在很多地方看到了ofollow,noindex">golang的面試題 ,看到了很多人對Golang的面試題心存恐懼,也是為了複習基礎,我把解題的過程總結下來。
面試題
1. 寫出下面程式碼輸出內容。
package main import ( "fmt" ) func main() { defer_call() } func defer_call() { defer func() { fmt.Println("列印前") }() defer func() { fmt.Println("列印中") }() defer func() { fmt.Println("列印後") }() panic("觸發異常") }
考點:defer執行順序
解答:defer 是後進先出。panic 需要等defer 結束後才會向上傳遞。
出現panic恐慌時候,會先按照defer的後入先出的順序執行,最後才會執行panic。
列印後 列印中 列印前 panic: 觸發異常
近期有同學遇到多次執行的時候發現panic的執行順序不定,那麼是不是因為panic與defer沒有先後關係呢?我們先來下面的例子:
func main() { defer_call() } func defer_call() { defer func() { if err := recover(); err != nil { fmt.Println("one=", err) } }() defer func() { fmt.Println("列印前") }() defer func() { fmt.Println("列印中") }() defer func() { fmt.Println("列印後") }() panic("觸發異常") }
大家再多次執行,看看是否都是輸出:
列印後 列印中 列印前 one= 觸發異常
那為什麼沒有加recover()時候,panic執行順序不定呢?
defer的執行順序肯定是FILO的,但是沒有被recover的panic協程(執行緒)可能爭奪CPU的順序比defer快,所以造成了這樣的情況,也可能是寫快取問題,所以對panic進行recover將其加入到defer佇列中。
2. 以下程式碼有什麼問題,說明原因。
type student struct { Name string Ageint } func pase_student() { m := make(map[string]*student) stus := []student{ {Name: "zhou", Age: 24}, {Name: "li", Age: 23}, {Name: "wang", Age: 22}, } for _, stu := range stus { m[stu.Name] = &stu } }
考點:foreach
解答:這樣的寫法初學者經常會遇到的,很危險!
與Java的foreach一樣,都是使用副本的方式。所以m[stu.Name]=&stu實際上一致指向同一個指標, 最終該指標的值為遍歷的最後一個struct的值拷貝。 就像想修改切片元素的屬性:
for _, stu := range stus { stu.Age = stu.Age+10 }
也是不可行的。 大家可以試試打印出來:
func pase_student() { m := make(map[string]*student) stus := []student{ {Name: "zhou", Age: 24}, {Name: "li", Age: 23}, {Name: "wang", Age: 22}, } // 錯誤寫法 for _, stu := range stus { m[stu.Name] = &stu } for k,v:=range m{ println(k,"=>",v.Name) } // 正確 for i:=0;i<len(stus);i++{ m[stus[i].Name] = &stus[i] } for k,v:=range m{ println(k,"=>",v.Name) } }
3. 下面的程式碼會輸出什麼,並說明原因
func main() { runtime.GOMAXPROCS(1) wg := sync.WaitGroup{} wg.Add(20) for i := 0; i < 10; i++ { go func() { fmt.Println("A: ", i) wg.Done() }() } for i := 0; i < 10; i++ { go func(i int) { fmt.Println("B: ", i) wg.Done() }(i) } wg.Wait() }
考點:go執行的隨機性和閉包
解答:誰也不知道執行後列印的順序是什麼樣的,所以只能說是隨機數字。
但是A:均為輸出10,B:從0~9輸出(順序不定)。 第一個go func中i是外部for的一個變數,地址不變化。遍歷完成後,最終i=10。 故go func執行時,i的值始終是10。
第二個go func中i是函式引數,與外部for中的i完全是兩個變數。 尾部(i)將發生值拷貝,go func內部指向值拷貝地址。
4. 下面程式碼會輸出什麼?
type People struct{} func (p *People) ShowA() { fmt.Println("showA") p.ShowB() } func (p *People) ShowB() { fmt.Println("showB") } type Teacher struct { People } func (t *Teacher) ShowB() { fmt.Println("teacher showB") } func main() { t := Teacher{} t.ShowA() }
考點:go的組合繼承
解答:這是Golang的組合模式,可以實現OOP的繼承。 被組合的型別People所包含的方法雖然升級成了外部型別Teacher這個組合型別的方法(一定要是匿名欄位),但它們的方法(ShowA())呼叫時接受者並沒有發生變化。 此時People型別並不知道自己會被什麼型別組合,當然也就無法呼叫方法時去使用未知的組合者Teacher型別的功能。
showA showB
5. 下面程式碼會觸發異常嗎?請詳細說明
func main() { runtime.GOMAXPROCS(1) int_chan := make(chan int, 1) string_chan := make(chan string, 1) int_chan <- 1 string_chan <- "hello" select { case value := <-int_chan: fmt.Println(value) case value := <-string_chan: panic(value) } }
考點:select隨機性
解答:select會隨機選擇一個可用通用做收發操作。 所以程式碼是有肯觸發異常,也有可能不會。
單個chan如果無緩衝時,將會阻塞。但結合 select可以在多個chan間等待執行。有三點原則:
- select 中只要有一個case能return,則立刻執行。
- 當如果同一時間有多個case均能return則偽隨機方式抽取任意一個執行。
- 如果沒有一個case能return則可以執行”default”塊。
6. 下面程式碼輸出什麼?
func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main() { a := 1 b := 2 defer calc("1", a, calc("10", a, b)) a = 0 defer calc("2", a, calc("20", a, b)) b = 1 }
考點:defer執行順序
解答:
這道題類似第1題 需要注意到defer執行順序和值傳遞 index:1肯定是最後執行的,但是index:1的第三個引數是一個函式,所以最先被呼叫calc(“10”,1,2)==>10,1,2,3 執行index:2時,與之前一樣,需要先呼叫calc(“20”,0,2)==>20,0,2,2 執行到b=1時候開始呼叫,index:2==>calc(“2”,0,2)==>2,0,2,2 最後執行index:1==>calc(“1”,1,3)==>1,1,3,4
7. 請寫出以下輸入內容
func main() { s := make([]int, 5) s = append(s, 1, 2, 3) fmt.Println(s) }
考點:make預設值和append
解答:make初始化是由預設值的哦,此處預設值為0
[0 0 0 0 0 1 2 3]
大家試試改為:
s := make([]int, 0) s = append(s, 1, 2, 3) fmt.Println(s)//[1 2 3]
8. 下面的程式碼有什麼問題?
type UserAges struct { ages map[string]int sync.Mutex } func (ua *UserAges) Add(name string, age int) { ua.Lock() defer ua.Unlock() ua.ages[name] = age } func (ua *UserAges) Get(name string) int { if age, ok := ua.ages[name]; ok { return age } return -1 }
考點:map執行緒安全
解答:可能會出現fatal error: concurrent map read and map write. 修改一下看看效果
func (ua *UserAges) Get(name string) int { ua.Lock() defer ua.Unlock() if age, ok := ua.ages[name]; ok { return age } return -1 }
9. 下面的迭代會有什麼問題?
func (set *threadSafeSet) Iter() <-chan interface{} { ch := make(chan interface{}) go func() { set.RLock() for elem := range set.s { ch <- elem } close(ch) set.RUnlock() }() return ch }
考點:chan快取池
解答:看到這道題,我也在猜想出題者的意圖在哪裡。 chan?sync.RWMutex?go?chan快取池?迭代? 所以只能再讀一次題目,就從迭代入手看看。 既然是迭代就會要求set.s全部可以遍歷一次。但是chan是為快取的,那就代表這寫入一次就會阻塞。 我們把程式碼恢復為可以執行的方式,看看效果
package main import ( "sync" "fmt" ) //下面的迭代會有什麼問題? type threadSafeSet struct { sync.RWMutex s []interface{} } func (set *threadSafeSet) Iter() <-chan interface{} { // ch := make(chan interface{}) // 解除註釋看看! ch := make(chan interface{},len(set.s)) go func() { set.RLock() for elem,value := range set.s { ch <- elem println("Iter:",elem,value) } close(ch) set.RUnlock() }() return ch } func main(){ th:=threadSafeSet{ s:[]interface{}{"1","2"}, } v:=<-th.Iter() fmt.Sprintf("%s%v","ch",v) }
10. 以下程式碼能編譯過去嗎?為什麼?
package main import ( "fmt" ) type People interface { Speak(string) string } type Stduent struct{} func (stu *Stduent) Speak(think string) (talk string) { if think == "bitch" { talk = "You are a good boy" } else { talk = "hi" } return } func main() { var peo People = Stduent{} think := "bitch" fmt.Println(peo.Speak(think)) }
考點:golang的方法集
解答:編譯不通過! 做錯了!?說明你對golang的方法集還有一些疑問。
一句話:golang的方法集僅僅影響介面實現和方法表示式轉化,與通過例項或者指標呼叫方法無關。
11. 以下程式碼打印出來什麼內容,說出為什麼。
package main import ( "fmt" ) type People interface { Show() } type Student struct{} func (stu *Student) Show() { } func live() People { var stu *Student return stu } func main() { if live() == nil { fmt.Println("AAAAAAA") } else { fmt.Println("BBBBBBB") } }
考點:interface內部結構
解答:很經典的題! 這個考點是很多人忽略的interface內部結構。 go中的介面分為兩種一種是空的介面類似這樣:
var in interface{}
另一種如題目:
type People interface { Show() }
他們的底層結構如下:
type eface struct {//空介面 _type *_type//型別資訊 dataunsafe.Pointer //指向資料的指標(go語言中特殊的指標型別unsafe.Pointer類似於c語言中的void*) } type iface struct {//帶有方法的介面 tab*itab//儲存type資訊還有結構實現方法的集合 data unsafe.Pointer//指向資料的指標(go語言中特殊的指標型別unsafe.Pointer類似於c語言中的void*) } type _type struct { sizeuintptr//型別大小 ptrdatauintptr//字首持有所有指標的記憶體大小 hashuint32//資料hash值 tflagtflag alignuint8//對齊 fieldalign uint8//嵌入結構體時的對齊 kinduint8//kind 有些列舉值kind等於0是無效的 alg*typeAlg //函式指標陣列,型別實現的所有方法 gcdata*byte strnameOff ptrToThis typeOff } type itab struct { inter*interfacetype//介面型別 _type*_type//結構型別 link*itab badint32 inhash int32 fun[1]uintptr//可變大小 方法集合 }
可以看出iface比eface 中間多了一層itab結構。 itab 儲存_type資訊和[]fun方法集,從上面的結構我們就可得出,因為data指向了nil 並不代表interface 是nil, 所以返回值並不為空,這裡的fun(方法集)定義了介面的接收規則,在編譯的過程中需要驗證是否實現介面 結果:
BBBBBBB
來源:https://my.oschina.net/u/553243/blog/1478739