【Golang 面试基础题】每日 5 题(十)

  1. ✍个人博客:Pandaconda-CSDN博客

📣专栏地址:http://t.csdnimg.cn/UWz06

📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~

❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪

46. Go 方法值接收者和指针接收者的区别?

在 Go 中,方法可以定义在结构体类型上。接收者是指在方法定义中声明的函数参数。接收者可以是值接收者,也可以是指针接收者。值接收者在方法调用时会对接收者进行复制,而指针接收者则会使用指针来引用原始接收者。

使用值接收者时,方法中对接收者所做的任何修改都不会影响原始接收者。而使用指针接收者时,方法中对接收者所做的任何修改都将影响原始接收者。

另外,指针接收者的优势在于它可以避免在每次调用方法时复制接收者,从而提高程序的性能。此外,在某些情况下,只有使用指针接收者才能修改接收者的状态,因为值接收者只能修改接收者的副本。

例如,以下代码演示了一个使用值接收者和指针接收者的方法:

Go 复制代码
type Counter struct {
    count int
}

// 值接收者方法
func (c Counter) increment() {
    c.count++
}

// 指针接收者方法
func (c *Counter) decrement() {
    c.count--
}

func main() {
    // 值接收者方法不会改变原始接收者的值
    c1 := Counter{count: 0}
    c1.increment()
    fmt.Println(c1.count) // 输出 0

    // 指针接收者方法会改变原始接收者的值
    c2 := Counter{count: 0}
    c2.decrement()
    fmt.Println(c2.count) // 输出 -1
}

在上面的示例中,increment() 方法使用值接收者,而 decrement() 方法使用指针接收者。在调用 increment() 方法后,原始 Counter 结构体实例的 count 属性保持为零,因为该方法对接收者的修改只影响了接收者的副本。而在调用 decrement() 方法后,原始 Counter 结构体实例的 count 属性减少了一,因为该方法直接修改了原始接收者。

47. Go 函数返回局部变量的指针是否安全?

一般来说,局部变量在函数返回后被销毁,因此被返回的引用就成为了 "无所指" 的引用,程序会进入未知状态。

但这在 Go 中是安全的,Go 编译器将会对每个局部变量进行逃逸分析。如果发现局部变量的作用域超出该函数,则不会将内存分配在栈上,而是分配在堆上,因为他们不在栈区,即使释放函数,其内容也不会受影响。

Go 复制代码
package main

import "fmt"

func add(x, y int) *int {
    res := 0
    res = x + y
    return &res
}

func main() {
    fmt.Println(add(1, 2))
}

这个例子中,函数 add 局部变量 res 发生了逃逸。res 作为返回值,在 main 函数中继续使用,因此 res 指向的内存不能够分配在栈上,随着函数结束而回收,只能分配在堆上。

编译时可以借助选项 -gcflags=-m,查看变量逃逸的情况。

html 复制代码
./main.go:6:2: res escapes to heap:
./main.go:6:2:   flow: ~r2 = &res:
./main.go:6:2:     from &res (address-of) at ./main.go:8:9
./main.go:6:2:     from return &res (return) at ./main.go:8:2
./main.go:6:2: moved to heap: res
./main.go:12:13: ... argument does not escape
0xc0000ae008

res escapes to heap 即表示 res 逃逸到堆上了。

48. def er 的执行顺序是什么? defer的作用和特点是什么?

在 Go 语言中,defer 是一种延迟执行机制,用于在函数退出前执行一些特定的代码,无论是函数正常返回还是发生异常。defer 语句是在函数调用结束后执行的,即使出现错误或 panic 也会执行。defer 可以用于清理资源、处理错误等场景。

defer 语句的执行顺序是 "后进先出" 的,也就是说最后一个被 defer 的语句会最先执行,直到第一个被 defer 的语句执行完毕为止。

例如,下面的代码中,defer 语句的执行顺序是 3、2、1。

Go 复制代码
func example() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    fmt.Println("done")
}

需要注意的是,defer 延迟执行的代码并不是在函数退出前立即执行,而是在函数执行结束后,当函数返回时才会执行。因此,如果在 defer 语句中使用的变量在函数返回前发生了改变,那么最终执行的代码将使用最终值。

49. Go defer 关键字的实现原理?

定义

defer 能够让我们推迟执行某些函数调用,推迟到当前函数返回前才实际执行。defer 与 panic 和 recover 结合,形成了 Go 语言风格的异常与捕获机制。

使用场景

defer 语句经常被用于处理成对的操作,如文件句柄关闭、连接关闭、释放锁。

优点:

方便开发者使用。

缺点:

有性能损耗。

实现原理

Go1.14 中编译器会将 defer 函数直接插入到函数的尾部,无需链表和栈上参数拷贝,性能大幅提升。把 defer 函数在当前函数内展开并直接调用,这种方式被称为 open coded defer。

源代码:

Go 复制代码
func A(i int) {
    defer A1(i, 2*i)
    if(i > 1) {
        defer A2("Hello", "eggo")
    }
    // code to do something
    return
}
func A1(a,b int) {
    //......
}
func A2(m,n string) {
    //......
}

编译后(伪代码):

Go 复制代码
func A(i int) {
        // code to do something
    if(i > 1){
       A2("Hello", "eggo")
    }
    A1(i, 2*i)
    return
}

代码示例

  1. 函数退出前,按照先进后出的顺序,执行 defer 函数

    Go 复制代码
    package main
    
    import "fmt"
    
    // defer:延迟函数执行,先进后出
    func main() {
        defer fmt.Println("defer1")
        defer fmt.Println("defer2")
        defer fmt.Println("defer3")
        defer fmt.Println("defer4")
        fmt.Println("11111")
    }
    
    // 11111
    // defer4
    // defer3
    // defer2
    // defer1
    点击并拖拽以移动
  2. panic 后的 defer 函数不会被执行(遇到 panic,如果没有捕获错误,函数会立刻终止)

    Go 复制代码
    package main
    
    import "fmt"
    
    // panic后的defer函数不会被执行
    func main() {
        defer fmt.Println("panic before")
        panic("发生panic")
        defer func() {
            fmt.Println("panic after")
        }()
    }
    
    // panic before
    // panic: 发生panic
    点击并拖拽以移动
  3. panic 没有被 recover 时,抛出的 panic 到当前 goroutine 最上层函数时,最上层程序直接异常终止。

    Go 复制代码
    package main
    
    import "fmt"
    
    func F() {
        defer func() {
            fmt.Println("b")
        }()
        panic("a")
    }
    
    // 子函数抛出的panic没有recover时,上层函数时,程序直接异常终止
    func main() {
        defer func() {
            fmt.Println("c")
        }()
        F()
        fmt.Println("继续执行")
    }
    
    // b
    // c
    // panic: a
    点击并拖拽以移动
  4. panic 有被 recover 时,当前 goroutine 最上层函数正常执行。

Go 复制代码
package main

import "fmt"

func F() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("捕获异常:", err)
        }
        fmt.Println("b")
    }()
    panic("a")
}

func main() {
    defer func() {
        fmt.Println("c")
    }()
    F()
    fmt.Println("继续执行")
}

// 捕获异常: a
// b
// 继续执行
// c
相关推荐
跟着珅聪学java35 分钟前
spring boot +Elment UI 上传文件教程
java·spring boot·后端·ui·elementui·vue
我命由我1234540 分钟前
Spring Boot 自定义日志打印(日志级别、logback-spring.xml 文件、自定义日志打印解读)
java·开发语言·jvm·spring boot·spring·java-ee·logback
徐小黑ACG2 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
0白露3 小时前
Apifox Helper 与 Swagger3 区别
开发语言
Tanecious.4 小时前
机器视觉--python基础语法
开发语言·python
叠叠乐4 小时前
rust Send Sync 以及对象安全和对象不安全
开发语言·安全·rust
想跑步的小弱鸡4 小时前
Leetcode hot 100(day 3)
算法·leetcode·职场和发展
战族狼魂4 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
Tttian6225 小时前
Python办公自动化(3)对Excel的操作
开发语言·python·excel
杉之6 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue