### 1、寫出下面代碼輸出內容。
~~~go
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`關鍵字的實現跟 go 關鍵字很類似,不同的是它調用的是`runtime.deferproc`而不是`runtime.newproc`。
在`defer`出現的地方,插入了指令`call runtime.deferproc`,然后在函數返回之前的地方,插入指令`call runtime.deferreturn`。
goroutine 的控制結構中,有一張表記錄`defer`,調用`runtime.deferproc`時會將需要 defer 的表達式記錄在表中,而在調用`runtime.deferreturn`的時候,則會依次從 defer 表中出棧并執行。
因此,題目最后輸出順序應該是`defer`定義順序的倒序。`panic`錯誤并不能終止`defer`的執行。
### 2、 以下代碼有什么問題,說明原因
~~~go
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
}
}
~~~
**解析:**
golang 的`for ... range`語法中,`stu`變量會被復用,每次循環會將集合中的值復制給這個變量,因此,會導致最后`m`中的`map`中儲存的都是`stus`最后一個`student`的值。
### 3、下面的代碼會輸出什么,并說明原因
~~~go
func main() {
runtime.GOMAXPROCS(1)
wg := sync.WaitGroup{}
wg.Add(20)
for i := 0; i < 10; i++ {
go func() {
fmt.Println("i: ", i)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println("i: ", i)
wg.Done()
}(i)
}
wg.Wait()
}
~~~
**解析:**
這個輸出結果決定來自于調度器優先調度哪個 G。從 runtime 的源碼可以看到,當創建一個 G 時,會優先放入到下一個調度的`runnext`字段上作為下一次優先調度的 G。因此,最先輸出的是最后創建的 G,也就是 9.
~~~go
func newproc(siz int32, fn *funcval) {
argp := add(unsafe.Pointer(&fn), sys.PtrSize)
gp := getg()
pc := getcallerpc()
systemstack(func() {
newg := newproc1(fn, argp, siz, gp, pc)
_p_ := getg().m.p.ptr()
//新創建的G會調用這個方法來決定如何調度
runqput(_p_, newg, true)
if mainStarted {
wakep()
}
})
}
...
if next {
retryNext:
oldnext := _p_.runnext
//當next是true時總會將新進來的G放入下一次調度字段中
if !_p_.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {
goto retryNext
}
if oldnext == 0 {
return
}
// Kick the old runnext out to the regular run queue.
gp = oldnext.ptr()
}
~~~
### 4、下面代碼會輸出什么?
~~~go
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`、`showB`。golang 語言中沒有繼承概念,只有組合,也沒有虛方法,更沒有重載。因此,`*Teacher`的`ShowB`不會覆寫被組合的`People`的方法。
### 5、下面代碼會觸發異常嗎?請詳細說明
~~~go
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)
}
}
~~~
**解析:**
結果是隨機執行。golang 在多個`case`可讀的時候會公平的選中一個執行。
### 6、下面代碼輸出什么?
~~~go
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
}
~~~
**解析:**
輸出結果為:
~~~
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4
~~~
`defer`在定義的時候會計算好調用函數的參數,所以會優先輸出`10`、`20`兩個參數。然后根據定義的順序倒序執行。
### 7、請寫出以下輸入內容
~~~go
func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
~~~
**解析:**
輸出為`0 0 0 0 0 1 2 3`。
`make`在初始化切片時指定了長度,所以追加數據時會從`len(s)`位置開始填充數據。
### 8、下面的代碼有什么問題?
~~~go
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
}
~~~
**解析:**
在執行 Get 方法時可能被 panic。
雖然有使用 sync.Mutex 做寫鎖,但是 map 是并發讀寫不安全的。map 屬于引用類型,并發讀寫時多個協程見是通過指針訪問同一個地址,即訪問共享變量,此時同時讀寫資源存在競爭關系。會報錯誤信息:“fatal error: concurrent map read and map write”。
因此,在`Get`中也需要加鎖,因為這里只是讀,建議使用讀寫鎖`sync.RWMutex`。
### 9、下面的迭代會有什么問題?
~~~go
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
}
~~~
**解析:**
默認情況下`make`初始化的`channel`是無緩沖的,也就是在迭代寫時會阻塞。
### 10、以下代碼能編譯過去嗎?為什么?
~~~go
package main
import (
"fmt"
)
type People interface {
Speak(string) string
}
type Student struct{}
func (stu *Student) Speak(think string) (talk string) {
if think == "bitch" {
talk = "You are a good boy"
} else {
talk = "hi"
}
return
}
func main() {
var peo People = Student{}
think := "bitch"
fmt.Println(peo.Speak(think))
}
~~~
**解析:**
編譯失敗,值類型`Student{}`未實現接口`People`的方法,不能定義為`People`類型。
在 golang 語言中,`Student`和`*Student`是兩種類型,第一個是表示`Student`本身,第二個是指向`Student`的指針。
### 11、以下代碼打印出來什么內容,說出為什么。。。
~~~go
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")
}
}
~~~
**解析:**
跟上一題一樣,不同的是`*Student`的定義后本身沒有初始化值,所以`*Student`是`nil`的,但是`*Student`實現了`People`接口,接口不為`nil`。
- Golnag常見面試題目解析
- 交替打印數組和字母
- 判斷字符串中字符是否全都不同
- 翻轉字符串
- 判斷兩個給定的字符串排序后是否一致
- 字符串替換問題
- 機器人坐標計算
- 語法題目一
- 語法題目二
- goroutine和channel使用一
- 實現阻塞讀的并發安全Map
- 定時與 panic 恢復
- 高并發下的鎖與map讀寫問題
- 為 sync.WaitGroup 中Wait函數支持 WaitTimeout 功能.
- 七道語法找錯題目
- golang 并發題目測試
- 記一道字節跳動的算法面試題
- 多協程查詢切片問題
- 對已經關閉的的chan進行讀寫,會怎么樣?為什么?
- 簡單聊聊內存逃逸?
- 字符串轉成byte數組,會發生內存拷貝嗎?
- http包的內存泄漏