defer简介
defer是一种注册延迟调用的机制。它可写在申请资源语句的附近,但它保证了在注册函数执行完毕后(正常return 或panic都算)再去释放资源的操作(一定会去)。
defer的必要性
在诸如open/close connection/file 、lock/unlock 的操作中,开和关之间的代码是不可控的,比如因为panic导致锁资源一直无法释放造成死锁,所以需要有这样一种延迟机制保证即使panic了资源也能得到正确释放。
defer执行的顺序
defer会进入一个栈,所以先进的后出,执行顺序与定义顺序相反
defer引用外部变量的两种方式
- 函数参数值 静态方式,参数值在定义时就已经确定
- 闭包引用 动态方式,将在defer执行时根据上下文确定参数值
关于闭包:它还有另外一个令人感到熟悉的名字:匿名函数
不过如果是计算机科学理论课里提到的闭包(比如笔者学的离散数学、编译原理、形式语言与自动机中提到的:简单来说是集合的幂集的并集)
如何理解go在函数结束后发挥的作用
return xxx
被编译生成了三条指令:
- 返回值=xxx
- defer函数
- 空return
数组与切片
概述
- 切片是对数组的封装,多个切片有可能指向同一个底层数组
- 数组中的长度也是其类型的一部分。 所以[3]int 和[4]int不是同一类型,不能进行比较;不过切片没有这种说法,
切片组成
type slice struct{
array unsafe.Pointer
len int
cap int
}
切片截取
- 含义:从数组或slice中截取某一段
两个共用底层数组的slice的操作会影响到彼此。
注意:扩容之后的切片有可能会指向新的地址,这时候两个slice不再是共用底层数组了,因此不会有影响。
原因:底层数组的长度是固定的,扩容后的slice不能超出底层数组的边界
切片扩容
在上个问题中我们见到了切片扩容问题,在这一部分我们继续细说。 在进行扩容操作时,会预留多一些buffer,将新容量扩大为原始容量的2倍。
关于扩容规律:
以下是go1.19的源码截图:
路径地址:$GOROOT/src/runtime/slice.go
由此可知:
- 当要扩容的长度大于原来长度的两倍时,直接赋值新长度
- 否则,设置一个阈值256,如果原长度<阈值256,赋值原长度的两倍
- 如果大于阈值,就设置一个平滑的过渡,从2倍过渡到1.25倍
make和new的区别?
通过阅读源码及其上的注释可知:
| make | new |
|---|---|
| 分配并初始化对象(仅适用于类型slice/map/channel) | 动态分配内存,且分配值置0 (字符为空,整型为0, 逻辑值为false) |
| 传入的是类型Type,返回值也是类型 | 传入的是类型Type,返回的是指针 |
参考文献
- Go源码(路径:$GOROOT/src下)
- 《Go程序员面试笔试宝典》
写在文末的一点碎碎念:
书是从图书馆借来的,本来想着只看纸质书,但是才看了20来页,就至少发现了三个错误,搞得后面看到和我认知不一样的地方我都第一时间觉得是书的问题,最后也开始看源码、参考网上其它博客,逐一验证书里讲的内容的真伪。
我看现在网上也有电子版,和我手上的纸质书不太一样,有的内容我书上有、电子版却没有。有些地方电子书确实也在及时勘误,建议大家最好去看电子版,及时跟作者提意见。
另外就是,一定要自己去看源码,第二手的资料就作为辅助就好了,不要本末倒置。
最后就是,就算是文档里面的注解,由于是英文的,可能由于语义的多样而存在理解上的偏差,这时候要动手多试几个例子验证一下,不要死啃理论。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情