Yifei Kong

May 02, 2018

Go 语言初体验

安装

安装 go 很简单,在 mac 上直接使用 brew install go 就可以了。注意的是需要设定GOPATH这个环境变量,这里设定到了 ~/.go 这个目录。

export GOPATH=$HOME/.go

语法和风格

Golang的整个语法还是极其简单的,基本是 C 和 Python 的混合体。Go 语言详尽地规定了代码的风格,所以就不用为了大括号究竟是放在哪儿而开始圣战了。Go 语言对程序员的约束很强,不容易犯错

Go 语言官方提供了一个工具 goimports 用来管理 import 语句,还不错。

比如import进来的包必须使用,声明的变量也必须使用。import 语句必须在 package 语句后面.虽然有大括号,但是大括号的位置也是指定的。

类型

几乎所有的值提供了默认都会初始化到对应的零值,即使没有初始化,不少函数处理 nil 的时候也是按照零值处理,这样避免了好多无谓的异常抛出。在go中也不会遇到len(None)这种问题,即使len(nil)也会返回0。

值得一提的是 Go 中的字符串,实际上是一个 byte 的只读数组,如果使用索引访问的话,是按照 byte 为单位来访问的。但是在打印和 range 的时候会直接按照 utf-8 解码输出。如果需要按照 rune(Go 语言对 unicode code point 的称呼)来遍历,需要使用 unicode/utf8 这个包中的函数。

即使字符串不是 utf-8的,或者不管怎样用错了编码,至少不会panic,而python中时不时就会抛出UnicodeDecodeError 。

不用思考蛋疼的 Unicode 问题,不过虽然 Go 的string是 utf-8 的,但是使用下标访问的是字节,而使用 for range 访问的又是 rune

复合类型

像是 C 语言一样,Go 中也有定长数组,var a [3]int

在 Go 中经常作为变长数组使用的是 Slice。Slice 指的是一个数组的一个切片。在 Go 语言中,默认的函数调用都是值传递的,但是 Slice 做参数的时候传递的是一个 Slice Header,也就相当于按照引用传递。

Map 类型是引用类型,也就是函数调用的时候是按照引用传递的。nil map 可以像空map一样使用,但是插入的时候会 panic,因为没有给他分配内存,所以 map 类型一般使用 make 初始化。

m := make(map[string]int)

循环

go 还从 C 中 继承了 if (p = fopen("xxx", "w")) != NULL 这种在把赋值语句写在if中的写法,不过好在 Go 语言中可以写做两句。

if err := r.ParseForm(); err != nil {
      //...
}

另外 switch 语句默认就会 break 了,而不是 fall through 了。

循环语句很有意思,Go 语言直接把 while 关键字扔掉了,只用 for 本身就够了

for {
// ...
}

就相当于其他语言中的 while(true) 了。

不管是 Python 中的 for...in... 还是 JavaScript 中的 for...of... 语句,迭代数组和字典的时候多少感到一些不一致。在 Go 语言中,还是比较统一的,每次迭代都会返回两个值:key, value。

迭代 slice

words := []string{"a", "b"}
for i, word := range words {
    fmt.Printf("%d -> %s", i, word)
}

迭代 map

words := make(map[string]string)
words["a"] = "a"
words["b"] = "b"
for k, v := range words {
    fmt.Printf("%s -> %s", k, v)
}

goroutine

goroutine 相对于 Python 的 coroutine 的好处就在于它是抢占的,不用主动交出。

Python 的 coroutine 需要特别小心不要调用阻塞性的函数,比如 time.sleep,而要使用asyncio.sleep,所以写起来不是非常得方便。Python必须使用 await 来显式交出控制,而 Go 中则没有这种限制。

Timers and tickers

Timers 定义在你在未来的某个时间想要去做一次某件事。而 Tickers 则是定期执行某一个动作。这两个有点像是 js 里面的 setTimeout 和 setInterval 两个函数。

defer 实际上就相当于 C++ 中的RAII,和Python中的 with 语句

Go 的类型总体来说,和 Python 的 duck type 有点像,而和 Java 严格的继承则是完全背道而驰的。

发生赋值时候会不会检查类型呢?

interface{}

io

一般读取统一从 io.Reader 类型中读取

记得一定要使用append函数,而不要直接在slice的结尾通过下标添加字符,这样可能会panic

函数应该接受 interface 作为参数,并使用 struct 作为返回值。

目前为止有几个不爽的地方

  1. nil 字典不能直接赋值,但是 nil slice 可以 append
  2. interface 的 nil 始终没有搞明白。empty slice(a[0:0]) 和 nil 也不一样
  3. 没有一个统一的包管理工具,刚刚花一下午时间学习了 dep,号称是官方的试验,结果又看到一篇文章说 vgo 要取代 dep,WTF
  4. defer 执行的地方是函数的结尾,而不是块的结尾

参考:

https://stackoverflow.com/questions/18058164/is-a-go-goroutine-a-coroutine