本文的重点:逃逸分析、延迟语句、散列表、通道、接口。
1.逃逸分析
问题描述:
有如下Go代码:
func foo( *int {
x := 1
return &x
}
func main( {
p := foo(
fmt.Println(*p
}
请问上面的代码中,变量x是否会发生逃逸?
答案解析:
如果变量x没有发生逃逸,那么它会被分配在函数栈帧中,随着函数的返回而被自动销毁。而如果发生了逃逸,变量x就需要在堆上分配内存,并由垃圾回收器负责回收。在实际的程序中,大量的逃逸会导致内存分配和垃圾回收的开销增加,从而影响程序的性能。
更多逃逸分析的内容,可以阅读我之前分享的文章:内存分配和逃逸分析详解
2.延迟语句
问题描述:
有如下Go代码:
func main( {
defer func( {
fmt.Println("defer 1"
}(
defer func( {
fmt.Println("defer 2"
}(
fmt.Println("main"
}
请问上面的代码中,输出的顺序是什么?
答案解析:
这个例子也展示了defer语句的另一个特性,即在函数返回前执行。在main函数返回前,两个defer语句分别执行了它们的函数体,输出了相应的内容。这种特性可以用于释放资源、关闭连接等操作,在函数返回前保证它们被执行。
需要注意的是,defer语句并不是一种异步操作,它只是将被延迟执行的函数加入到一个栈中,在函数返回前按照后进先出的顺序执行。因此,在defer语句中的函数应该是轻量级的,避免影响程序的性能。同时,也需要注意defer语句的执行顺序和函数返回时的状态,避免出现不符合预期的结果。
3.散列表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]
}
请问上面的代码中,输出的结果是什么?
答案解析:
接下来,我们使用delete函数从map中删除了键为2的元素。然后,我们尝试输出键为2的值,但是输出为空。这是因为我们已经从map中删除了键为2的元素,所以它对应的值已经不存在了。
需要注意的是,当我们从map中访问一个不存在的键时,它会返回该值类型的零值。在本例中,值的类型是string,它的零值是""。所以,当我们尝试输出键为2的值时,它返回的是空字符串。
map是一种引用类型的数据结构,它的底层实现是一个哈希表。在使用map时,需要注意以下几点:
- map是无序的,即元素的顺序不固定。
- map的键必须是可以进行相等性比较的类型,如int、string、指针等。(通俗来说就是可以用==和!=来比较的,除了slice、map、function这几个类型都可以
- map的值可以是任意类型,包括函数、结构体等。
- 在多个goroutine之间使用map时需要进行加锁,避免并发访问导致的竞态问题。
4.通道Channel
问题描述:
有如下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"
}
请问上面的代码中,输出的结果是什么?
答案解析:
接着,在主函数中,我们使用for循环不断从通道中读取数据,直到通道被关闭。每次从通道中读取到一个整数后,我们将它输出。最后输出"done",表示所有的数据已经读取完毕。
在通道被关闭后,读取操作仍然可以从通道中读取到之前写入的数据。这是因为通道中的数据并没有立即消失,而是在读取完毕后被垃圾回收器回收。因此,在使用通道时,需要根据实际情况判断何时关闭通道,以避免出现不必要的竞态和内存泄漏。
5.接口
问题描述:
有如下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(
}
}
请问上面的代码中,输出的结果是什么?
答案解析:
接着,在main函数中,我们创建了一个Animal类型的切片,其中包含了一个Dog对象和一个Cat对象。然后,我们使用for循环遍历这个切片,调用每个对象的Speak方法,并输出它们返回的字符串。
因此,输出的结果应该是:
需要注意的是,接口是一种动态类型,它可以包含任何实现了它所定义的方法集的类型。在使用接口时,需要注意以下几点:
- 接口是一种引用类型的数据结构,它的值可以为nil。
- 实现接口的类型必须实现接口中所有的方法,否则会编译错误。
- 接口的值可以赋给实现接口的类型的变量,反之亦然。
- 在实现接口的类型的方法中,可以通过类型断言来判断接口值的实际类型和值。
总结
下一篇文章计划分享的5个知识点是:unsafe、context、错误处理、计时器、反射。
欢迎大家三连支持一波,你的点赞、分享,是我更文的最大动力。
公众号:程序员升职加薪之旅