常见的一些 Go 面试题

127 阅读6分钟
  1. 什么是 Go 切片? 答:Go 切片是一种动态数组,它能够自动扩容,这意味着无需预先分配数组长度。切片在使用前不需要定义长度,底层会自动根据需要扩容。
  2. 什么是 Goroutine? 答:Goroutine 是 Go 语言的轻量级线程,它可以在不同的执行路径上运行,通过使用 Go runtime 管理,可以实现并发执行。同时,Goroutine 的开销较小,Go 语言可以支持大量 Goroutine 的同时执行。
  3. 什么是 Go 接口? 答:Go 接口是指定了一组方法签名的抽象类型。实现了这个方法签名的类型就可以实现该接口。接口实现了 Go 语言中的多态机制,它比继承更加灵活。
  4. 什么是 Go defer? 答:defer 是 Go 语言提供的一种机制,用来在函数退出前执行释放资源等善后工作。defer 主要用于在函数返回前释放资源或呈现内容。
  5. 什么是 Go 并发安全?

答:Go 并发安全可以确保在多协程并发时,共享的数据结构不会出现竞态条件。 互斥锁(Mutex) 和 读写锁(RLock) 是 Go 语言中最常用的并发安全解决方案。

  1. 如何自定义 HTTP 客户端超时?

答:可以使用 Context 来跟踪请求并设置超时,通过创建一个带时间的上下文(Context)和使用 Timeout 方法即可。

  1. Go 中变量的作用域有哪些? 答:Go 中的变量作用域可以分为以下几种:

    • 函数内部声明的变量作用域为函数内部。
    • 外部包中的变量是全局变量,其他包可以使用。
    • 通常结构体与接口定义的字段作用于整个包。
  2. 如何封装 Go 结构体? 答:结构体是一种可以定义各种类型的数据结构,并与函数和方法关联的新类型。Go 提供了私有和公有的访问控制权限,可以使用小写字母定义私有成员,大写字母定义公有成员,用于封装和保护数据。

  3. 如何防止 Go 的资源泄漏? 答:编写 Go 代码时,开发人员可以动态分配内存和创建 Goroutine 以便高效、可扩展。但是这也可能会导致资源泄漏。主要是通过关闭文件、释放数据库连接和避免创建出现无限循环等。

  4. Go 如何识别 nil 值? 答:nil 值在 Go 中表示指针或接口的底层类型为 nil。可以通过类型断言或使用 value == nil 来判断指针或接口是否为 nil。

代码题

  1. 请解释下面一段代码的输出:

    Copy code
    package main
    
    import "fmt"
    
    func main() {
        a := make([]int, 5)
        b := make([]int, 5, 10)
    
        fmt.Println(a, len(a), cap(a))
        fmt.Println(b, len(b), cap(b))
    }
    

答:该代码将创建两个长度为5的整型切片。变量 a 没有使用第三个参数,因此,底层数组的大小为5。变量b 使用了第三个参数10,因此底层数组的大小为10。输出结果为:

Copy code
[0 0 0 0 0] 5 5
[0 0 0 0 0] 5 10
  1. 请解释下面一段代码:

    Copy code
    package main
    
    import "fmt"
    
    func main() {
        ch := make(chan int, 1)
        ch <- 1
        fmt.Println(<-ch)
    }
    

答:该代码将创建了一个缓冲区大小为1的整型channel。然后向channel发送一个1,最后从该channel接收数据并打印。因为channel是带缓冲的,可以安全的发送和接收数据。

  1. 请解释下面一段代码的输出:

    Copy code
    package main
    
    import "fmt"
    
    func main() {
        var a []int
    
        fmt.Println(a == nil)
    
        a2 := []int{}
    
        fmt.Println(a2 == nil)
    }
    

答:该代码将创建了两个整型切片,第一个切片 a 没有分配内存因此值为 nil,第二个切片 a2 是个空切片,因此不是 nil。

输出结果为:

Copy code
true
false
  1. 请解释下面一段代码:

    Copy code
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    var wg sync.WaitGroup
    
    func main() {
        for i := 1; i <= 5; i++ {
            wg.Add(1)
            go processData(i)
        }
    
        wg.Wait()
    }
    
    func processData(id int) {
        defer wg.Done()
    
        fmt.Println("Processing started for ", id)
    }
    

答:该代码将并发处理5个不同的进程。我们使用 WaitGroup 来等待所有进程完成。每个进程在处理之前,调用 Add() 增加 WaitGroup 的计数器,处理完成之后,调用 Done() 函数来减少计数器。

输出结果为:

Copy code
Processing started for 1
Processing started for 2
Processing started for 3
Processing started for 4
Processing started for 5
  1. 请解释下面一段代码:

    Copy code
    package main
    
    import (
        "fmt"
    )
    
    func main() {
        a := [3][2]string{
            {"apple", "banana"},
            {"pear", "plum"},
            {"peach", "kiwi"},
        }
    
        for i, v := range a {
            for j, val := range v {
                fmt.Printf("[%d][%d] = %s\n", i, j, val)
            }
        }
    }
    

答:该代码创建了一个三行两列的字符串数组。然后使用嵌套 for 循环遍历该数组,并将数组元素的值打印到控制台。

输出结果为:

Copy code
[0][0] = apple
[0][1] = banana
[1][0] = pear
[1][1] = plum
[2][0] = peach
[2][1] = kiwi
  1. 请解释下面一段代码:

    Copy code
    package main
    
    import (
        "fmt"
        "os"
    )
    
    func main() {
        file, err := os.Open("file.txt")
    
        if err != nil {
            fmt.Println(err)
            return
        }
    
        defer file.Close()
    
        // Use the file variable.
    }
    

答:该代码首先尝试打开文件 file.txt。如果打开文件不成功,它将打印错误并返回;否则,它会使用 defer 语句来确保在函数退出前,关闭文件。

  1. 请解释下面一段代码:

    Copy code
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        fmt.Println("Started")
    
        for i := 1; i <= 5; i++ {
            go process(i)
        }
    
        time.Sleep(time.Second * 2)
    }
    
    func process(i int) {
        fmt.Println("Processing ", i)
        time.Sleep(time.Second)
    }
    

答:该代码将并发运行 5 个群体。main() 函数打印 "Started" ,然后启动 5 个 Goroutine 进程,这些进程将 process() 函数作为参数,并传递一个整数参数。每个 process(i) 函数打印一个正在执行的消息,并休眠一秒钟。然后,main() 函数休眠 2 秒钟,这样所有的 Goroutine 进程都将有足够的时间完成它们的工作。

输出结果为:

Copy code
Started
Processing 1
Processing 4
Processing 2
Processing 3
Processing 5
  1. 请解释下面一段代码:

    Copy code
    package main
    
    import "fmt"
    
    func main() {
        a := [3]int{1, 2, 3}
    
        for _, v := range a {
            fmt.Println(v)
        }
    }
    

答:该代码将创建一个整型数组,然后使用 range 表达式中的 _ 替换了循环变量索引 i。因此,可以使用变量 v 访问数组中的元素。使用循环将每个数组值打印到控制台。

输出结果为:

Copy code
1
2
3
  1. 请解释下面一段代码的输出:

    Copy code
    package main
    
    import "fmt"
    
    func main() {
        var x float64 = 15.5
        fmt.Printf("x is of type %T\n", x)
    
        var y = "Hello, World!"
        fmt.Printf("y is of type %T\n", y)
    
    }
    

答:该代码将声明变量 xfloat64 类型并赋值,然后将该变量的类型用格式符 %T 打印到控制台。接下来,变量 y 赋值为一个字符串,并将该变量的类型使用格式符 %T 打印到控制台。

输出结果为:

Copy code
x is of type float64
y is of type string
  1. 请解释下面一段代码:

    Copy code
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func hello(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello, World!")
    }
    
    func main() {
        http.HandleFunc("/", hello)
        http.ListenAndServe(":80", nil)
    }
    

答:该代码使用 Go 的 http 包创建了一个简单的 Web 服务器。我们定义了一个名为 hello() 的函数,它接受 http.ResponseWriter*http.Request 两个参数,且在请求时输出 "Hello, World!" 到客户端。 在 main() 函数中,通过调用 http.HandleFunc() 函数来处理请求,向默认路由 / 的使用者响应。

Copy code
最后,我们通过 `http.ListenAndServe()` 函数来监听特定端口的 HTTP 请求。在本例中,我们监听 80 端口,也就是 Web 服务器默认的 HTTP 端口。