Golang面试专题 - 笔试题
收藏

最近在很多地方看到了,看到了很多人对 Golang 的面试题心存恐惧,也是为了复习基础,我把解题的过程总结下来。

面试题

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


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 执行顺序

解答:

defer 是后进先出。

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

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

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

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
    }

}

考点:foreach

解答:

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

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

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

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
    }

    for k,v:=range m{
        println(k,"=>",v.Name)
    }

    // 正确
    for i:=0;i<len(stus);i++  {
        m[stus[i].Name] = &stus[i]
    }
    for k,v:=range m{
        println(k,"=>",v.Name)
    }
}

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

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()
}

考点: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. 下面代码会输出什么?

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 的组合继承

解答:

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

showA
showB

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

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)
    }
}

考点:select 随机性

解答:

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

  • select 中只要有一个 case 能 return,则立刻执行。
  • 当如果同一时间有多个 case 均能 return 则伪随机方式抽取任意一个执行。
  • 如果没有一个 case 能 return 则可以执行”default” 块。

6. 下面代码输出什么?

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
}

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

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

7. 请写出以下输入内容

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

考点:make 默认值和 append

解答:

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

[0 0 0 0 0 1 2 3]

大家试试改为:

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

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

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
}

考点:map 线程安全

解答:

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

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

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

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
}

考点:chan 缓存池

解答:

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

package main

import (
    "sync"
    "fmt"
)

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

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.Sprintf("%s%v","ch",v)
}

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))
}

考点:golang 的方法集

解答:

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

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

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")
	}
}

考点:interface 内部结构

解答:

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

var in interface{}

另一种如题目:

type People interface {
    Show()
}

他们的底层结构如下:

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

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

BBBBBBB

12. 是否可以编译通过?如果通过,输出什么?

func main() {
	i := GetValue()

	switch i.(type) {
	case int:
		println("int")
	case string:
		println("string")
	case interface{}:
		println("interface")
	default:
		println("unknown")
	}

}

func GetValue() int {
	return 1
}

解析

考点:type

编译失败,因为 type 只能使用在 interface

13. 下面函数有什么问题?

func funcMui(x,y int)(sum int,error){
    return x+y,nil
}

解析

考点:函数返回值命名

在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名。 如果返回值有有多个返回值必须加上括号; 如果只有一个返回值并且有命名也需要加上括号; 此处函数第一个返回值有 sum 名称,第二个未命名,所以错误。

14. 是否可以编译通过?如果通过,输出什么?

package main

func main() {

	println(DeferFunc1(1))
	println(DeferFunc2(1))
	println(DeferFunc3(1))
}

func DeferFunc1(i int) (t int) {
	t = i
	defer func() {
		t += 3
	}()
	return t
}

func DeferFunc2(i int) int {
	t := i
	defer func() {
		t += 3
	}()
	return t
}

func DeferFunc3(i int) (t int) {
	defer func() {
		t += i
	}()
	return 2
}

解析

考点: defer 和函数返回值

需要明确一点是 defer 需要在函数结束前执行。 函数返回值名字会在函数起始处被初始化为对应类型的零值并且作用域为整个函数 DeferFunc1 有函数返回值 t 作用域为整个函数,在 return 之前 defer 会被执行,所以 t 会被修改,返回 4; DeferFunc2 函数中 t 的作用域为函数,返回 1; DeferFunc3 返回 3

15. 是否可以编译通过?如果通过,输出什么?

func main() {
	list := new([]int)
	list = append(list, 1)
	fmt.Println(list)
}

解析

考点:new

list:=make([]int,0)

16. 是否可以编译通过?如果通过,输出什么?

package main

import "fmt"

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

解析

考点:append

append 切片时候别漏了’…’

17. 是否可以编译通过?如果通过,输出什么?

func main() {

	sn1 := struct {
		age  int
		name string
	}{age: 11, name: "qq"}
	sn2 := struct {
		age  int
		name string
	}{age: 11, name: "qq"}

	if sn1 == sn2 {
		fmt.Println("sn1 == sn2")
	}

	sm1 := struct {
		age int
		m   map[string]string
	}{age: 11, m: map[string]string{"a": "1"}}
	sm2 := struct {
		age int
		m   map[string]string
	}{age: 11, m: map[string]string{"a": "1"}}

	if sm1 == sm2 {
		fmt.Println("sm1 == sm2")
	}
}

解析

考点: 结构体比较

进行结构体比较时候,只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。

sn3:= struct {
    name string
    age  int
}{age:11,name:"qq"}

sn3 与 sn1 就不是相同的结构体了,不能比较。 还有一点需要注意的是结构体是相同的,但是结构体属性中有不可以比较的类型,如 map,slice。 如果该结构属性都是可以比较的,那么就可以使用 “==” 进行比较操作。

可以使用 reflect.DeepEqual 进行比较

if reflect.DeepEqual(sn1, sm) {
    fmt.Println("sn1 ==sm")
}else {
    fmt.Println("sn1 !=sm")
}

所以编译不通过: invalid operation: sm1 == sm2

18. 是否可以编译通过?如果通过,输出什么?

func Foo(x interface{}) {
	if x == nil {
		fmt.Println("empty interface")
		return
	}
	fmt.Println("non-empty interface")
}
func main() {
	var x *int = nil
	Foo(x)
}

解析

考点:interface 内部结构

non-empty interface

19. 是否可以编译通过?如果通过,输出什么?

func GetValue(m map[int]string, id int) (string, bool) {
	if _, exist := m[id]; exist {
		return "存在数据", true
	}
	return nil, false
}
func main()  {
	intmap:=map[int]string{
		1:"a",
		2:"bb",
		3:"ccc",
	}

	v,err:=GetValue(intmap,3)
	fmt.Println(v,err)
}

解析

考点:函数返回值类型

nil 可以用作 interface、function、pointer、map、slice 和 channel 的 “空值”。但是如果不特别指定的话,Go 语言不能识别类型,所以会报错。报:cannot use nil as type string in return argument.

20. 是否可以编译通过?如果通过,输出什么?

const (
	x = iota
	y
	z = "zz"
	k
	p = iota
)

func main()  {
	fmt.Println(x,y,z,k,p)
}

解析

考点:iota

结果:

0 1 zz zz 4

21. 编译执行下面代码会出现什么?

package main
var(
    size :=1024
    max_size = size*2
)
func main()  {
    println(size,max_size)
}

解析

考点: 变量简短模式

变量简短模式限制:

  • 定义变量同时显式初始化
  • 不能提供数据类型
  • 只能在函数内部使用

结果:

syntax error: unexpected :=

22. 下面函数有什么问题?

package main
const cl  = 100

var bl    = 123

func main()  {
    println(&bl,bl)
    println(&cl,cl)
}

解析

考点: 常量

常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,

cannot take the address of cl

23. 编译执行下面代码会出现什么?

package main

func main()  {

    for i:=0;i<10 ;i++  {
    loop:
        println(i)
    }
    goto loop
}

解析

考点:goto

goto 不能跳转到其他函数或者内层代码

goto loop jumps into block starting at

24. 编译执行下面代码会出现什么?

package main
import "fmt"

func main()  {
    type MyInt1 int
    type MyInt2 = int
    var i int =9
    var i1 MyInt1 = i
    var i2 MyInt2 = i
    fmt.Println(i1,i2)
}

解析

考点:**Go 1.9 新特性 Type Alias **

基于一个类型创建一个新类型,称之为 defintion;基于一个类型创建一个别名,称之为 alias。 MyInt1 为称之为 defintion,虽然底层类型为 int 类型,但是不能直接赋值,需要强转; MyInt2 称之为 alias,可以直接赋值。

结果:

cannot use i (type int) as type MyInt1 in assignment

25. 编译执行下面代码会出现什么?

package main
import "fmt"

type User struct {
}
type MyUser1 User
type MyUser2 = User
func (i MyUser1) m1(){
    fmt.Println("MyUser1.m1")
}
func (i User) m2(){
    fmt.Println("User.m2")
}

func main() {
    var i1 MyUser1
    var i2 MyUser2
    i1.m1()
    i2.m2()
}

解析

考点:**Go 1.9 新特性 Type Alias **

因为 MyUser2 完全等价于 User,所以具有其所有的方法,并且其中一个新增了方法,另外一个也会有。 但是

i1.m2()

是不能执行的,因为 MyUser1 没有定义该方法。 结果:

MyUser1.m1
User.m2

26. 编译执行下面代码会出现什么?

package main

import "fmt"

type T1 struct {
}
func (t T1) m1(){
    fmt.Println("T1.m1")
}
type T2 = T1
type MyStruct struct {
    T1
    T2
}
func main() {
    my:=MyStruct{}
    my.m1()
}

解析

考点:**Go 1.9 新特性 Type Alias **

是不能正常编译的, 异常:

ambiguous selector my.m1

结果不限于方法,字段也也一样;也不限于 type alias,type defintion 也是一样的,只要有重复的方法、字段,就会有这种提示,因为不知道该选择哪个。 改为:

my.T1.m1()
my.T2.m1()

type alias 的定义,本质上是一样的类型,只是起了一个别名,源类型怎么用,别名类型也怎么用,保留源类型的所有方法、字段等。

27. 编译执行下面代码会出现什么?

package main

import (
    "errors"
    "fmt"
)

var ErrDidNotWork = errors.New("did not work")

func DoTheThing(reallyDoIt bool) (err error) {
    if reallyDoIt {
        result, err := tryTheThing()
        if err != nil || result != "it worked" {
            err = ErrDidNotWork
        }
    }
    return err
}

func tryTheThing() (string,error)  {
    return "",ErrDidNotWork
}

func main() {
    fmt.Println(DoTheThing(true))
    fmt.Println(DoTheThing(false))
}

解析

考点:变量作用域

因为 if 语句块内的 err 变量会遮罩函数作用域内的 err 变量,结果:

<nil>
<nil>

改为:

func DoTheThing(reallyDoIt bool) (err error) {
    var result string
    if reallyDoIt {
        result, err = tryTheThing()
        if err != nil || result != "it worked" {
            err = ErrDidNotWork
        }
    }
    return err
}

28. 编译执行下面代码会出现什么?

package main

func test() []func()  {
    var funs []func()
    for i:=0;i<2 ;i++  {
        funs = append(funs, func() {
            println(&i,i)
        })
    }
    return funs
}

func main(){
    funs:=test()
    for _,f:=range funs{
        f()
    }
}

解析

考点:闭包延迟求值

for 循环复用局部变量 i,每一次放入匿名函数的应用都是想一个变量。 结果:

0xc042046000 2
0xc042046000 2

如果想不一样可以改为:

func test() []func()  {
    var funs []func()
    for i:=0;i<2 ;i++  {
        x:=i
        funs = append(funs, func() {
            println(&x,x)
        })
    }
    return funs
}

29. 编译执行下面代码会出现什么?

package main

func test(x int) (func(),func())  {
    return func() {
        println(x)
        x+=10
    }, func() {
        println(x)
    }
}

func main()  {
    a,b:=test(100)
    a()
    b()
}

解析

考点:闭包引用相同变量 *

结果:

100
110

30. 编译执行下面代码会出现什么?

package main

import (
    "fmt"
    "reflect"
)

func main1()  {
    defer func() {
       if err:=recover();err!=nil{
           fmt.Println(err)
       }else {
           fmt.Println("fatal")
       }
    }()

    defer func() {
        panic("defer panic")
    }()
    panic("panic")
}

func main()  {
    defer func() {
        if err:=recover();err!=nil{
            fmt.Println("++++")
            f:=err.(func()string)
            fmt.Println(err,f(),reflect.TypeOf(err).Kind().String())
        }else {
            fmt.Println("fatal")
        }
    }()

    defer func() {
        panic(func() string {
            return  "defer panic"
        })
    }()
    panic("panic")
}

解析

考点:panic 仅有最后一个可以被 revover 捕获

触发panic("panic")后顺序执行 defer,但是 defer 中还有一个 panic,所以覆盖了之前的panic("panic")

defer panic
回复: