提交 7862c4b4 编写于 作者: 写代码的明哥's avatar 写代码的明哥

新增文章

上级 f8bb58f4
# 7.15 defer 的变量快照什么情况会失效?
关于 defer 的基本知识点,我在以前的教程中有写过:[流程控制:defer 延迟语句](https://golang.iswbm.com/c01/c01_12.html)
其中有一个知识是 defer 的变量快照,举个简单的例子来说
在下面这段代码中,会先打印出来 `18`,即使后面 age 已经被改变了,可 defer 中的 age还是 修改之前的 `0`,这种现象称之为变量快照。
```go
func func1() {
age := 0
defer fmt.Println(age) // output: 0
age = 18
fmt.Println(age) // output: 18
}
func main() {
func1()
}
```
对于这个输出结果,相信还是挺容易理解的。
接下来,我请大家再看下面这个例子,可以猜猜看会输出什么?
```go
func func1() {
age := 0
defer func() {
fmt.Println(age)
}()
age = 18
return
}
func main() {
func1()
}
```
正确的答案是:`18`, 而不是 `0`
你肯定会纳闷:不对啊,defer 不是会对变量的值做一个快照吗?答案应该是 0 啊,为什么会是 18?
实际上,仔细观察,可以发现上面的两个例子的区别就在于,一个 defer 后接的是单个表达式,另一个 defer 后接的是一个函数,并且不是普通函数,而是一个匿名的闭包函数。
根据闭包的特性,实际上在闭包函数存的是 age 这个变量的指针(原因可以查看上一篇文章:[Go 语言面试题 100 讲之 014篇:说说 Go 中闭包的底层原理?](https://iswbm.com/534.html)),因而,在 defer 后所修改的值会直接影响到 defer 中的 age 的值。
总结一下:
1. 若 defer 后接的是单行表达式,那defer 中的 age 只是拷贝了 `func1` 函数栈中 defer 之前的 age 的值;
2. 若 defer 后接的是闭包函数,那defer 中的 age 只是存储的是 `func1` 函数栈中 age 的指针。
\ No newline at end of file
7.15 defer 的变量快照什么情况会失效?
=====================================
关于 defer 的基本知识点,我在以前的教程中有写过:\ `流程控制:defer
延迟语句 <https://golang.iswbm.com/c01/c01_12.html>`__
其中有一个知识是 defer 的变量快照,举个简单的例子来说
在下面这段代码中,会先打印出来 ``18``\ ,即使后面 age 已经被改变了,可
defer 中的 age还是 修改之前的 ``0``\ ,这种现象称之为变量快照。
.. code:: go
func func1() {
age := 0
defer fmt.Println(age) // output: 0
age = 18
fmt.Println(age) // output: 18
}
func main() {
func1()
}
对于这个输出结果,相信还是挺容易理解的。
接下来,我请大家再看下面这个例子,可以猜猜看会输出什么?
.. code:: go
func func1() {
age := 0
defer func() {
fmt.Println(age)
}()
age = 18
return
}
func main() {
func1()
}
正确的答案是:\ ``18``\ , 而不是 ``0``
你肯定会纳闷:不对啊,defer 不是会对变量的值做一个快照吗?答案应该是 0
啊,为什么会是 18?
实际上,仔细观察,可以发现上面的两个例子的区别就在于,一个 defer
后接的是单个表达式,另一个 defer
后接的是一个函数,并且不是普通函数,而是一个匿名的闭包函数。
根据闭包的特性,实际上在闭包函数存的是 age
这个变量的指针(原因可以查看上一篇文章:\ `Go 语言面试题 100 讲之
014篇:说说 Go
中闭包的底层原理? <https://iswbm.com/534.html>`__\ ),因而,在 defer
后所修改的值会直接影响到 defer 中的 age 的值。
总结一下:
1. 若 defer 后接的是单行表达式,那defer 中的 age 只是拷贝了 ``func1``
函数栈中 defer 之前的 age 的值;
2. 若 defer 后接的是闭包函数,那defer 中的 age 只是存储的是 ``func1``
函数栈中 age 的指针。
7.1 20 个学习 Go 语言的精品网站
===============================
|image0|
本文写自我的知乎回答:https://www.zhihu.com/question/23486344/answer/1204644361
--------------
先说下我个人的情况吧,我本是个 Python
重度使用者,年初出于工作的需要,开始学习 Golang
,学到现在已经有4个多月的时间了。
期间为了记录自己的学习过程,同时给正想学习 Go
语言的同学一些帮助,我将自己的所学写成一个教程,发布在我的 Github
上(https://github.com/iswbm/GolangCodingTime)以及我的个人网站上。
**网站链接** 是:http://golang.iswbm.com/
目前已经更新了 30 多篇,覆盖了 Golang 90%
的入门必学知识点,内容我还在不断完善更新中,而且\ **我还规划实战板块,比如
Web 开发,爬虫程序的编写等**\ ,敬请期待。
如果你还在入门阶段,或者准备入门,那么建议把我的个人网站收藏一下,完全可以当做
wiki 查阅。对于基础知识我讲的还是比较全面,细致的。
|image1|
写 《\ `Go
编程时光 <https://golang.iswbm.com/>`__\ 》这个系列教程,为了让文章出错的概率降到最低,我如履薄冰,我每写一个知识点,就要翻阅大量的其他人的教程、博客,以及官方文档。
也因此,\ **我收藏了很多学习 Go 语言的网站和资源。**
俗话说,授人以鱼,不如授人以渔。今天就把这些资源全部分享给大家。
接下来,废话不多说了,直接上干货。
下面网站比较多,我把将它们分成 五大类,你可以根据自己的实际
情况进行选择:
1. 入门阶段
2. Web开发
3. 进阶阶段
4. 工具使用
5. 技术社区
6. 源码阅读
另外,为了方便大家,我制作了张思维导图,这下清晰多了吧(hhhhh
|image2|
1. 入门阶段
-----------
Go语言入门教程
~~~~~~~~~~~~~~
C语言中文网的系列教程,对新手非常友好的一个入门教程,很多内容我也是从这里学习的,推荐大家看看。
**网站链接**\ :http://c.biancheng.net/golang/
|image3|
菜鸟教程 - Go 系列
~~~~~~~~~~~~~~~~~~
菜鸟教程(RUNOOB)是一个一站式编程入门学习网站,想当年我学习 Python
时也经常 在这里同样也有 Go 语言的系列。
**网站链接**\ :https://www.runoob.com/go/go-tutorial.html
|image4|
易百教程 - Go 系列
~~~~~~~~~~~~~~~~~~
和菜鸟教程,W3Cschool一样,易百教程也是一个一站式的编程语言入门教程网站,目前为止,共发布了
157 个系列教程,每个教程都很适合新手学习。
**网站链接**\ :https://www.yiibai.com/go/
|image5|
W3Cshcool - Go 系列
~~~~~~~~~~~~~~~~~~~
W3Cschool
也是一个专业的编程入门学习及技术文档查询应用,提供包括\ `HTML <https://baike.baidu.com/item/HTML>`__\ ,\ `CSS <https://baike.baidu.com/item/CSS/5457>`__\ ,\ `Javascript <https://baike.baidu.com/item/Javascript>`__\ ,\ `jQuery <https://baike.baidu.com/item/jQuery>`__\ ,\ `C <https://baike.baidu.com/item/C/7252092>`__\ ,\ `PHP <https://baike.baidu.com/item/PHP/9337>`__\ ,\ `Java <https://baike.baidu.com/item/Java/85979>`__\ ,\ `Python <https://baike.baidu.com/item/Python>`__\ ,\ `Sql <https://baike.baidu.com/item/Sql>`__\ ,MySQL,Golang
等编程语言和开源技术的在线教程及使用手册,是类似国外w3schools的学习社区及菜鸟编程平台。
**网站链接**\ :https://www.w3cschool.cn/go/
|image6|
TopGoer 教程
~~~~~~~~~~~~
这个网站是我在搜索资料的时候偶然发现的,从目录可以看出内容非常多,网络编程、并发编程,很多主流的
Web 框架都有。
**网站链接**\ :http://www.topgoer.com/
|image7|
The Way to Go
~~~~~~~~~~~~~
《the way to go》的
中文版本,目前还在持续更新中,截止今天(2020/04/30)翻译进度已经达到
96.92%。
**网站链接**\ :https://learnku.com/docs/the-way-to-go
|image8|
极客学院(Go 教程)
~~~~~~~~~~~~~~~~~~~
极客学院,这个网站里收集了很多领域的编程语言相关的教程,在这里依然可以找到不少
Go 的教程。
我不知道这些内容是否获得授权,但对于要学习需求的同学来说,确实是一个不错的网站。
**网站链接**\ :https://wiki.jikexueyuan.com/list/go/
|image9|
Go 语言简明教程
~~~~~~~~~~~~~~~
**网站链接**\ :https://geektutu.com/post/quick-golang.html
|image10|
Go by Example
~~~~~~~~~~~~~
这个网站的 idea 非常好,网站里收集了很多的小例子,来帮助你快速了解 Go
语言里那些基础的知识点。不过要深入理解这些知识,还是需要你学习更多的资料,并加以练习。
**网站链接**\ :https://gobyexample-cn.github.io/
|image11|
Go语言101
~~~~~~~~~
《Go语言101》是一本着墨于Go语法语义以及运行时相关知识点的编程指导书(Go
1.16就绪)。 此书旨在尽可能地帮助Go程序员更深更全面地理解Go语言。
此书也搜集了Go语言和Go编程中的很多细节。
此书同时适合Go初学者和有一定经验的Go程序员阅读。
**网站链接**\ :https://gfw.go101.org/article/101.html
|image12|
2. Web开发
----------
gin 中文文档
~~~~~~~~~~~~
**网站链接**\ :https://github.com/skyhee/gin-doc-cn
beego 开发文档
~~~~~~~~~~~~~~
**网站链接**\ :https://www.kancloud.cn/hello123/beego/126086
echo 中文文档
~~~~~~~~~~~~~
**网站链接**\ :https://www.bookstack.cn/read/go-echo/README.md
Iris 中文文档
~~~~~~~~~~~~~
**网站链接**\ :https://studyiris.com/doc/
Buffalo 中文文档
~~~~~~~~~~~~~~~~
**网站链接**\ :https://learnku.com/docs/buffalo-doc-cn
Revel 中文文档
~~~~~~~~~~~~~~
**网站链接**\ :https://www.bookstack.cn/books/gorevel-manual-zh
3. 进阶学习
-----------
跟煎鱼学Go
~~~~~~~~~~
煎鱼大佬的博客,跟着学习了很多的干货。良心推荐一波。
**网站链接**\ :https://eddycjy.gitbook.io/golang/
|image13|
Go语言圣经
~~~~~~~~~~
本书由《C程序设计语言》
的作者Kernighan和谷歌公司Go团队合作编写而成,是关于Go语言编程的权威著作。
**网站链接**\ :https://books.studygolang.com/gopl-zh/
|image14|
mojotv 进阶系列
~~~~~~~~~~~~~~~
一个致力于 Go 语言编程知识分享的高质量网站,里面有大量关于 Go
进阶的文章,此外还有 Python、 Docker ,K8S
,算法的文章。我试图在网站上找出作者的相关信息,不过并没有什么收获。
**网站链接**\ :https://mojotv.cn/404#Golang
|image15|
Go 语言高级编程
~~~~~~~~~~~~~~~
**网站链接**\ :https://chai2010.gitbooks.io/advanced-go-programming-book/content/
|image16|
4. 工具使用
-----------
Go 命令教程
~~~~~~~~~~~
go 的命令非常多,如果想系统的学习,推荐郝林的 Go 命令教程,非常的全。
**网站链接**\ :
1、 https://hyper0x.github.io/go_command_tutorial/#/
2、https://wiki.jikexueyuan.com/project/go-command-tutorial/0.0.html
|image17|
Uber 编程规范
~~~~~~~~~~~~~
Uber 开源了其公司内部使用的《\ `Go
语言编程规范 <https://github.com/uber-go/guide/blob/master/style.md>`__\ 》。该指南是为了使代码库更易于管理,同时让工程师有效地使用
Go 语言特性。文档中详细描述了在 Uber 编写 Go
代码的各种注意事项,包括具体的“Dos and Don’ts of writing Go code at
Uber”,也就是 Go 代码应该怎样写、不该怎样写。
**网站链接**
英文原文:https://github.com/uber-go/guide/blob/master/style.md
中文译文:https://www.infoq.cn/article/G6c95VyU5telNXXCC9yO
|image18|
Go Walker
~~~~~~~~~
Go Walker 是一个可以在线生成并浏览 `Go <https://golang.org/>`__ 项目 API
文档的 Web 服务器,目前已支持包括 **GitHub** 等代码托管平台。
|image19|
CTOLib 码库
~~~~~~~~~~~
像是一个收集中心,收藏着网络上的各种教程资源 ,里面也可以看到 Go
的很多工具,教程。
**网站链接**\ :https://www.ctolib.com/go/categories/go-guide.html
|image20|
5. 技术社区
-----------
GoCN
~~~~
**网站链接**\ :https://gocn.vip/
|image21|
Go 语言中文网
~~~~~~~~~~~~~
Go 语言爱好者的聚集地,是目前最大的Go 语言中文社区,关于 Go 语言
你所需要的,不需要的,都可以在这里找得到,包括
Go历史版本的下载,各种高质量的电子书资源,各种大牛写的高质量文章等。
**网站链接**\ :https://studygolang.com/
|image22|
6. 源码学习
-----------
Go 夜读
~~~~~~~
这是一个由众多资深 Gopher 组织的开源项目,主要是对 Go
源码进行解读,并约定每周四晚上进行技术分享。
**网站链接**\ :https://talkgo.org/
|image23|
Go 语言原本
~~~~~~~~~~~
**网站链接**\ :https://changkun.de/golang/
|image24|
Go 语言设计与实现
~~~~~~~~~~~~~~~~~
目前还在更新中,写得有点深,进阶的可以看看
**网站链接**\ :https://draveness.me/golang/
|image25|
能翻到这里的,一定是真爱了,本以为 Go
语言还处于不温不火的状态,没想到收集整理一下,资料还挺多的。
--------------
|image26|
.. |image0| image:: http://image.iswbm.com/20200607145423.png
.. |image1| image:: http://image.iswbm.com/20200430112024.png
.. |image2| image:: http://image.iswbm.com/20200506192746.png
.. |image3| image:: http://image.iswbm.com/20200430102243.png
.. |image4| image:: http://image.iswbm.com/20200430170656.png
.. |image5| image:: http://image.iswbm.com/20200430172511.png
.. |image6| image:: http://image.iswbm.com/20200430171029.png
.. |image7| image:: http://image.iswbm.com/20200430102508.png
.. |image8| image:: http://image.iswbm.com/20200430165344.png
.. |image9| image:: http://image.iswbm.com/20200430104324.png
.. |image10| image:: http://image.iswbm.com/20200430174507.png
.. |image11| image:: http://image.iswbm.com/20200430112319.png
.. |image12| image:: http://image.iswbm.com/image-20210411181553375.png
.. |image13| image:: http://image.iswbm.com/20200430105116.png
.. |image14| image:: http://image.iswbm.com/20200430100755.png
.. |image15| image:: http://image.iswbm.com/20200430095544.png
.. |image16| image:: http://image.iswbm.com/20200430175818.png
.. |image17| image:: http://image.iswbm.com/20200430102821.png
.. |image18| image:: http://image.iswbm.com/20200430113756.png
.. |image19| image:: http://image.iswbm.com/20200430170054.png
.. |image20| image:: http://image.iswbm.com/20200430174109.png
.. |image21| image:: http://image.iswbm.com/20200506192127.png
.. |image22| image:: http://image.iswbm.com/20200430134207.png
.. |image23| image:: http://image.iswbm.com/20200430174216.png
.. |image24| image:: http://image.iswbm.com/20200506191803.png
.. |image25| image:: http://image.iswbm.com/20200506191632.png
.. |image26| image:: http://image.iswbm.com/20200607174235.png
7.2 Go 语言中边界检查
=====================
|image0|
1. 什么是边界检查?
-------------------
边界检查,英文名 ``Bounds Check Elimination``\ ,简称为 BCE。它是 Go
语言中防止数组、切片越界而导致内存不安全的检查手段。如果检查下标已经越界了,就会产生
Panic
边界检查使得我们的代码能够安全地运行,但是另一方面,也使得我们的代码运行效率略微降低。
比如下面这段代码,会进行三次的边界检查
.. code:: go
package main
func f(s []int) {
_ = s[0] // 检查第一次
_ = s[1] // 检查第二次
_ = s[2] // 检查第三次
}
func main() {}
你可能会好奇了,三次?我是怎么知道它要检查三次的。
实际上,你只要在编译的时候,加上参数即可,命令如下
.. code:: shell
$ go build -gcflags="-d=ssa/check_bce/debug=1" main.go
# command-line-arguments
./main.go:4:7: Found IsInBounds
./main.go:5:7: Found IsInBounds
./main.go:6:7: Found IsInBounds
2. 边界检查的条件?
-------------------
并不是所有的对数组、切片进行索引操作都需要边界检查。
比如下面这个示例,就不需要进行边界检查,因为编译器根据上下文已经得知,\ ``s``
这个切片的长度是多少,你的终止索引是多少,立马就能判断到底有没有越界,因此是不需要再进行边界检查,因为在编译的时候就已经知道这个地方会不会
panic
.. code:: go
package main
func f() {
s := []int{1,2,3,4}
_ = s[:9] // 不需要边界检查
}
func main() {}
因此可以得出结论,对于在编译阶段无法判断是否会越界的索引操作才会需要边界检查,比如这样子
.. code:: go
package main
func f(s []int) {
_ = s[:9] // 需要边界检查
}
func main() {}
3. 边界检查的特殊案例
---------------------
3.1 案例一
~~~~~~~~~~
在如下示例代码中,由于索引 2
在最前面已经检查过会不会越界,因此聪明的编译器可以推断出后面的索引 0
1 不用再检查啦
.. code:: go
package main
func f(s []int) {
_ = s[2] // 检查一次
_ = s[1] // 不会检查
_ = s[0] // 不会检查
}
func main() {}
3.2 案例二
~~~~~~~~~~
在下面这个示例中,可以在逻辑上保证不会越界的代码,同样是不会进行越界检查的。
.. code:: go
package main
func f(s []int) {
for index, _ := range s {
_ = s[index]
_ = s[:index+1]
_ = s[index:len(s)]
}
}
func main() {}
3.3 案例三
~~~~~~~~~~
在如下示例代码中,虽然数组的长度和容量可以确定,但是索引是通过
``rand.Intn()``
函数取得的随机数,在编译器看来这个索引值是不确定的,它有可能大于数组的长度,也有可能小于数组的长度。
因此第一次是需要进行检查的,有了第一次检查后,第二次索引从逻辑上就能推断,所以不会再进行边界检查。
.. code:: go
package main
import (
"math/rand"
)
func f() {
s := make([]int, 3, 3)
index := rand.Intn(3)
_ = s[:index] // 第一次检查
_ = s[index:] // 不会检查
}
func main() {}
但如果把上面的代码稍微改一下,让切片的长度和容量变得不一样,结果又会变得不一样了。
.. code:: go
package main
import (
"math/rand"
)
func f() {
s := make([]int, 3, 5)
index := rand.Intn(3)
_ = s[:index] // 第一次检查
_ = s[index:] // 第二次检查
}
func main() {}
我们只有当数组的长度和容量相等时, ``:index`` 成立,才能一定能推出
``index:`` 也成立,这样的话,只要做一次检查即可
一旦数组的长度和容量不相等,那么 index
在编译器看来是有可能大于数组长度的,甚至大于数组的容量。
我们假设 index 取得的随机数为 4,那么它大于数组长度,此时 ``s[:index]``
虽然可以成功,但是 ``s[index:]``
是要失败的,因此第二次边界的检查是有必要的。
你可能会说, index 不是最大值为 3 吗?怎么可能是 4呢?
要知道编译器在编译的时候,并不知道 index 的最大值是 3 呢。
**小结一下**
1. 当数组的长度和容量相等时,\ ``s[:index]`` 成立能够保证 ``s[index:]``
也成立,因为只要检查一次即可
2. 当数组的长度和容量不等时,\ ``s[:index]`` 成立不能保证 ``s[index:]``
也成立,因为要检查两次才可以
3.4 案例四
~~~~~~~~~~
有了上面的铺垫,再来看下面这个示例,由于数组是调用者传入的参数,所以编译器的编译的时候无法得知数组的长度和容量是否相等,因此只能保险一点,两个都检查。
.. code:: go
package main
import (
"math/rand"
)
func f(s []int, index int) {
_ = s[:index] // 第一次检查
_ = s[index:] // 第二次检查
}
func main() {}
但是如果把两个表达式的顺序反过来,就只要做一次检查就行了,原因我就不赘述了。
.. code:: go
package main
import (
"math/rand"
)
func f(s []int, index int) {
_ = s[index:] // 第一次检查
_ = s[:index] // 不用检查
}
func main() {}
5. 主动消除边界检查
-------------------
虽然编译器已经非常努力去消除一些应该消除的边界检查,但难免会有一些遗漏。
这就需要“警民合作”,对于那些编译器还未考虑到的场景,但开发者又极力追求程序的运行效率的,可以使用一些小技巧给出一些暗示,告诉编译器哪些地方可以不用做边界检查。
比如下面这个示例,从代码的逻辑上来说,是完全没有必要做边界检查的,但是编译器并没有那么智能,实际上每个for循环,它都要做一次边界的检查,非常的浪费性能。
.. code:: go
package main
func f(is []int, bs []byte) {
if len(is) >= 256 {
for _, n := range bs {
_ = is[n] // 每个循环都要边界检查
}
}
}
func main() {}
可以试着在 for 循环前加上这么一句 ``is = is[:256]`` 来告诉编译器新 is
的长度为 256,最大索引值为 255,不会超过 byte 的最大值,因为 ``is[n]``
从逻辑上来说是一定不会越界的。
.. code:: go
package main
func f(is []int, bs []byte) {
if len(is) >= 256 {
is = is[:256]
for _, n := range bs {
_ = is[n] // 不需要做边界检查
}
}
}
func main() {}
参考文档
--------
- `边界检查消除 <https://gfw.go101.org/article/bounds-check-elimination.html>`__
|image1|
.. |image0| image:: http://image.iswbm.com/20200607145423.png
.. |image1| image:: http://image.iswbm.com/20200607174235.png
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册