十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
《Go语言学习笔记》(雨痕)电子书网盘下载免费在线阅读
成都创新互联技术团队10多年来致力于为客户提供成都网站设计、做网站、品牌网站制作、全网整合营销推广、搜索引擎SEO优化等服务。经过多年发展,公司拥有经验丰富的技术团队,先后服务、推广了上千家网站,包括各类中小企业、企事单位、高校等机构单位。
链接:
提取码:rta5
书名:Go语言学习笔记
作者:雨痕
豆瓣评分:8.1
出版社:电子工业出版社
出版年份:2016-6
页数:468
内容简介:
作为时下流行的一种系统编程语言,Go 简单易学,性能很好,且支持各类主流平台。已有大量项目采用 Go 编写,这其中就包括 Docker 等明星作品,其开发和执行效率早已被证明。本书经四年多逐步完善,内容覆盖了语言、运行时、性能优化、工具链等各层面知识。且内容经大量读者反馈和校对,没有明显的缺陷和错误。上卷细致解析了语言规范相关细节,便于读者深入理解语言相关功能的使用方法和注意事项。下卷则对运行时源码做出深度剖析,引导读者透彻了解语言功能背后的支持环境和运行体系,诸如内存分配、垃圾回收和并发调度等。本书不适合编程初学入门,可供有实际编程经验或正在使用Go 工作的人群参考。
作者简介:
自 1996 年从事计算机软件开发工作以来,已 20 春秋。期间供职于北大方正、西单电子商务、九城数码、知乎等公司。主要从事核心开发、架构设计,以及部分管理工作。
GO是编译性语言,所以函数的顺序是无关紧要的,为了方便阅读,建议入口函数 main 写在最前面,其余函数按照功能需要进行排列
GO的函数 不支持嵌套,重载和默认参数
GO的函数 支持 无需声明变量,可变长度,多返回值,匿名,闭包等
GO的函数用 func 来声明,且左大括号 { 不能另起一行
一个简单的示例:
输出为:
参数:可以传0个或多个值来供自己用
返回:通过用 return 来进行返回
输出为:
上面就是一个典型的多参数传递与多返回值
对例子的说明:
按值传递:是对某个变量进行复制,不能更改原变量的值
引用传递:相当于按指针传递,可以同时改变原来的值,并且消耗的内存会更少,只有4或8个字节的消耗
在上例中,返回值 (d int, e int, f int) { 是进行了命名,如果不想命名可以写成 (int,int,int){ ,返回的结果都是一样的,但要注意:
当返回了多个值,我们某些变量不想要,或实际用不到,我们可以使用 _ 来补位,例如上例的返回我们可以写成 d,_,f := test(a,b,c) ,我们不想要中间的返回值,可以以这种形式来舍弃掉
在参数后面以 变量 ... type 这种形式的,我们就要以判断出这是一个可变长度的参数
输出为:
在上例中, strs ...string 中, strs 的实际值是b,c,d,e,这就是一个最简单的传递可变长度的参数的例子,更多一些演变的形式,都非常类似
在GO中 defer 关键字非常重要,相当于面相对像中的析构函数,也就是在某个函数执行完成后,GO会自动这个;
如果在多层循环中函数里,都定义了 defer ,那么它的执行顺序是先进后出;
当某个函数出现严重错误时, defer 也会被调用
输出为
这是一个最简单的测试了,当然还有更复杂的调用,比如调试程序时,判断是哪个函数出了问题,完全可以根据 defer 打印出来的内容来进行判断,非常快速,这种留给你们去实现
一个函数在函数体内自己调用自己我们称之为递归函数,在做递归调用时,经常会将内存给占满,这是非常要注意的,常用的比如,快速排序就是用的递归调用
本篇重点介绍了GO函数(func)的声明与使用,下一篇将介绍GO的结构 struct
本文是对 Gopher 2017 中一个非常好的 Talk�: [Understanding Channel](GopherCon 2017: Kavya Joshi - Understanding Channels) 的学习笔记,希望能够通过对 channel 的关键特性的理解,进一步掌握其用法细节以及 Golang 语言设计哲学的管窥蠡测。
channel 是可以让一个 goroutine 发送特定值到另一个 gouroutine 的通信机制。
原生的 channel 是没有缓存的(unbuffered channel),可以用于 goroutine 之间实现同步。
关闭后不能再写入,可以读取直到 channel 中再没有数据,并返回元素类型的零值。
gopl/ch3/netcat3
首先从 channel 是怎么被创建的开始:
在 heap 上分配一个 hchan 类型的对象,并将其初始化,然后返回一个指向这个 hchan 对象的指针。
理解了 channel 的数据结构实现,现在转到 channel 的两个最基本方法: sends 和 receivces ,看一下以上的特性是如何体现在 sends 和 receives 中的:
假设发送方先启动,执行 ch - task0 :
如此为 channel 带来了 goroutine-safe 的特性。
在这样的模型里, sender goroutine - channel - receiver goroutine 之间, hchan 是唯一的共享内存,而这个唯一的共享内存又通过 mutex 来确保 goroutine-safe ,所有在队列中的内容都只是副本。
这便是著名的 golang 并发原则的体现:
发送方 goroutine 会阻塞,暂停,并在收到 receive 后才恢复。
goroutine 是一种 用户态线程 , 由 Go runtime 创建并管理,而不是操作系统,比起操作系统线程来说,goroutine更加轻量。
Go runtime scheduler 负责将 goroutine 调度到操作系统线程上。
runtime scheduler 怎么将 goroutine 调度到操作系统线程上?
当阻塞发生时,一次 goroutine 上下文切换的全过程:
然而,被阻塞的 goroutine 怎么恢复过来?
阻塞发生时,调用 runtime sheduler 执行 gopark 之前,G1 会创建一个 sudog ,并将它存放在 hchan 的 sendq 中。 sudog 中便记录了即将被阻塞的 goroutine G1 ,以及它要发送的数据元素 task4 等等。
接收方 将通过这个 sudog 来恢复 G1
接收方 G2 接收数据, 并发出一个 receivce ,将 G1 置为 runnable :
同样的, 接收方 G2 会被阻塞,G2 会创建 sudoq ,存放在 recvq ,基本过程和发送方阻塞一样。
不同的是,发送方 G1如何恢复接收方 G2,这是一个非常神奇的实现。
理论上可以将 task 入队,然后恢复 G2, 但恢复 G2后,G2会做什么呢?
G2会将队列中的 task 复制出来,放到自己的 memory 中,基于这个思路,G1在这个时候,直接将 task 写到 G2的 stack memory 中!
这是违反常规的操作,理论上 goroutine 之间的 stack 是相互独立的,只有在运行时可以执行这样的操作。
这么做纯粹是出于性能优化的考虑,原来的步骤是:
优化后,相当于减少了 G2 获取锁并且执行 memcopy 的性能消耗。
channel 设计背后的思想可以理解为 simplicity 和 performance 之间权衡抉择,具体如下:
queue with a lock prefered to lock-free implementation:
比起完全 lock-free 的实现,使用锁的队列实现更简单,容易实现
———文章来源 YamiOdymel/PHP-to-Golang
PHP和模块之间的关系令人感到烦躁,假设你要读取 yaml 档案,你需要有一个 yaml 的模块,为此,你还需要将其编译然后将编译后的模块摆放至指定位置,之后换了一台伺服器你还要重新编译,这点到现在还是没有改善;顺带一提之后出了PHP 7效能确实提升了许多(比Python 3快了些),但PHP仍令我感到臃肿,我觉得是时候
(转行)了。
PHP 和Golang 的效能我想毋庸置疑是后者比较快(而且是以倍数来算),也许有的人会认为两种不应该被放在一起比较,但Golang 本身就是偏向Web 开发的,所以这也是为什么我考虑转用Golang 的原因,起初我的考虑有几个:Node.js 和Rust 还有最终被选定的Golang;先谈谈Node.js 吧。
Node.js的效能可以说是快上PHP 3.5倍至6倍左右 ,而且撰写的语言还是JavaScript,蒸蚌,如此一来就不需要学习新语言了!搭配Babel更可以说是万能,不过那跟「跳跳虎」一样的Async逻辑还有那恐怖的Callback Hell,有人认为前者是种优点,这点我不否认,但是对学习PHP的我来说太过于"Mind Fuck",至于后者的Callback Hell虽然有Promise,但是那又是另一个「Then Hell」的故事了。相较于Golang之下,Node.js似乎就没有那么吸引我了。你确实可以用Node.js写出很多东西,不过那V8引擎的效能仍然有限,而且要学习新的事物,不就应该是「全新」的吗;)?
题外话: 为什么Node.js不适合大型和商业专案?
在抛弃改用Node.js 之后我曾经花了一天的时间尝试Rust 和Iron 框架,嗯⋯⋯Rust 太强大了,强大到让我觉得Rust 不应该用在这里,这想法也许很蠢,但Rust 让我觉得适合更应该拿来用在系统或者是部分底层的地方,而不应该是网路服务。
Golang是我最终的选择,主要在于我花了一天的时间来研究的时候意外地发现Golang夭寿简洁( 关键字只有25个 ),相较之下Rust太过于「强大」令我怯步;而且Golang带有许多工具,例如 go fmt 会自动帮你整理程式码、 go doc 会自动帮你生产文件、 go test 可以自动单元测试并生产覆盖率报表、也有 go get 套件管理工具(虽然没有版本功能),不过都很实用,而且也不需要加上分号( ; ),真要说不好的地方⋯⋯大概就是强迫你花括号不能换行放吧(没错,我就是花括号会换行放的人)。
当我在撰写这份文件的时候 我会先假设你有一定的基础 ,你可以先阅读下列的手册,他们都很不错。
你能够在PHP 里面想建立一个变数的时候就直接建立,夭寿赞,是吗?
蒸蚌!那么Golang 呢?在Golang 中变数分为几类:「新定义」、「预先定义」、「自动新定义」、「覆盖」。让我们来看看范例:
在PHP中你会很常用到 echo 来显示文字,像这样。
然而在Golang中你会需要 fmt 套件,关于「什么是套件」的说明你可以在文章下述了解。
这很简单,而且两个语言的用法相差甚少,下面这是PHP:
只是Golang 稍微聒噪了一点,你必须在函式后面宣告他最后会回传什么资料型别。
在PHP 中你要回传多个资料你就会用上阵列,然后将资料放入阵列里面,像这样。
然而在Golang 中你可以不必用到一个阵列,函式可以一次回传多个值:
两个语言的撰写方式不尽相同。
主要是PHP 的阵列能做太多事情了,所以在PHP 里面要储存什么用阵列就好了。
在Golang里⋯⋯没有这么万能的东西,首先要先了解Golang中有这些型态: array , slice , map , interface ,
你他妈的我到底看了三洨,首先你要知道Golang是个强型别语言,意思是你的阵列中 只能有一种型态 ,什么意思?当你决定这个阵列是用来摆放字串资料的时候,你就只能在里面放字串。没有数值、没有布林值,就像你没有女朋友一样。
先撇开PHP 的「万能阵列」不管,Golang 中的阵列既单纯却又十分脑残,在定义一个阵列的时候,你必须给他一个长度还有其内容存放的资料型态,你的阵列内容不一定要填满其长度,但是你的阵列内容不能超过你当初定义的长度。
切片⋯⋯这听起来也许很奇怪,但是你确实可以「切」他,让我们先谈谈「切片」比起「阵列」要好在哪里:「你不用定义其最大长度,而且你可以直接赋予值」,没了。
我们刚才有提到你可以「切」他,记得吗?这有点像是PHP中的 array_slice() ,但是Golang直接让Slice「内建」了这个用法,其用法是: slice[开始:结束] 。
在PHP中倒是没有那么方便,在下列PHP范例中你需要不断地使用 array_slice() 。
你可以把「映照」看成是一个有键名和键值的阵列,但是记住:「你需要事先定义其键名、键值的资料型态」,这仍限制你没办法在映照中存放多种不同型态的资料。
在Golang里可就没这么简单了,你需要先用 make() 宣告 map 。
也许你不喜欢「接口」这个词,但用「介面」我怕会误导大众,所以,是的,接下来我会继续称其为「接口」。还记得你可以在PHP 的关联阵列里面存放任何型态的资料吗,像下面这样?
现在你有福了!正因为Golang中的 interface{} 可以接受任何内容,所以你可以把它拿来存放任何型态的资料。
有时候你也许会有个不定值的变数,在PHP 里你可以直接将一个变数定义成字串、数值、空值、就像你那变心的女友一样随时都在变。
在Golang中你必须给予变数一个指定的资料型别,不过还记得刚才提到的:「Golang中有个 interface{} 能够 存放任何事物 」吗( 虽然也不是真的任何事物啦⋯⋯ )?
当我们程式中不需要继续使用到某个资源或是发生错误的时候,我们索性会将其关闭或是抛弃来节省资源开销,例如PHP 里的读取档案:
在Golang中,你可以使用 defer 来在函式结束的时候自动执行某些程式(其执行方向为反向)。所以你就不需要在函式最后面结束最前面的资源。
defer 可以被称为「推迟执行」,实际上就是在函式结束后会「反序」执行的东西,例如你按照了这样的顺序定义 defer : A-B-C-D ,那么执行的顺序其实会是 D-C-B-A ,这用在程式结束时还蛮有用的,让我们看看Golang如何改善上述范例。
这东西很邪恶,不是吗?又不是在写BASIC,不过也许有时候你会在PHP 用上呢。但是拜托,不要。
Golang中仅有 for 一种回圈但却能够达成 foreach 、 while 、 for 多种用法。普通 for 回圈写法在两个语言中都十分相近。
在Golang请记得:如果你的 i 先前并不存在,那么你就需要定义它,所以下面这个范例你会看见 i := 0 。
在PHP里, foreach() 能够直接给你值和键名,用起来十分简单。
Golang里面虽然仅有 for() 但却可以使用 range 达成和PHP一样的 foreach 方式。
一个 while(条件) 回圈在PHP里面可以不断地执行区块中的程式,直到 条件 为 false 为止。
在Golang里也有相同的做法,但仍是透过 for 回圈,请注意这个 for 回圈并没有任何的分号( ; ),而且一个没有条件的 for 回圈会一直被执行。
PHP中有 do .. while() 回圈可以先做区块中的动作。
在Golang中则没有相关函式,但是你可以透过一个无止尽的 for 回圈加上条件式来让他结束回圈。
要是你真的希望完全符合像是PHP那样的设计方式,或者你可以在Golang中使用很邪恶的 goto 。
在PHP中我们可以透过 date() 像这样取得目前的日期。
在Golang就稍微有趣点了,因为Golang中并不是以 Y-m-d 这种格式做为定义,而是 1 、 2 、 3 ,这令你需要去翻阅文件,才能够知道 1 的定义是代表什么。
俗话说:「爆炸就是艺术」,可爱的PHP用词真的很大胆,像是: explode() (爆炸)、 die() (死掉),回归正传,如果你想在PHP里面将字串切割成阵列,你可以这么做。
简单的就让一个字串给「爆炸」了,那么Golang 呢?
对了,记得引用 strings 套件。
这真的是很常用到的功能,就像物件一样有着键名和键值,在PHP 里面你很简单的就能靠阵列(Array)办到。
真是太棒了,那么Golang呢?用 map 是差不多啦。如果有必要的话,你可以稍微复习一下先前提到的「多资料储存型态-Stores」。
你很常会在PHP里面用 isset() 检查一个索引是否存在,不是吗?
在Golang里面很简单的能够这样办到(仅适用于 map )。
指针(有时也做参照)是一个像是「变数别名」的方法,这种方法让你不用整天覆盖旧的变数,让我们假设 A = 1; B = A; 这个时候 B 会复制一份 A 且两者不相干,倘若你希望修改 B 的时候实际上也会修改到 A 的值,就会需要指针。
指针比起复制一个变数,他会建立一个指向到某个变数的记忆体位置,这也就是为什么你改变指针,实际上是在改变某个变数。
在Golang你需要用上 * 还有 符号。
有些时候你会回传一个阵列,这个阵列里面可能有资料还有错误代号,而你会用条件式判断错误代号是否非空值。
在Golang中函式可以一次回传多个值。为此,你不需要真的回传一个阵列,不过要注意的是你将会回传一个属于 error 资料型态的错误,所以你需要引用 errors 套件来帮助你做这件事。
该注意的是Golang没有 try .. catch ,因为 Golang推荐这种错误处理方式 ,你应该在每一次执行可能会发生错误的程式时就处理错误,而非后来用 try 到处包覆你的程式。
在 if 条件式里宣告变数会让你只能在 if 内部使用这个变数,而不会污染到全域范围。
也许你在PHP中更常用的会是 try .. catch ,在大型商业逻辑时经常看见如此地用法,实际上这种用法令人感到聒噪(因为你会需要一堆 try 区块):
Golang中并没有 try .. catch ,实际上Golang也 不鼓励这种行为 (Golang推荐逐一处理错误的方式),倘若你真想办倒像是捕捉异常这样的方式,你确实可以使用Golang中另类处理错误的方式(可以的话尽量避免使用这种方式): panic() , recover() , defer 。
你可以把 panic() 当作是 throw (丢出错误),而这跟PHP的 exit() 有87%像,一但你执行了 panic() 你的程式就会宣告而终,但是别担心,因为程式结束的时候会呼叫 defer ,所以我们接下来要在 defer 停止 panic() 。
关于 defer 上述已经有提到了,他是一个反向执行的宣告,会在函式结束后被执行,当你呼叫了 panic() 结束程式的时候,也就会开始执行 defer ,所以我们要在 defer 内使用 recover() 让程式不再继续进行结束动作,这就像是捕捉异常。
recover() 可以看作 catch (捕捉),我们要在 defer 里面用 recover() 解决 panic() ,如此一来程式就会回归正常而不会被结束。
还记得在PHP里要引用一堆档案的日子吗?到处可见的 require() 或是 include() ?到了Golang这些都不见了,取而代之的是「套件(Package)」。现在让我们来用PHP解释一下。
这看起来很正常对吧?但假设你有一堆档案,这马上就成了 Include Hell ,让我们看看Golang怎么透过「套件」解决这个问题。
「 蛤???杀小??? 」你可能如此地说道。是的, main.go 中除了引用 fmt 套件( 为了要输出结果用的套件 )之外完全没有引用到 a.go 。
「 蛤???杀小?????? 」你仿佛回到了几秒钟前的自己。
既然没有引用其他档案,为什么 main.go 可以输出 foo 呢?注意到了吗, 两者都是属于 main 套件 ,因此 他们共享同一个区域 ,所以接下来要介绍的是什么叫做「套件」。
套件是每一个 .go 档案都必须声明在Golang原始码中最开端的东西,像下面这样:
这意味着目前的档案是属于 main 套件( 你也可以依照你的喜好命名 ),那么要如何让同个套件之间的函式沟通呢?
接着是Golang;注意!你不需要引用任何档案,因为下列两个档案同属一个套件。
一个由「套件」所掌握的世界,比起PHP的 include() 和 require() 还要好太多了,对吗?
在Golang 中没有引用单独档案的方式,你必须汇入一整个套件,而且你要记住:「一定你汇入了,你就一定要使用它」,像下面这样。
假如你不希望使用你汇入的套件,你只是为了要触发那个套件的 main() 函式而引用的话⋯⋯,那么你可以在前面加上一个底线( _ )。
如果你的套件出现了名称冲突,你可以在套件来源前面给他一个新的名称。
现在你知道可以汇入套件了,那么什么是「汇出」?同个套件内的函式还有共享变数确实可以直接用,但那 并不表示可以给其他套件使用 ,其方法取决于 函式/变数的「开头大小写」 。
是的。 Golang依照一个函式/变数的开头大小写决定这个东西是否可供「汇出」 。
这用在区别函式的时候格外有用,因为小写开头的任何事物都是不供汇出的,反之,大写开头的任何事物都是用来汇出供其他套件使用的。
一开始可能会觉得这是什么奇异的规定,但写久之后,你就能发现比起JavaScript和Python以「底线为开头的命名方式」还要来得更好;比起成天宣告 public 、 private 、 protected 还要来得更快。
在Golang 中没有类别,但有所谓的「建构体(Struct)」和「接口(Interface)」,这就能够满足几乎所有的需求了,这也是为什么我认为Golang 很简洁却又很强大的原因。
让我们先用PHP 建立一个类别,然后看看Golang 怎么解决这个问题。
虽然Golang没有类别,但是「建构体(Struct)」就十分地堪用了,首先你要知道在Golang中「类别」的成员还有方法都是在「类别」外面所定义的,这跟PHP在类别内定义的方式有所不同,在Golang中还有一点,那就是他们没有 public 、 private 、 protected 的种类。
在PHP中,当有一个类别被 new 的时候会自动执行该类别内的建构子( __construct() ),通常你会用这个来初始化一些类别内部的值。
但是在Golang 里因为没有类别,也就没有建构子,不巧的是建构体本身也不带有建构子的特性,这个时候你只能自己在外部建立一个建构用函式。
让我们假设你有两个类别,你会把其中一个类别传入到另一个类别里面使用,废话不多说!先上个PHP 范例(为了简短篇幅我省去了换行)。
在Golang中你也有相同的用法,但是请记得:「 任何东西都是在「类别」外完成建构的 」。
在PHP 中没有相关的范例,这部分会以刚才「嵌入」章节中的Golang 范例作为解说对象。
你可以看见Golang在进行 Foo 嵌入 Bar 的时候,会自动将 Foo 的成员暴露在 Bar 底下,那么假设「双方之间有相同的成员名称」呢?
这个时候被嵌入的成员就会被「遮蔽」,下面是个实际范例,还有你如何解决遮蔽问题:
虽然都是呼叫同一个函式,但是这个函式可以针对不同的资料来源做出不同的举动,这就是多形。你也能够把这看作是:「讯息的意义由接收者定义,而不是传送者」。
目前PHP 中没有真正的「多形」,不过你仍可以做出同样的东西。
嗯⋯⋯那么Golang呢?实际上更简单而且更有条理了,在Golang中有 interface 可以帮忙完成这个工作。
如果你对Interface还不熟悉,可以试着查看「 解释Golang中的Interface到底是什么 」文章。
谢谢你看到这里,可惜这篇文章却没有说出Golang 最重要的卖点:「Goroutine」和「Channel」