您好,登錄后才能下訂單哦!
最近在很多地方看到了golang的面試題,看到了很多人對(duì)Golang的面試題心存恐懼,也是為了復(fù)習(xí)基礎(chǔ),我把解題的過(guò)程總結(jié)下來(lái)。
package main import ( "fmt" ) func main() { defer_call() } func defer_call() { defer func() { fmt.Println("打印前") }() defer func() { fmt.Println("打印中") }() defer func() { fmt.Println("打印后") }() panic("觸發(fā)異常") }
考點(diǎn):defer執(zhí)行順序
解答: defer 是后進(jìn)先出。
panic
需要等defer 結(jié)束后才會(huì)向上傳遞。出現(xiàn)panic恐慌時(shí)候,會(huì)先按照defer的后入先出的順序執(zhí)行,最后才會(huì)執(zhí)行panic。
打印后 打印中 打印前 panic: 觸發(fā)異常
type student struct { Name string Age int } 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 } }
考點(diǎn):foreach
解答:這樣的寫(xiě)法初學(xué)者經(jīng)常會(huì)遇到的,很危險(xiǎn)!與Java的foreach一樣,都是使用副本的方式。所以m[stu.Name]=&stu實(shí)際上一致指向同一個(gè)指針,最終該指針的值為遍歷的最后一個(gè)struct的值拷貝。就像想修改切片元素的屬性:
for _, stu := range stus { stu.Age = stu.Age+10 }
也是不可行的。大家可以試試打印出來(lái):
func pase_student() { m := make(map[string]*student) stus := []student{ {Name: "zhou", Age: 24}, {Name: "li", Age: 23}, {Name: "wang", Age: 22}, } // 錯(cuò)誤寫(xiě)法 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) } }
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() }
考點(diǎn):go執(zhí)行的隨機(jī)性和閉包
解答:誰(shuí)也不知道執(zhí)行后打印的順序是什么樣的,所以只能說(shuō)是隨機(jī)數(shù)字。但是A:
均為輸出10,B:
從0~9輸出(順序不定)。第一個(gè)go
func中i是外部for的一個(gè)變量,地址不變化。遍歷完成后,最終i=10。故go func執(zhí)行時(shí),i的值始終是10。
第二個(gè)go func中i是函數(shù)參數(shù),與外部for中的i完全是兩個(gè)變量。尾部(i)將發(fā)生值拷貝,go func內(nèi)部指向值拷貝地址。
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() }
考點(diǎn):go的組合繼承
解答:這是Golang的組合模式,可以實(shí)現(xiàn)OOP的繼承。被組合的類(lèi)型People所包含的方法雖然升級(jí)成了外部類(lèi)型Teacher這個(gè)組合類(lèi)型的方法(一定要是匿名字段),但它們的方法(ShowA())調(diào)用時(shí)接受者并沒(méi)有發(fā)生變化。此時(shí)People類(lèi)型并不知道自己會(huì)被什么類(lèi)型組合,當(dāng)然也就無(wú)法調(diào)用方法時(shí)去使用未知的組合者Teacher類(lèi)型的功能。
showA showB
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) } }
考點(diǎn):select隨機(jī)性
解答:
select會(huì)隨機(jī)選擇一個(gè)可用通用做收發(fā)操作。所以代碼是有肯觸發(fā)異常,也有可能不會(huì)。單個(gè)chan如果無(wú)緩沖時(shí),將會(huì)阻塞。但結(jié)合
select可以在多個(gè)chan間等待執(zhí)行。有三點(diǎn)原則:
select 中只要有一個(gè)case能return,則立刻執(zhí)行。 *
當(dāng)如果同一時(shí)間有多個(gè)case均能return則偽隨機(jī)方式抽取任意一個(gè)執(zhí)行。
如果沒(méi)有一個(gè)case能return則可以執(zhí)行”default”塊。
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 }
考點(diǎn):defer執(zhí)行順序
解答:這道題類(lèi)似第1題需要注意到defer執(zhí)行順序和值傳遞
index:1肯定是最后執(zhí)行的,但是index:1的第三個(gè)參數(shù)是一個(gè)函數(shù),所以最先被調(diào)用calc("10",1,2)==>10,1,2,3
執(zhí)行index:2時(shí),與之前一樣,需要先調(diào)用calc("20",0,2)==>20,0,2,2
執(zhí)行到b=1時(shí)候開(kāi)始調(diào)用,index:2==>calc("2",0,2)==>2,0,2,2
最后執(zhí)行index:1==>calc("1",1,3)==>1,1,3,4
10 1 2 3 20 0 2 2 2 0 2 2 1 1 3 4
func main() { s := make([]int, 0) s = append(s, 1, 2, 3) fmt.Println(s) }
考點(diǎn):make默認(rèn)值和append
解答: make初始化是由默認(rèn)值的哦,此處默認(rèn)值為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]
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 }
考點(diǎn):map線程安全
解答:可能會(huì)出現(xiàn)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 }
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 }
考點(diǎn):chan緩存池
解答:看到這道題,我也在猜想出題者的意圖在哪里。
chan?sync.RWMutex?go?chan緩存池?迭代?
所以只能再讀一次題目,就從迭代入手看看。既然是迭代就會(huì)要求set.s全部可以遍歷一次。但是chan是為緩存的,那就代表這寫(xiě)入一次就會(huì)阻塞。我們把代碼恢復(fù)為可以運(yùn)行的方式,看看效果
package main import ( "sync" "fmt" ) //下面的迭代會(huì)有什么問(wèn)題? 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) }
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)) }
考點(diǎn):golang的方法集
解答:編譯不通過(guò)!做錯(cuò)了???說(shuō)明你對(duì)golang的方法集還有一些疑問(wèn)。一句話:golang的方法集僅僅影響接口實(shí)現(xiàn)和方法表達(dá)式轉(zhuǎn)化,與通過(guò)實(shí)例或者指針調(diào)用方法無(wú)關(guān)。
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") } }
考點(diǎn):interface內(nèi)部結(jié)構(gòu)
解答:很經(jīng)典的題!這個(gè)考點(diǎn)是很多人忽略的interface內(nèi)部結(jié)構(gòu)。
go中的接口分為兩種一種是空的接口類(lèi)似這樣:
var in interface{}
另一種如題目:
type People interface { Show() }
他們的底層結(jié)構(gòu)如下:
type eface struct { //空接口 _type *_type //類(lèi)型信息 data unsafe.Pointer //指向數(shù)據(jù)的指針(go語(yǔ)言中特殊的指針類(lèi)型unsafe.Pointer類(lèi)似于c語(yǔ)言中的void*) } type iface struct { //帶有方法的接口 tab *itab //存儲(chǔ)type信息還有結(jié)構(gòu)實(shí)現(xiàn)方法的集合 data unsafe.Pointer //指向數(shù)據(jù)的指針(go語(yǔ)言中特殊的指針類(lèi)型unsafe.Pointer類(lèi)似于c語(yǔ)言中的void*) } type _type struct { size uintptr //類(lèi)型大小 ptrdata uintptr //前綴持有所有指針的內(nèi)存大小 hash uint32 //數(shù)據(jù)hash值 tflag tflag align uint8 //對(duì)齊 fieldalign uint8 //嵌入結(jié)構(gòu)體時(shí)的對(duì)齊 kind uint8 //kind 有些枚舉值kind等于0是無(wú)效的 alg *typeAlg //函數(shù)指針數(shù)組,類(lèi)型實(shí)現(xiàn)的所有方法 gcdata *byte str nameOff ptrToThis typeOff } type itab struct { inter *interfacetype //接口類(lèi)型 _type *_type //結(jié)構(gòu)類(lèi)型 link *itab bad int32 inhash int32 fun [1]uintptr //可變大小 方法集合 }
可以看出iface比eface 中間多了一層itab結(jié)構(gòu)。 itab 存儲(chǔ)_type信息和[]fun方法集,從上面的結(jié)構(gòu)我們就可得出,因?yàn)閐ata指向了nil 并不代表interface 是nil,所以返回值并不為空,這里的fun(方法集)定義了接口的接收規(guī)則,在編譯的過(guò)程中需要驗(yàn)證是否實(shí)現(xiàn)接口結(jié)果:
BBBBBBB
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。