Golang经典面试题上
1. 寫出下面代碼輸出內容
package mainimport ("fmt" )func main() {defer_call() }func defer_call() {defer func() {fmt.Println("打印前")}()defer func() {fmt.Println("打印中")}()defer func() {fmt.Println("打印后")}()panic("觸發異常") }在這個案例中,觸發異常這幾個字打印的順序其實是不確定的。defer, panic, recover一般都會配套使用來捕捉異常。先看下面的案例:
- 案例一
輸出內容為:
觸發異常 打印后 打印中 打印前Process finished with exit code 0- 案例二
輸出內容為:
打印后 觸發異常 打印中 打印前Process finished with exit code 0- 案例三
輸出內容為:
觸發異常 打印后 打印中 打印前Process finished with exit code 0總結:
2. 以下代碼有什么問題,說明原因
package main import ("fmt" ) type student struct {Name stringAge int } func pase_student() map[string]*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}return m } func main() {students := pase_student()for k, v := range students {fmt.Printf("key=%s,value=%v \n", k, v)} }運行結果:
key=zhou,value=&{wang 22} key=li,value=&{wang 22} key=wang,value=&{wang 22} Process finished with exit code 0修改一下代碼:
將下面的代碼:
for _, stu := range stus {m[stu.Name] = &stu }修改為:
for _, stu := range stus {fmt.Printf("%v\t%p\n",stu,&stu)m[stu.Name] = &stu }運行結果為:
{shen 24} 0xc4200a4020 {li 23} 0xc4200a4020 {wang 22} 0xc4200a4020 key=shen,value=&{wang 22} key=li,value=&{wang 22} key=wang,value=&{wang 22} Process finished with exit code 0通過上面的案例,我們不難發現stu變量的地址始終保持不變,每次遍歷僅進行struct值拷貝,故m[stu.Name]=&stu實際上一直指向同一個地址,最終該地址的值為遍歷的最后一個struct的值拷貝。
形同如下代碼:
var stu student for _, stu = range stus {m[stu.Name] = &stu }修正方案,取數組中原始值的地址:
for i, _ := range stus {stu:=stus[i]m[stu.Name] = &stu }重新運行,效果如下:
{shen 24} 0xc42000a060 {li 23} 0xc42000a0a0 {wang 22} 0xc42000a0e0 key=shen,value=&{shen 24} key=li,value=&{li 23} key=wang,value=&{wang 22} Process finished with exit code 03. 下面的代碼會輸出什么,并說明原因
package mainimport ("fmt""runtime""sync" )func init() {fmt.Println("Current Go Version:", runtime.Version()) } func main() {runtime.GOMAXPROCS(1)count := 10wg := sync.WaitGroup{}wg.Add(count * 2)for i := 0; i < count; i++ {go func() {fmt.Printf("[%d]", i)wg.Done()}()}for i := 0; i < count; i++ {go func(i int) {fmt.Printf("-%d-", i)wg.Done()}(i)}wg.Wait() }運行效果:
Current Go Version: go1.10.1 -9-[10][10][10][10][10][10][10][10][10][10]-0--1--2--3--4--5--6--7--8- Process finished with exit code 0兩個for循環內部go func 調用參數i的方式是不同的,導致結果完全不同。這也是新手容易遇到的坑。
第一個go func中i是外部for的一個變量,地址不變化。遍歷完成后,最終i=10。故go func執行時,i的值始終是10(10次遍歷很快完成)。
第二個go func中i是函數參數,與外部for中的i完全是兩個變量。尾部(i)將發生值拷貝,go func內部指向值拷貝地址。
4. 下面代碼會輸出什么?
package mainimport "fmt"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() }運行結果如下:
showA showBProcess finished with exit code 0Go中沒有繼承,上面這種寫法叫組合。
上面的t.ShowA()等價于t.People.ShowA(),將上面的代碼修改如下:
func main() {t := Teacher{}t.ShowA()fmt.Println("---------------")t.People.ShowA() }運行結果為:
showA showB --------------- showA showBProcess finished with exit code 05. 下面代碼會觸發異常嗎?請詳細說明
package mainfunc main() {runtime.GOMAXPROCS(1)int_chan := make(chan int, 1)string_chan := make(chan string, 1)int_chan <- 1string_chan <- "hello"select {case value := <-int_chan:fmt.Println(value)case value := <-string_chan:panic(value)} }有可能會發生異常,如果沒有selct這段代碼,就會出現線程阻塞,當有selct這個語句后,系統會隨機抽取一個case進行判斷,只有有其中一條語句正常return,此程序將立即執行。
6. 下面代碼輸出什么?
package mainimport "fmt"func calc(index string, a, b int) int {ret := a + bfmt.Println(index, a, b, ret)return ret }func main() {a := 1b := 2defer calc("1", a, calc("10", a, b))a = 0defer calc("2", a, calc("20", a, b))b = 1 }運行結果如下:
10 1 2 3 20 0 2 2 2 0 2 2 1 1 3 4Process finished with exit code 0在解題前需要明確兩個概念:
- defer是在函數末尾的return前執行,先進后執行。
- 函數調用時 int 參數發生值拷貝。
不管代碼順序如何,defer calc func中參數b必須先計算,故會在運行到第三行時,執行calc("10",a,b)輸出:10 1 2 3得到值3,將cal("1",1,3)存放到延后執執行函數隊列中。
執行到第五行時,現行計算calc("20", a, b)即calc("20", 0, 2)輸出:20 0 2 2得到值2,將cal("2",0,2)存放到延后執行函數隊列中。
執行到末尾行,按隊列先進后出原則依次執行:cal("2",0,2)、cal("1",1,3),依次輸出:2 0 2 2、1 1 3 4 。
7. 請寫出以下輸入內容
package mainimport "fmt"func main() {s := make([]int, 5)fmt.Printf("%p\n", s)s = append(s, 1, 2, 3)fmt.Printf("%p\n", s) //new pointerfmt.Println(s) }運行結果:
0xc4200180c0 0xc42001c0a0 [0 0 0 0 0 1 2 3]Process finished with exit code 08. 下面的代碼有什么問題
package mainimport ("fmt""sync" )type UserAges struct {ages map[string]intsync.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 }func main() {count := 1000gw := sync.WaitGroup{}gw.Add(count * 3)u := UserAges{ages: map[string]int{}}add := func(i int) {u.Add(fmt.Sprintf("user_%d", i), i)gw.Done()}for i := 0; i < count; i++ {go add(i)go add(i)}for i := 0; i < count; i++ {go func(i int) {defer gw.Done()u.Get(fmt.Sprintf("user_%d", i))}(i)}gw.Wait()fmt.Println("Done") }輸出結果:
fatal error: concurrent map read and map writegoroutine 2022 [running]: runtime.throw(0x10c5472, 0x21)結論: 在執行 Get 方法時可能被panic。
雖然有使用sync.Mutex做寫鎖,但是map是并發讀寫不安全的。map屬于引用類型,并發讀寫時多個協程見是通過指針訪問同一個地址,即訪問共享變量,此時同時讀寫資源存在競爭關系。所以會報錯誤信息:fatal error: concurrent map read and map write。
如果第一次沒復現panic問題,可以再次運行,復現該問題。那么如何改善呢? 在Go1.9新版本中將提供并發安全的map。首先需要了解兩種鎖的不同:
- sync.Mutex互斥鎖
- sync.RWMutex讀寫鎖,基于互斥鎖的實現,可以加多個讀鎖或者一個寫鎖。
RWMutex相關方法:
type RWMutexfunc (rw *RWMutex) Lock() func (rw *RWMutex) RLock()func (rw *RWMutex) RLocker() Lockerfunc (rw *RWMutex) RUnlock()func (rw *RWMutex) Unlock()代碼改進如下:
package mainimport ("fmt""sync" )type UserAges struct {ages map[string]intsync.RWMutex }func (ua *UserAges) Add(name string, age int) {ua.Lock()defer ua.Unlock()ua.ages[name] = age }func (ua *UserAges) Get(name string) int {ua.RLock()defer ua.RUnlock()if age, ok := ua.ages[name]; ok {return age}return -1 }func main() {count := 10000gw := sync.WaitGroup{}gw.Add(count * 3)u := UserAges{ages: map[string]int{}}add := func(i int) {u.Add(fmt.Sprintf("user_%d", i), i)gw.Done()}for i := 0; i < count; i++ {go add(i)go add(i)}for i := 0; i < count; i++ {go func(i int) {defer gw.Done()u.Get(fmt.Sprintf("user_%d", i))fmt.Print(".")}(i)}gw.Wait()fmt.Println("Done") }運行結果如下:
. . . . DoneProcess finished with exit code 09. 下面的迭代會有什么問題?
package mainimport "fmt" import "sync" import "time"type ThreadSafeSet struct {sync.RWMutexs []int }func (set *ThreadSafeSet) Iter() <-chan interface{} {ch := make(chan interface{})go func() {set.RLock()for elem := range set.s {ch <- elemfmt.Print("get:", elem, ",")}close(ch)set.RUnlock()}()return ch }func main() {//read()unRead() } func read() {set := ThreadSafeSet{}set.s = make([]int, 100)ch := set.Iter()closed := falsefor {select {case v, ok := <-ch:if ok {fmt.Print("read:", v, ",")} else {closed = true}}if closed {fmt.Print("closed")break}}fmt.Print("Done") }func unRead() {set := ThreadSafeSet{}set.s = make([]int, 100)ch := set.Iter()_ = chtime.Sleep(5 * time.Second)fmt.Print("Done") }結論:內部迭代出現阻塞。默認初始化時無緩沖區,需要等待接收者讀取后才能繼續寫入。
chan在使用make初始化時可附帶一個可選參數來設置緩沖區。默認無緩沖,題目中便初始化的是無緩沖區的chan,這樣只有寫入的元素直到被讀取后才能繼續寫入,不然就一直阻塞。
設置緩沖區大小后,寫入數據時可連續寫入到緩沖區中,直到緩沖區被占滿。從chan中接收一次便可從緩沖區中釋放一次。可以理解為chan是可以設置吞吐量的處理池。
ch := make(chan interface{})和 ch := make(chan interface{},1)是不一樣的
無緩沖的 不僅僅是只能向 ch 通道放 一個值 而是一直要有人接收,那么ch <- elem才會繼續下去,要不然就一直阻塞著,也就是說有接收者才去放,沒有接收者就阻塞。
而緩沖為1則即使沒有接收者也不會阻塞,因為緩沖大小是1只有當 放第二個值的時候 第一個還沒被人拿走,這時候才會阻塞
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)) }結論:編譯失敗,值類型 Student{} 未實現接口People的方法,不能定義為 People 類型。
兩種正確修改方法:
- 方法一
- 方法二
總結:指針類型的結構體對象可以同時調用結構體值類型和指針類型對應的方法。而值類型的結構體對象只能調用值類型對應的接口方法。
總結
以上是生活随笔為你收集整理的Golang经典面试题上的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AttnGAN: Fine-Graine
- 下一篇: Golang经典面试题下