Golang 面試題解析 1
1.寫出以下代碼輸出內容
defer 先進後出 (stack) , 然後再執行 panic()
https://go.dev/play/p/6p9PVulq0El
package main
import (
"fmt"
)
func main() {
defer_call()
}
func defer_call() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印後") }()
panic("觸發異常")
}
/*
打印後
打印中
打印前
panic: 觸發異常
*/
延遲的英文後進先出。協程遇到死機時,遍歷本協程的延遲鍊錶,並執行延遲在執行延遲過程中,遇到恢復則停止恐慌,返回回收處繼續往如果沒有遇到恢復,遍歷完本協程的defer鍊錶後,向stderr引發panic信息。從執行順序上來看,實際上是按照先進後出的順序執行defer
2.以下代碼有什麼問題,說明原因
package main
import "fmt"
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
stu.Age = stu.Age + 10
}
for _, v := range m {
fmt.Printf("%+v", v)
}
}
func main() {
pase_student()
}
/*
遇到寫進m的資料都是stus的最後一筆
&{Name:wang Age:32}&{Name:wang Age:32}&{Name:wang Age:32}
*/
// https://play.golang.org/p/_wGc12urMV1
與Java的foreach一樣,都是使用副本的方式。所以m[stu.Name]=&stu實際上一致指向同一個指針, 最終該指針的值為遍歷的最後一個struct的值拷貝。就像想修改切片元素的屬性:
解答
for i:=0;i<len(stus);i++ {
m[stus[i].Name] = &stus[i]
}
for k,v:=range m{
println(k,"=>",v.Name)
}
3.下面的代碼會輸出什麼,並說明原因: go func中i是外部的一個變量,地址不變化,但是值都在改變
package main
import (
"fmt"
"runtime"
"sync"
)
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()
}
https://play.golang.org/p/uTn9OnK0B5E
A:輸出完全隨機,因此goroutine執行時i的值是多少;\ 而B:一定輸出為0〜9,但順序不定。第一個go func中i是外部的一個變量,地址不變化,但是值都在改變。
第二個go func中i是函數參數,與外部對於中的i完全是兩個變量。\ 尾部(i)將發生值拷貝,go func內部指向值拷貝地址。
所以在使用goroutine在處理閉包的時候,避免發生類似第一個go func中的問題。
4.下面代碼會輸出什麼? go的組合繼承
t.showA() -> showA showB
如果是 t.ShowB() -> teacher showB
package main
import (
"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
showB
*/
https://play.golang.org/p/eSGvASIBn5K >
go的組合繼承 解答: 這是Golang的組合模式,可以實現OOP的繼承。被組合的類型People所包含的方法雖然升級成了外部類型Teacher這個組合類型的方法(一定要是匿名字段),但它們的方法(ShowA())調用時接受者並沒有發生變化。此時People類型並不知道自己會被什麼類型組合,當然也就無法調用方法時去使用未知的組合者Teacher類型的功能
5.下面代碼會觸發異常嗎?請詳細說明 : select隨機性
package main
import (
"fmt"
"runtime"
)
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:
fmt.Println(value)
panic(value)
}
}
https://play.golang.org/p/MX7B99Rcsu5
解答
- select隨機性 解答: select會隨機選擇一個可用通用做收發操作。所以代碼是有可能觸發異常,也有可能不會。單個chan如果無緩衝時,將會阻塞。但結合 select可以在多個chan間等待執行。
- 有三點原則:
- select 中只要有一個case能return,則立刻執行
- 當如果同一時間有多個case均能return則偽隨機方式抽取任意一個執行
- 如果沒有一個case能return則可以執行
default塊
6.下面代碼輸出什麼?
package main
import (
"fmt"
)
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
*/
https://play.golang.org/p/HX-2hHy5kzB
calc("10",a=1,b=2) -> calc("20",a=0,b=2) -> calc("2",a=0,b=0+2) -> calc("1",a=1,b=1+2)
這道題類似第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
7.請寫出以下輸入內容: make初始化是由默認值的哦,此處默認值為0
package main
import (
"fmt"
)
func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
/*
[0 0 0 0 0 1 2 3]
*/
https://play.golang.org/p/3-sPBVD2L3u
make初始化是由默認值的哦,此處默認值為0
8.下面的代碼有什麼問題? : map線程安全
要用 sync.Map 或 sync.RWMutex
package main
import (
"fmt"
"sync"
)
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
}
func main() {
t := &UserAges{ages: map[string]int{"k": 1, "y": 2}}
t.Add("k10", 10)
fmt.Println(t.Get("k10"))
// fmt.Printf("%+v \n",t)
}
https://play.golang.org/p/Yfg68Q9FHO7
map線程安全 解答: 可能會出現fatal error: concurrent map read and map write. 修改一下看看效果
解答1 : 用sync.Map
package main
import (
"fmt"
"sync"
)
type UserAges struct {
ages map[string]int
sync.Map
}
func (ua *UserAges) Add(name string, age int) {
ua.Store(name, age)
}
func (ua *UserAges) Get(name string) int {
if age, ok := ua.Load(name); ok {
return age.(int)
}
return -1
}
func main() {
t := &UserAges{ages: map[string]int{"k": 1, "y": 2}}
go func() {
for {
t.Add("k10", 10)
}
}()
go func() {
for {
fmt.Println(t.Get("k10"))
}
}()
for {
}
}
解答2 : 用sync.RWMutex
package main
import (
"fmt"
"sync"
)
type UserAges struct {
ages map[string]int
sync.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() {
t := &UserAges{ages: map[string]int{"k": 1, "y": 2}}
go func() {
for {
t.Add("k10", 10)
}
}()
go func() {
for {
fmt.Println(t.Get("k10"))
}
}()
for {
}
}
9.下面的迭代會有什麼問題?
https://go.dev/play/p/LiCp_gt7dVZ
package main
import (
"fmt"
"sync"
)
type threadSafeSet struct {
sync.RWMutex
s []interface{}
}
func (set *threadSafeSet) Iter() <-chan interface{} {
ch := make(chan interface{})
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.Printf("---%s%v", "ch", v)
}
// Iter: 0---ch0
chan緩存池 https://studygolang.com/articles/12512 解答: 看到這道題,我也在猜想出題者的意圖在哪裡。 chan?sync.RWMutex?go?chan緩存池?迭代? 所以只能再讀一次題目,就從迭代入手看看。既然是迭代就會要求set.s全部可以遍歷一次。但是chan是為緩存的,那就代表這寫入一次就會阻塞。我們把代碼恢復為可以運行的方式,看看效果
package main
import (
"fmt"
"sync"
)
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.Printf("---%s%v", "ch", v)
}
// Iter: 0 (0x486fe0,0x4b2998)
// Iter: 1 (0x486fe0,0x4b29a8)
// ---ch0
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))
}
go:23:6: cannot use Stduent literal (type Stduent) as type People in assignment: Stduent does not implement People (Speak method has pointer receiver)
golang的方法集 解答: 編譯不通過!做錯了! ?說明你對golang的方法集還有一些疑問。一句話:golang的方法集僅僅影響接口實現和方法表達式轉化,與通過實例或者指針調用方法無關。
解法
- (t T) -> T
func (stu *Student)Speak(think string)(talk string){} // 這邊是Methods Receivers, (t *T)的話, Values 只能是 *T func main() { var p People = &Student{} // 這邊是Values }
(t T) -> *T and T
func (stu Student)Speak(think string)(talk string) // 這邊是Methods Receivers, (t T)的話, Values 可以是 T跟 *T func main() { var p People = Student{} // 這邊是Values // or var p2 People = &Student{} // 這邊是Values }new http://www.jeepxie.net/article/227769.html
var p People = new(Stduent)

| Methods Receivers | Value |
|---|---|
| (t T) | T and *T |
| (t *T) | *T |
解法一: Methods Receivers: t T, Value: T
https://play.golang.org/p/8c\_4T6vElcB
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 p People = &Stduent{}
think := "bitch"
fmt.Println(p.Speak(think))
}
解法二 : Methods Receivers: t T, Value: *T or T
https://go.dev/play/p/xRjg8K5Ph4E
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 p People = Stduent{}
think := "bitch"
fmt.Println(p.Speak(think))
}
解法三 : new(Student)
https://go.dev/play/p/dShDAGXkvEf
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 p People = new(Stduent)
think := "bitch"
fmt.Println(p.Speak(think))
}
用法
package main
import (
"fmt"
)
type Member interface {
GetName() string
GetAge() int
}
type Robot struct {
name string
age int
power int
}
func (r *Robot) Work() {}
func (r *Robot) GetName() string {
return r.name
}
func (r *Robot) GetAge() int {
return r.age
}
type Car struct {
name string
age int
odometer int
}
func (r *Car) Run() {
}
func (r *Car) GetName() string {
return r.name
}
func (r *Car) GetAge() int {
return r.age
}
func Cleaning(m Member) {
fmt.Printf("Consumer Name:%s, Age:%d\n", m.GetName(), m.GetAge())
}
func main() {
r := &Robot{
name: "GUMDAM",
age: 1,
power: 100,
}
c := &Car{
name: "BENZ",
age: 2,
odometer: 100,
}
Cleaning(c)
Cleaning(r)
}
/*
Consumer Name:BENZ, Age:2
Consumer Name:GUMDAM, Age:1
*/
11.以下代碼打印出來什麼內容,說出為什麼
https://go.dev/play/p/2nBU-dMwW6b
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")
}
}
// BBBBBBB
interface內部結構 解答: 很經典的題!這個考點是很多人忽略的interface內部結構。 go中的接口分為兩種一種是空的接口類似這樣: