GO·NOTE

一份 Go 开发工程师的学习笔记

0%

Golang 面试题·卷一

线上测试地址:https://play.studygolang.com/

TODO 第1题,

1、写出下面代码输出结果(20分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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执行顺序,panic的执行方式

结果:

1
2
3
4
打印后
打印中
“触发异常”
打印前

解析:“触发异常”可能出现在任何位置。defer是先进后出,逆序执行。
笔者猜测:panic()函数相当与go出去的一个协程,相关资料:https://blog.golang.org/defer-panic-and-recover, 将马上更新

2、以下代码有什么问题,说明原因(20分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
}
}
参考答案

在range循环中,变量是不会随着遍历过程发生变化的。
因此在代码中stu是不会变化的,变化的是放在&stu地址上的数据,
因此最后m中value都将是最后一个放在&stu中的值

3、下面的代码会输出什么,并说明原因(20分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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()
}
参考答案

先说说WaitGroup的用途:它能够一直等到所有的goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成。
这里要注意一下,他们的执行结果是没有顺序的,调度器不能保证多个 goroutine 执行次序,且进程退出时不会等待它们结束。
WaitGroup总共有三个方法:Add(delta int),Done(),Wait()。简单的说一下这三个方法的作用。

  • Add:添加或者减少等待goroutine的数量
  • Done:相当于Add(-1)
  • Wait:执行阻塞,直到所有的WaitGroup数量变成0

4、下面代码会输出什么?(20分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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中没有继承关系。也不应该提及“继承”这个词,其中Trecher并没有继承Propler,而是嵌套People,
而t.ShowA()是一个语法糖,其实t.ShowA() = t.people.ShowA(),也就是说在嵌套结构中,go会优先调用本身方法,
如果本身没有此方法,就回去调用其所包含结构的方法。

本题中,showA()是Teacher不具有的,但是它所嵌套的People具有,因此回调用People.showA(),People.showA()
中调用了*People 的showB()当然会展示“shwoB”,而不是“teacher showB”

引申一点,
如果嵌套有两个结构,并且两个结构具有相同的方法,如何执行的?
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
type People struct{}

func (p *People) ShowA() {
fmt.Println("showA")
p.ShowB()
}
func (p *People) ShowB() {
fmt.Println("showB")
}

type Human struct{}

func (h *Human) ShowA() {
fmt.Println("Human showA")
}

type Teacher struct {
Human
People
}

func (t *Teacher) ShowB() {
fmt.Println("teacher showB")
}

func main() {
t := Teacher{}
t.ShowA()
}

答案是 编译报错,不支持这种情况的。

5、下面代码会触发异常吗?请详细说明(20分)

1
2
3
4
5
6
7
8
9
10
11
12
13
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)
}
}
参考答案

结果:

都可能发生

1
1
1
panic: hello

解析:

如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行

比较容易复现的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"runtime"
)

func main() {
runtime.GOMAXPROCS(1)
int_chan := make(chan int, 100)
string_chan := make(chan string, 100)

for i:=0;i<100;i++{
int_chan <- i
string_chan <- fmt.Sprintf("hello-%d",i)
}

for {
select {
case value := <-int_chan:
fmt.Println(value)
case value := <-string_chan:
panic(value)
}
}
}

参考文档

https://www.jianshu.com/p/2a1146dc42c3

6、下面代码会输出什么?(20分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
}
参考答案

结果:

1
2
3
4
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4

解析:

1、当程序运行到defer函数时,不会执行函数实现,但会将defer函数中的参数代码进行执行。
因此首先执行的是calc(“10”, a, b)),随后执行的是calc(“2”, a, calc(“20”, a, b))
得到第一行和第二行结果。

2、defer的执行结果是先进后出,从函数尾部向函数头部以此执行。因此会首先执行calc(“2”, a, calc(“20”, a, b)),
然后执行defer calc(“1”, a, calc(“10”, a, b)),相应打印第三行和第四行

7、请写出以下输出内容(20分)

1
2
3
4
5
func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
参考答案

结果:

1
0 0 0 0 0 1 2 3

解析:

make([]int,5)的含义是创建数组,并且数组初始化5个元素,5个元素的值为类型零值。

8、下面的代码有什么问题(20分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
}
参考答案

结果:

1
fatal error: concurrent map read and map write

解析:

map 是并发不安全的,不能同时读写,为了复现,将代码改为以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"fmt"
"sync"
"time"
)

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() {
ua := new(UserAges)
ua.ages = make(map[string]int)

for i:=0;i<1000;i++ {

go func(i int) {
ua.Add(fmt.Sprintf("student-%d",i),i)
}(i)
}

for i:=0;i<1000;i++{

go func(i int) {
fmt.Println(ua.Get(fmt.Sprintf("student-%d",i)))
}(i)
}

time.Sleep(10 * time.Second)
}

9、实现一个并发安全的set(20分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package main

import (
"fmt"
"sync"
"time"
)

type threadSafeSet struct {
sync.RWMutex
data map[interface{}]interface{}
}

func (set *threadSafeSet)Add(item interface{}) {
// map is not safe in concurrency
set.Lock()
set.data[item] = true
set.Unlock()
}

func (set *threadSafeSet)Remove(item interface{}) {
set.Lock()
delete(set.data,item)
set.Unlock()
}

func (set *threadSafeSet) Iter() <-chan interface{} {
ch := make(chan interface{})
go func() {
set.RLock()
for item,_ := range set.data {
ch <- item
}
close(ch)
set.RUnlock()

}()
return ch
}

func main() {
set := new(threadSafeSet)
set.data = make(map[interface{}]interface{})
for i:=0;i<10 ;i++ {
set.Add(i)
}

for item := range set.Iter(){
fmt.Printf("get value from set,value is %v\n",item)
}

time.Sleep(5 * time.Second)
}
参考答案

等待更新…

10、以下代码能编译过去吗?为什么?(20分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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))
}
参考答案

结果:

1
编译不过去

解析:

原因是Student并没有继承People,people中有Speak(string)string方法,而Student类型中没有Speak()方法,
代码中的Student方法是*Student类型的,所有 var peo People = Student{}是不符合规范的。