最近在很多地方看到了golang的面試題鳞上,看到了很多人對(duì)Golang的面試題心存恐懼粒没,也是為了復(fù)習(xí)基礎(chǔ)揩抡,我把解題的過程總結(jié)下來曲管。
面試題
1. 寫出下面代碼輸出內(nè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ā)異常
近期有同學(xué)遇到多次執(zhí)行的時(shí)候發(fā)現(xiàn)panic的執(zhí)行順序不定撬腾,那么是不是因?yàn)閜anic與defer沒有先后關(guān)系呢?我們先來下面的例子:
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("觸發(fā)異常")
}
大家再多次執(zhí)行恢恼,看看是否都是輸出:
打印后
打印中
打印前
one= 觸發(fā)異常
那為什么沒有加recover()時(shí)候民傻,panic執(zhí)行順序不定呢?
defer的執(zhí)行順序肯定是FILO的场斑,但是沒有被recover的panic協(xié)程(線程)可能爭(zhēng)奪CPU的順序比defer快漓踢,所以造成了這樣的情況,也可能是寫緩存問題和簸,所以對(duì)panic進(jìn)行recover將其加入到defer隊(duì)列中彭雾。
2. 以下代碼有什么問題,說明原因锁保。
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
解答:這樣的寫法初學(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
}
也是不可行的做葵。 大家可以試試打印出來:
func pase_student() {
m := make(map[string]*student)
stus := []student{
{Name: "zhou", Age: 24},
{Name: "li", Age: 23},
{Name: "wang", Age: 22},
}
// 錯(cuò)誤寫法
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. 下面的代碼會(huì)輸出什么,并說明原因
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í)行后打印的順序是什么樣的心墅,所以只能說是隨機(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)部指向值拷貝地址棺禾。
4. 下面代碼會(huì)輸出什么缀蹄?
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的繼承帘睦。 被組合的類型People所包含的方法雖然升級(jí)成了外部類型Teacher這個(gè)組合類型的方法(一定要是匿名字段)袍患,但它們的方法(ShowA())調(diào)用時(shí)接受者并沒有發(fā)生變化。 此時(shí)People類型并不知道自己會(huì)被什么類型組合竣付,當(dāng)然也就無法調(diào)用方法時(shí)去使用未知的組合者Teacher類型的功能。
showA
showB
5. 下面代碼會(huì)觸發(fā)異常嗎滞欠?請(qǐng)?jiān)敿?xì)說明
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如果無緩沖時(shí)逸绎,將會(huì)阻塞。但結(jié)合 select可以在多個(gè)chan間等待執(zhí)行夭谤。有三點(diǎn)原則:
- select 中只要有一個(gè)case能return棺牧,則立刻執(zhí)行。
- 當(dāng)如果同一時(shí)間有多個(gè)case均能return則偽隨機(jī)方式抽取任意一個(gè)執(zhí)行朗儒。
- 如果沒有一個(gè)case能return則可以執(zhí)行”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
}
考點(diǎn):defer執(zhí)行順序
解答:
這道題類似第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í)候開始調(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
7. 請(qǐng)寫出以下輸入內(nèi)容
func main() {
s := make([]int, 5)
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]
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
}
考點(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
}
9. 下面的迭代會(huì)有什么問題?
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是為緩存的家妆,那就代表這寫入一次就會(huì)阻塞。 我們把代碼恢復(fù)為可以運(yùn)行的方式冕茅,看看效果
package main
import (
"sync"
"fmt"
)
//下面的迭代會(huì)有什么問題伤极?
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))
}
考點(diǎn):golang的方法集
解答:編譯不通過! 做錯(cuò)了UС当编?說明你對(duì)golang的方法集還有一些疑問。
一句話:golang的方法集僅僅影響接口實(shí)現(xiàn)和方法表達(dá)式轉(zhuǎn)化徒溪,與通過實(shí)例或者指針調(diào)用方法無關(guān)忿偷。
11. 以下代碼打印出來什么內(nèi)容,說出為什么臊泌。
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中的接口分為兩種一種是空的接口類似這樣:
var in interface{}
另一種如題目:
type People interface {
Show()
}
他們的底層結(jié)構(gòu)如下:
type eface struct { //空接口
_type *_type //類型信息
data unsafe.Pointer //指向數(shù)據(jù)的指針(go語(yǔ)言中特殊的指針類型unsafe.Pointer類似于c語(yǔ)言中的void*)
}
type iface struct { //帶有方法的接口
tab *itab //存儲(chǔ)type信息還有結(jié)構(gòu)實(shí)現(xiàn)方法的集合
data unsafe.Pointer //指向數(shù)據(jù)的指針(go語(yǔ)言中特殊的指針類型unsafe.Pointer類似于c語(yǔ)言中的void*)
}
type _type struct {
size uintptr //類型大小
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是無效的
alg *typeAlg //函數(shù)指針數(shù)組渠概,類型實(shí)現(xiàn)的所有方法
gcdata *byte
str nameOff
ptrToThis typeOff
}
type itab struct {
inter *interfacetype //接口類型
_type *_type //結(jié)構(gòu)類型
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ī)則猪狈,在編譯的過程中需要驗(yàn)證是否實(shí)現(xiàn)接口 結(jié)果:
BBBBBBB
來源:https://my.oschina.net/u/553243/blog/1478739
添加小編微信:grey0805箱沦,加入知識(shí)分享小分隊(duì),別掉隊(duì)哦雇庙!