关于后端:刷起来Go必看的进阶面试题详解

4次阅读

共计 3406 个字符,预计需要花费 9 分钟才能阅读完成。

勤学如春起之苗,不见其增日有所长;辍学如磨刀之石,不见其损日有所亏。

本文的重点:逃逸剖析、提早语句、散列表、通道、接口。

1. 逃逸剖析

逃逸剖析是 Go 语言中的一项重要优化技术,能够帮忙程序缩小内存调配和垃圾回收的开销,从而进步程序的性能。上面是一道波及逃逸剖析的面试题及其详解。

问题形容:

有如下 Go 代码:

func foo() *int {
    x := 1
    return &x
}

func main() {p := foo()
    fmt.Println(*p)
}

请问下面的代码中,变量 x 是否会产生逃逸?

答案解析:

在下面的代码中,变量 x 只在函数 foo() 中被定义和初始化,而后其地址被返回给了主函数 main()。因为返回值是指针类型,须要在堆上分配内存,所以变量 x 会产生逃逸。所谓逃逸,就是指变量的生命周期不仅限于函数栈帧,而是超出了函数的范畴,须要在堆上分配内存。

如果变量 x 没有产生逃逸,那么它会被调配在函数栈帧中,随着函数的返回而被主动销毁。而如果产生了逃逸,变量 x 就须要在堆上分配内存,并由垃圾回收器负责回收。在理论的程序中,大量的逃逸会导致内存调配和垃圾回收的开销减少,从而影响程序的性能。

逃逸剖析是 Go 语言的一项优化技术,能够在编译期间剖析代码,确定变量的生命周期和调配地位,从而防止不必要的内存调配和垃圾回收。通过逃逸剖析的优化,能够无效地进步程序的性能和可靠性。

更多逃逸剖析的内容,能够浏览我之前分享的文章:内存调配和逃逸剖析详解

2. 提早语句

defer 语句是 Go 语言中的一项重要个性,能够用于在函数返回前执行一些清理或收尾工作,例如开释资源、敞开连贯等。上面是一道波及 defer 语句的面试题及其详解。

问题形容:

有如下 Go 代码:

func main() {defer func() {fmt.Println("defer 1")
    }()
    defer func() {fmt.Println("defer 2")
    }()
    fmt.Println("main")
}

请问下面的代码中,输入的程序是什么?

答案解析:

在下面的代码中,咱们定义了两个 defer 语句,它们别离输入 ”defer 1″ 和 ”defer 2″。这两个 defer 语句的执行程序是先进后出的,也就是说后定义的 defer 语句先执行,先定义的 defer 语句后执行。因而,输入的程序应该是 ”main”、”defer 2″、”defer 1″。

这个例子也展现了 defer 语句的另一个个性,即在函数返回前执行。在 main 函数返回前,两个 defer 语句别离执行了它们的函数体,输入了相应的内容。这种个性能够用于开释资源、敞开连贯等操作,在函数返回前保障它们被执行。

须要留神的是,defer 语句并不是一种异步操作,它只是将被提早执行的函数退出到一个栈中,在函数返回前依照后进先出的程序执行。因而,在 defer 语句中的函数应该是轻量级的,防止影响程序的性能。同时,也须要留神 defer 语句的执行程序和函数返回时的状态,避免出现不合乎预期的后果。

3.Map

Go 语言中的 map 是一种十分有用的数据结构,能够用于存储键值对。上面是一道波及 map 的面试题及其详解。

问题形容:

有如下 Go 代码:

func main() {m := make(map[int]string)
    m[1] = "a"
    m[2] = "b"
    fmt.Println(m[1], m[2])
    delete(m, 2)
    fmt.Println(m[2])
}

请问下面的代码中,输入的后果是什么?

答案解析:

在下面的代码中,咱们应用 make 函数创立了一个 map,而后向其中增加了两个键值对,别离是 1:”a” 和 2:”b”。接着,咱们输入了这两个键对应的值,别离是 ”a” 和 ”b”。

接下来,咱们应用 delete 函数从 map 中删除了键为 2 的元素。而后,咱们尝试输入键为 2 的值,然而输入为空。这是因为咱们曾经从 map 中删除了键为 2 的元素,所以它对应的值曾经不存在了。

须要留神的是,当咱们从 map 中拜访一个不存在的键时,它会返回该值类型的零值。在本例中,值的类型是 string,它的零值是 ””。所以,当咱们尝试输入键为 2 的值时,它返回的是空字符串。

须要揭示的是,map 是一种援用类型的数据结构,它的底层实现是一个哈希表。 在应用 map 时,须要留神以下几点:

  1. map 是无序的,即元素的程序不固定。
  2. map 的键必须是能够进行相等性比拟的类型,如 int、string、指针等。(艰深来说就是能够用 == 和!= 来比拟的,除了 slice、map、function 这几个类型都能够)
  3. map 的值能够是任意类型,包含函数、构造体等。
  4. 在多个 goroutine 之间应用 map 时须要进行加锁,防止并发拜访导致的竞态问题。

4. 通道 Channel

Go 语言中的通道(channel)是一种十分有用的个性,用于在不同的 goroutine 之间传递数据。上面是一道波及通道的面试题及其详解。

问题形容:

有如下 Go 代码:

func main() {ch := make(chan int)
    go func() {
        ch <- 1
        ch <- 2
        ch <- 3
        close(ch)
    }()
    for {
        n, ok := <-ch
        if !ok {break}
        fmt.Println(n)
    }
    fmt.Println("done")
}

请问下面的代码中,输入的后果是什么?

答案解析:

在下面的代码中,咱们应用 make 函数创立了一个整型通道 ch。而后,咱们启动了一个 goroutine,向通道中写入了三个整数 1、2 和 3,并在最初应用 close 函数敞开了通道。

接着,在主函数中,咱们应用 for 循环不断从通道中读取数据,直到通道被敞开。每次从通道中读取到一个整数后,咱们将它输入。最初输入 ”done”,示意所有的数据曾经读取结束。

因为通道是一种同步的数据传输方式,写入和读取会阻塞直到对方筹备好,所以输入的后果应该是:

须要留神的是: 在通道被敞开后,读取操作依然能够从通道中读取到之前写入的数据。这是因为通道中的数据并没有立刻隐没,而是在读取结束后被垃圾回收器回收。因而,在应用通道时,须要依据理论状况判断何时敞开通道,以避免出现不必要的竞态和内存透露。

5. 接口

Go 语言中的接口(interface)是一种十分重要的个性,用于定义一组办法。上面是一道波及接口的面试题及其详解。

问题形容:

有如下 Go 代码:

type Animal interface {Speak() string
}

type Dog struct{}

func (d *Dog) Speak() string {return "Woof!"}

type Cat struct{}

func (c *Cat) Speak() string {return "Meow!"}

func main() {animals := []Animal{&Dog{}, &Cat{}}
    for _, animal := range animals {fmt.Println(animal.Speak())
    }
}

请问下面的代码中,输入的后果是什么?

答案解析:

在下面的代码中,咱们定义了一个 Animal 接口,它有一个 Speak 办法。而后,咱们定义了 Dog 和 Cat 两个构造体,别离实现了 Animal 接口的 Speak 办法。

接着,在 main 函数中,咱们创立了一个 Animal 类型的切片,其中蕴含了一个 Dog 对象和一个 Cat 对象。而后,咱们应用 for 循环遍历这个切片,调用每个对象的 Speak 办法,并输入它们返回的字符串。

因为 Dog 和 Cat 都实现了 Animal 接口的 Speak 办法,所以它们都是 Animal 类型的对象,能够被放入 Animal 类型的切片中。在遍历切片时,咱们调用每个对象的 Speak 办法,它们别离返回 ”Woof!” 和 ”Meow!”,而后被输入。

因而,输入的后果应该是:

须要留神的是,接口是一种动静类型,它能够蕴含任何实现了它所定义的办法集的类型。在应用接口时,须要留神以下几点:

  1. 接口是一种援用类型的数据结构,它的值能够为 nil。
  2. 实现接口的类型必须实现接口中所有的办法,否则会编译谬误。
  3. 接口的值能够赋给实现接口的类型的变量,反之亦然。
  4. 在实现接口的类型的办法中,能够通过类型断言来判断接口值的理论类型和值。

总结

这篇文章总结了 5 个知识点的面试题:逃逸剖析、提早语句 defer、散列表 map、通道 Channel、接口 interface

下一篇文章打算分享的 5 个知识点是:unsafe、context、错误处理、计时器、反射。

欢送大家三连反对一波,你的点赞、分享,是我更文的最大能源。

正文完
 0