golang 语言面试题解析

1. 写出下面代码输出内容。

  1. package main
  2. import("fmt")
  3. func main() {
  4. defer_call()
  5. }
  6. func defer_call() {
  7. defer func() {
  8. fmt.Println("打印前")
  9. }()
  10. defer func() {
  11. fmt.Println("打印中")
  12. }()
  13. defer func() {
  14. fmt.Println("打印后")
  15. }()
  16. panic("触发异常")
  17. }

考点:defer执行顺序

解答: defer 是后进先出。 panic 需要等defer 结束后才会向上传递。出现panic恐慌时候,会先按照defer的后入先出的顺序执行,最后才会执行panic。

  1. 打印后打印中打印前panic: 触发异常

2. 以下代码有什么问题,说明原因。

  1. type student struct {
  2. Name string Age int
  3. }
  4. func pase_student() {
  5. m := make(map[string] * student)
  6. stus := [] student {
  7. {
  8. Name: "zhou",
  9. Age: 24
  10. }, {
  11. Name: "li",
  12. Age: 23
  13. }, {
  14. Name: "wang",
  15. Age: 22
  16. },
  17. }
  18. for _, stu := range stus {
  19. m[stu.Name] = &stu
  20. }
  21. }

考点:foreach

解答:这样的写法初学者经常会遇到的,很危险!与Java的foreach一样,都是使用副本的方式。所以m[stu.Name]=&stu实际上一致指向同一个指针,最终该指针的值为遍历的最后一个struct的值拷贝。就像想修改切片元素的属性:

  1. for _, stu := range stus {
  2. stu.Age = stu.Age + 10
  3. }

也是不可行的。大家可以试试打印出来:

  1. func pase_student() {
  2. m := make(map[string] * student)
  3. stus := [] student {
  4. {
  5. Name: "zhou",
  6. Age: 24
  7. }, {
  8. Name: "li",
  9. Age: 23
  10. }, {
  11. Name: "wang",
  12. Age: 22
  13. },
  14. }
  15. // 错误写法
  16. for _, stu := range stus {
  17. m[stu.Name] = & stu
  18. }
  19. for k, v := range m {
  20. println(k, "=>", v.Name)
  21. }
  22. // 正确
  23. for i := 0; i < len(stus); i++ {
  24. m[stus[i].Name] = & stus[i]
  25. }
  26. for k, v := range m {
  27. println(k, "=>", v.Name)
  28. }
  29. }

3. 下面的代码会输出什么,并说明原因

  1. func main() {
  2. runtime.GOMAXPROCS(1)
  3. wg := sync.WaitGroup {}
  4. wg.Add(20)
  5. for i := 0; i < 10; i++ {
  6. go func() {
  7. fmt.Println("A: ", i)
  8. wg.Done()
  9. }()
  10. }
  11. for i := 0; i < 10; i++ {
  12. go func(i int) {
  13. fmt.Println("B: ", i)
  14. wg.Done()
  15. }(i)
  16. }
  17. 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内部指向值拷贝地址。

4. 下面代码会输出什么?

  1. type People struct {}
  2. func(p * People) ShowA() {
  3. fmt.Println("showA")
  4. p.ShowB()
  5. }
  6. func(p * People) ShowB() {
  7. fmt.Println("showB")
  8. }
  9. type Teacher struct {
  10. People
  11. }
  12. func(t * Teacher) ShowB() {
  13. fmt.Println("teacher showB")
  14. }
  15. func main() {
  16. t := Teacher {}
  17. t.ShowA()
  18. }

考点:go的组合继承

解答:这是Golang的组合模式,可以实现OOP的继承。被组合的类型People所包含的方法虽然升级成了外部类型Teacher这个组合类型的方法(一定要是匿名字段),但它们的方法(ShowA())调用时接受者并没有发生变化。此时People类型并不知道自己会被什么类型组合,当然也就无法调用方法时去使用未知的组合者Teacher类型的功能。

  1. showAshowB

5. 下面代码会触发异常吗?请详细说明

  1. func main() {
  2. runtime.GOMAXPROCS(1)
  3. int_chan := make(chan int, 1)
  4. string_chan := make(chan string, 1)
  5. int_chan <- 1
  6. string_chan <- "hello"
  7. select {
  8. case value := <-int_chan:
  9. fmt.Println(value)
  10. case value := <-string_chan:
  11. panic(value)
  12. }
  13. }

考点:select随机性

解答: select会随机选择一个可用通用做收发操作。所以代码是有肯触发异常,也有可能不会。单个chan如果无缓冲时,将会阻塞。但结合 select可以在多个chan间等待执行。有三点原则:

  • select 中只要有一个case能return,则立刻执行。 *

  • 当如果同一时间有多个case均能return则伪随机方式抽取任意一个执行。

  • 如果没有一个case能return则可以执行”default”块。

6. 下面代码输出什么?

  1. func calc(index string, a, b int) int {
  2. ret := a + b
  3. fmt.Println(index, a, b, ret)
  4. return ret
  5. }
  6. func main() {
  7. a := 1
  8. b := 2
  9. defer calc("1", a, calc("10", a, b))
  10. a = 0
  11. defer calc("2", a, calc("20", a, b))
  12. b = 1
  13. }

考点: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

  1. 10 1 2 320 0 2 22 0 2 21 1 3 4

7. 请写出以下输入内容

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

考点:make默认值和append

解答: make初始化是由默认值的哦,此处默认值为0

  1. [0 0 0 0 0 1 2 3]

大家试试改为:

  1. s := make([] int, 0)
  2. s = append(s, 1, 2, 3)
  3. fmt.Println(s) //[1 2 3]

8. 下面的代码有什么问题?

  1. type UserAges struct {
  2. ages map[string]
  3. int sync.Mutex
  4. }
  5. func(ua * UserAges) Add(name string, age int) {
  6. ua.Lock()
  7. defer ua.Unlock()
  8. ua.ages[name] = age
  9. }
  10. func(ua * UserAges) Get(name string) int {
  11. if age, ok := ua.ages[name]; ok {
  12. return age
  13. }
  14. return -1
  15. }

考点:map线程安全

解答:可能会出现fatal error: concurrent map read and map write. 修改一下看看效果

  1. func(ua * UserAges) Get(name string) int {
  2. ua.Lock()
  3. defer ua.Unlock()
  4. if age, ok := ua.ages[name]; ok {
  5. return age
  6. }
  7. return -1
  8. }

9. 下面的迭代会有什么问题?

  1. func(set * threadSafeSet) Iter() <-chan interface {} {
  2. ch := make(chan interface {})
  3. go func() {
  4. set.RLock()
  5. for elem := range set.s {
  6. ch <-elem
  7. }
  8. close(ch)
  9. set.RUnlock()
  10. }()
  11. return ch
  12. }

考点:chan缓存池

解答:看到这道题,我也在猜想出题者的意图在哪里。 chan?sync.RWMutex?go?chan缓存池?迭代? 所以只能再读一次题目,就从迭代入手看看。既然是迭代就会要求set.s全部可以遍历一次。但是chan是为缓存的,那就代表这写入一次就会阻塞。我们把代码恢复为可以运行的方式,看看效果

  1. package main
  2. import(
  3. "sync"
  4. "fmt"
  5. )
  6. //下面的迭代会有什么问题?
  7. type threadSafeSet struct {
  8. sync.RWMutex
  9. s []interface {}
  10. }
  11. func(set * threadSafeSet) Iter() <-chan interface {} {
  12. // ch := make(chan interface{})
  13. // 解除注释看看!
  14. ch := make(chan interface {}, len(set.s))
  15. go func() {
  16. set.RLock()
  17. for elem, value := range set.s {
  18. ch <-elem
  19. println("Iter:", elem, value)
  20. }
  21. close(ch)
  22. set.RUnlock()
  23. }()
  24. return ch
  25. }
  26. func main() {
  27. th := threadSafeSet {
  28. s :[]interface {} {
  29. "1", "2"
  30. },
  31. }
  32. v := <-th.Iter()
  33. fmt.Sprintf("%s%v", "ch", v)
  34. }

10. 以下代码能编译过去吗?为什么?

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type People interface {
  6. Speak(string) string
  7. }
  8. type Stduent struct {}
  9. func(stu * Stduent) Speak(think string)(talk string) {
  10. if think == "bitch" {
  11. talk = "You are a good boy"
  12. } else {
  13. talk = "hi"
  14. }
  15. return
  16. }
  17. func main() {
  18. var peo People = Stduent {}
  19. think := "bitch"
  20. fmt.Println(peo.Speak(think))
  21. }

考点:golang的方法集

解答:编译不通过!做错了!?说明你对golang的方法集还有一些疑问。一句话:golang的方法集仅仅影响接口实现和方法表达式转化,与通过实例或者指针调用方法无关。

11. 以下代码打印出来什么内容,说出为什么。

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. type People interface {
  6. Show()
  7. }
  8. type Student struct {}
  9. func(stu * Student) Show() {}
  10. func live() People {
  11. var stu * Student
  12. return stu
  13. }
  14. func main() {
  15. if live() == nil {
  16. fmt.Println("AAAAAAA")
  17. } else {
  18. fmt.Println("BBBBBBB")
  19. }
  20. }

考点:interface内部结构

解答:很经典的题!这个考点是很多人忽略的interface内部结构。 go中的接口分为两种一种是空的接口类似这样:

  1. var in interface{}

另一种如题目:

  1. type People interface {
  2. Show()
  3. }

他们的底层结构如下:

  1. type eface struct {
  2. //空接口
  3. _type *_type //类型信息
  4. data unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
  5. }
  6. type iface struct {
  7. //带有方法的接口
  8. tab *itab //存储type信息还有结构实现方法的集合
  9. data unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)}
  10. type _type struct {
  11. size uintptr //类型大小
  12. ptrdata uintptr //前缀持有所有指针的内存大小
  13. hash uint32 //数据hash值
  14. tflag tflag align uint8 //对齐
  15. fieldalign uint8 //嵌入结构体时的对齐
  16. kind uint8 //kind 有些枚举值kind等于0是无效的
  17. alg *typeAlg //函数指针数组,类型实现的所有方法
  18. gcdata *byte str nameOff ptrToThis typeOff
  19. }
  20. type itab struct {
  21. inter *interfacetype //接口类型
  22. _type *_type //结构类型
  23. link *itab bad int32 inhash int32 fun [1]uintptr //可变大小 方法集合
  24. }

可以看出iface比eface 中间多了一层itab结构。 itab 存储_type信息和[]fun方法集,从上面的结构我们就可得出,因为data指向了nil 并不代表interface 是nil,所以返回值并不为空,这里的fun(方法集)定义了接口的接收规则,在编译的过程中需要验证是否实现接口结果:

  1. BBBBBBB