## 一、寫出下面代碼輸出內容。
~~~
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: 觸發異常
~~~
## 二、以下代碼有什么問題,說明原因。
~~~
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) }}
~~~
## 三、下面的代碼會輸出什么,并說明原因
~~~
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內部指向值拷貝地址。
## 四、下面代碼會輸出什么?
~~~
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類型的功能。
~~~
showAshowB
~~~
## 五、下面代碼會觸發異常嗎?請詳細說明
~~~
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”塊。
## 六、下面代碼輸出什么?
~~~
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
~~~
10 1 2 320 0 2 22 0 2 21 1 3 4
~~~
## 七、請寫出以下輸入內容
~~~
func main() { s := make([] int, 0) 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]
~~~
## 八、下面的代碼有什么問題?
~~~
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}
~~~
## 九、下面的迭代會有什么問題?
~~~
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 mainimport( "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)}
~~~
## 十、以下代碼能編譯過去嗎?為什么?
~~~
package mainimport ( "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的方法集僅僅影響接口實現和方法表達式轉化,與通過實例或者指針調用方法無關。
## 十一、 以下代碼打印出來什么內容,說出為什么。
~~~
package mainimport( "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 //類型信息 data unsafe.Pointer //指向數據的指針(go語言中特殊的指針類型unsafe.Pointer類似于c語言中的void*)}type iface struct { //帶有方法的接口 tab *itab //存儲type信息還有結構實現方法的集合 data unsafe.Pointer //指向數據的指針(go語言中特殊的指針類型unsafe.Pointer類似于c語言中的void*)}type _type struct { size uintptr //類型大小 ptrdata uintptr //前綴持有所有指針的內存大小 hash uint32 //數據hash值 tflag tflag align uint8 //對齊 fieldalign uint8 //嵌入結構體時的對齊 kind uint8 //kind 有些枚舉值kind等于0是無效的 alg *typeAlg //函數指針數組,類型實現的所有方法 gcdata *byte str nameOff ptrToThis typeOff}type itab struct { inter *interfacetype //接口類型 _type *_type //結構類型 link *itab bad int32 inhash int32 fun [1]uintptr //可變大小 方法集合}
~~~
可以看出iface比eface 中間多了一層itab結構。 itab 存儲\_type信息和\[\]fun方法集,從上面的結構我們就可得出,因為data指向了nil 并不代表interface 是nil,所以返回值并不為空,這里的fun(方法集)定義了接口的接收規則,在編譯的過程中需要驗證是否實現接口結果:
~~~
BBBBBBB
~~~
- 一、經典(一)
- 二、經典(二)
- 三、經典(三)
- 四、經典(四)
- 五、經典(五)
- 六、經典(六)
- 七、經典(七)
- 八、經典(八)
- 九、經典(九)
- 十、經典(十)
- 十一、經典(十一)
- 十二、經典(十二)
- 其他
- 1、知識點一
- 2、面試集
- 3、負載均衡原理
- 4、LVS相關了解
- 5、微服務架構
- 6、分布式鎖實現原理
- 7、Etcd怎么實現分布式鎖
- 8、Redis的數據結構有哪些,以及實現場景
- 9、Mysql高可用方案有哪些
- 10、Go語言的棧空間管理是怎么樣的
- 11、Goroutine和Channel的作用分別是什么
- 12、Go中的鎖有哪些?三種鎖,讀寫鎖,互斥鎖,還有map的安全的鎖?
- 13、怎么限制Goroutine的數量
- 14、Goroutine和線程的區別?
- 15、中間件原理