声明:
本博客欢迎转发,但请保留原作者信息!
新浪微博:@Lingxian_kong;
博客地址: http://lingxiankong.github.io/
内容系本人学习、研究和总结,如有雷同,实属荣幸!
Go官方文档: https://golang.org/doc/
查看标准库列表: https://gowalker.org/search?q=gorepos
几个不错的翻译教程:
https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/directory.md
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/01.1.md
写此文时,Go的最新版是1.6beta,我直接在Ubuntu虚拟机中,下载Go的二进制包,并设置我的环境变量。
$ echo "export GOROOT=$HOME/go" >> ~/.bashrc $ echo "export PATH=$PATH:$HOME/go/bin" >> ~/.bashrc $ echo "export GOPATH=$HOME/Applications/Go" >> ~/.bashrc (一般来说,你自己的代码不应该直接放置在GOPATH的src目录下) $ source ~/.bashrc $ wget https://storage.googleapis.com/golang/go1.7.4.linux-amd64.tar.gz (到https://golang.org/dl/查看版本) $ tar xvzf go1.7.4.linux-amd64.tar.gz $ mv go $GOROOT $ apt-get install bison ed gawk gcc libc6-dev make (我执行了这一步,不知道是不是必须的,有个文档说Go 的工具链是用 C 语言编写的,因此在安装 Go 之前你需要先安装相关的 C 工具)
安装完毕,就这么简单。然后就可以编写Go代码了。一个最简单的hello world如下:
package main func main() { println("Hello", "world") }
保存为hello.go文件,命令行运行go run hello.go,就会看到输出。一些教程还会举个稍微高级的例子,打印Go的版本号:
package main import ( "fmt" "runtime" ) func main() { fmt.Printf("%s/n", runtime.Version()) }
很多教程上来就说Go如何好,但我觉得,如果你真有阅读Go代码的需求(比如我是为了阅读Swarm源码),可略过那些章节,直接学习如何写Go语言即可。因为在很熟悉一门编程语言并有与之相关的项目经验前,那些东西除了吹嘘,没有任何实质意义。
如下是我自己看教程的一些笔记,没有过目不忘的本事,只能靠反复看笔记了。
先谈谈我自己的感受,大概扫了一遍Go语言基础教程,发现Go其实融合了Java、C、Objective-C、Python等语言的一些特性,学Go的过程中,脑子里一直有好几个小人在打架,是并行的几个线程自我否定的过程,比较痛苦。但多掌握一门编程语言不是坏事,就算是锻炼自己脑子了。如果你先前没有其他编程语言经验,那么恭喜,对于Go语言你会很快上手的。另外,一门编程语言真正强大的是它的库函数,所以教程中有关库函数的讲解其实也可以忽略,因为你真正要用的时候,最好还是翻阅权威的官方文档(Go自带package文档参阅 这里 ,国内用户可以访问 这里 )。
点比较散。有些东西当熟悉了Go语言之后再回过头来看,可能会比较简单。
Go 把 runtime 嵌入到了每一个可执行文件当中,所以go可执行文件的运行不需要依赖任何其它文件,它只需要一个单独的静态文件即可(所以导致可执行文件比较大)。
如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。
main包中的main函数既没有参数,也没有返回类型(与C家族中的其它语言恰好相反)
每一个源文件都可以包含且只包含一个 init 函数,init 函数是不能被调用的。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性;也经常被用在当一个程序开始之前调用后台执行的 goroutine.
即使用 import _ <package path>
引入一个包,仅仅是为了调用init()函数,而无法通过包名来调用包中的其他函数。
Go语言中的函数命名方式和注释方式类似Java,不推荐使用像Python那样的下划线。
var _ I = T{}
,这个语句其实在测试结构体T是否实现了接口I,下划线忽略变量名
在一个变量上调用 reflect.TypeOf() 可以获取变量的正确类型。
声明数组: var arr1 [5]int
,数组的初始化有很多种方式,数组是值类型。
声明切片: var slice1 []type
,不需要说明长度,初始化: var slice1 []type = arr1[start:end]
,切片是引用类型。切换类似于Python中的list,切片使用make()创建。切片的cap就是数组的长度
声明Map: var map1 map[string]int
,Map是引用类型,用make()创建。判断一个map中是否存在某个key: val1, isPresent = map1[key1]
,在python中如果key1不存在会有KeyError异常。遍历map: for key, value := range map1
函数支持可变参数, func myFunc(a, b, args ...int) {}
,如果是传递数组,可以这样 myFunc(1, 2, arr...)
。如果可变参数中值的类型都不一样,可以使用struct或空接口。
Go支持的代码结构语句:
定义函数时,最好额外返回一个执行成功与否的变量,如下写法会被经常用到:
if value, ok := readData(); ok { … }
for就类似于C语言中的结构了。Go不支持while语句,无限循环可以用for { } 代替。
for-range结构是Go独有的,类似于有些语言的for-each,写法是 for ix, val := range coll { }
,需要注意,val 始终为集合中对应索引的值拷贝,是只读的。
switch无需使用break退出分支,默认行为是匹配到一个分支运行结束后,就退出switch语句。如果希望继续执行,使用fallthrough。
channel轮询
对数组的遍历两种方式:
// 第一种 for i:=0; i < len(arr1); i++{ arr1[i] = ... } // 第二种 for index,value := range arr1{ ... }
recover 只能在 defer 修饰的函数中使用:用于取得 panic 调用中传递过来的错误值,如果是正常执行,调用 recover 会返回 nil,且没有其它效果。
Go支持标签和goto语法,但尽可能少用。
为了保证同一时刻只有一个线程会修改一个struct,通常的做法是在struct的定义中加入sync.Mutex类型的变量。在函数中,可以通过lock和unlock函数加锁和解锁。在 sync 包中还有一个 RWMutex 锁:他能通过 RLock() 来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用 Lock() 将和普通的 Mutex 作用相同。
https://github.com/spf13/cobra CLI程序
https://github.com/spf13/pflag 增强的flag库
https://blog.gopheracademy.com/advent-2014/reading-config-files-the-go-way/ 解析配置文件 https://github.com/go-ini/ini 读写INI文件
使用fmt.Printf, %v
代表使用类型的默认输出格式, %t
表示布尔型, %g
表示浮点型, %p
表示指针。
Python里面的原生字符串(比如 r('xxx')
)在Go中用反引号表示。
在使用接口时, v, ok := varI.(T)
可以在多态下检查一个接口变量是否是某个类型。也可以用 switch t := varI.(type)
对接口变量的类型进行判断。
测试一个变量v是否实现了某个接口: sv, ok := v.(varI)
关于select的介绍: http://yanyiwu.com/work/2014/11/08/golang-select-typical-usage.html
装饰器的效果:
func errorHandler(fn fType1) fType1 { return func(a type1, b type2) { defer func() { if e, ok := recover().(error); ok { log.Printf(“run time panic: %v”, err) } }() fn(a, b) } }