Go语言笔记

2016-12-20

本博客所有文章采用的授权方式为 自由转载-非商用-非衍生-保持署名 ,转载请务必注明出处,谢谢。

声明:
本博客欢迎转发,但请保留原作者信息!
新浪微博:@Lingxian_kong;
博客地址:孔令贤的博客;
内容系本人学习、研究和总结,如有雷同,实属荣幸!

安装和试用

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/preface.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-else

定义函数时,最好额外返回一个执行成功与否的变量,如下写法会被经常用到:

if value, ok := readData(); ok {
…
}
  • for/for-range

for就类似于C语言中的结构了。Go不支持while语句,无限循环可以用for { } 代替。
for-range结构是Go独有的,类似于有些语言的for-each,写法是for ix, val := range coll { },需要注意,val 始终为集合中对应索引的值拷贝,是只读的。

  • switch

switch无需使用break退出分支,默认行为是匹配到一个分支运行结束后,就退出switch语句。如果希望继续执行,使用fallthrough。

  • select

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 作用相同。

技巧

使用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)
    }
}

文章评论

comments powered by Disqus


章节列表