- ✍个人博客:Pandaconda-CSDN博客
📣专栏地址:http://t.csdnimg.cn/UWz06
📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
46. 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 编译器将会对每个局部变量进行逃逸分析。如果发现局部变量的作用域超出该函数,则不会将内存分配在栈上,而是分配在堆上,因为他们不在栈区,即使释放函数,其内容也不会受影响。
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
,查看变量逃逸的情况。
./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。
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。
源代码:
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) {
//......
}
编译后(伪代码):
func A(i int) {
// code to do something
if(i > 1){
A2("Hello", "eggo")
}
A1(i, 2*i)
return
}
代码示例:
-
函数退出前,按照先进后出的顺序,执行 defer 函数
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
-
panic 后的 defer 函数不会被执行(遇到 panic,如果没有捕获错误,函数会立刻终止)
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
-
panic 没有被 recover 时,抛出的 panic 到当前 goroutine 最上层函数时,最上层程序直接异常终止。
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
- panic 有被 recover 时,当前 goroutine 最上层函数正常执行。
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