Go 语言新手笔记(一)

Go 语言 2009 年面世,几个成功项目 Docker, Kubernetes, etcs, consul, flannel, 还有 Terraform。Go 编译运行快,听说学习也不难。

安装好 Go 后,有两个环境变量很重要, GOPATH 是工作目录,GOROOT 是 Go 的安装目录, 如 /usr/local/go。GOPATH 允许多个目录,用 : 或 ;(Windows 下) 分隔,当 GOPATH 有多个目录,go get 命令的包放在第一个目录下。

$GOPATH 目录中约定的三个子目录
  1. src: 存放 .go, .c, .h, .s 等源码文件,go run, go install 等命令也要在该目录下执行
  2. pkg: 存放编译时生成的中间文件,如 .a
  3. bin: 存放编译后的可执行文件 
Go 编程是声明了不用的全局变量是绝对的罪过,编译这一关过不了。Go 用 var 声明变量,如 var b bool = true。Go 有精确长度的丰富的基本类型,如 int8, int16, int32, int64, unit8, float32(6位精度), float64(15位精度) 等,单纯写成  int, unit 时,长度与系统有关。Go 的字符串用 UTF-8 格式编码的 Unicode 字符,每个字符对应一个 rune 类型,rune 是字符的意思,相当于 int32.
1var c rune = 'a'
2// 相当于 Java 的 int c = 'a'
3// 打印出来都是 97
Go 还有复数类型, complex64(实虚两部分为 float32), compex128, var c complex128=1.0+10i

Go 的类型定义可以在 $GOROOT/src/builtlin/builtin.go 中看到,如
1type byte = unit8
2type rune = int32
3
4const (
5  true = 0 == 0
6  false = 0 != 0
7)
一些 Go 变量声明与赋值的代码
 1package main
 2
 3import "fmt"
 4
 5func main() {
 6  var (
 7    a int
 8    b bool
 9  )
10  fmt.Println(a, b)  // 0 false, Go 的类型也有像 Java 一样的零值,如 0, 空字符串, nil
11  a, b = 3, true
12  fmt.Println(a, b)  // 3 true
13
14  c, d := 'k', 2.1   // 这种简单声明变量 a := 1 的方式只能在函数中
15  fmt.Println(c, d)  // 107 2.1
16
17  e, f := 4, 5
18  e, f = f, e
19  fmt.Println(e, f)  // 5 4
20
21  _, e = 7, 8   // _ 表示丢弃值,比如函数返回多值时
22  fmt.Println(e, f)  // 8 4
23
24  var x = 9
25  fmt.Println(x)  // 9
26}
_ 还有许多避免编译不过的妙用,如  import _"fmt", var _=log.Println 等

Go 语言的数据是值类型,向函数传递数组会产生一份副本,而更新数组数据时,考虑用数组指针。数组的声明是
1var arr = []string{"aa", "bb"}
与通常的语言 string[] 反过来了,中括号提前

Go 的字符串初始为空字符串,var s string 相当于 s := ""

Go 用 const 声明常量,iota 是每次出现加 1 的常量,从 0 开始,const 声明中未赋值的常量将继承上一个的值
 1func main() {
 2  const (
 3    a = iota
 4    b = iota
 5    c
 6    d = 8
 7    e
 8  )
 9
10  fmt.Print(a, b, c, d, e) // 0 1 2 88
11}
iota 在每遇到一个新的常量块时又会重置为 0

Go 净整一些奇怪的单词,像前面的 rune(符文,文字), iota(希腊字母第九个字母:Ι,ι, 微小,一点的意思)

Go 的反引号像是 Python raw 字符串的效果, fmt.Print(`a\nb`) 输出为 a\nb, \n 不被输出为行,像 Python 的 print(r"a\nb") 一样。

Go 运算符很丰富,其他语言中的逻辑,位运算, ++, --,运算后赋值(如 +=, %=, <<=, >>=, &= 等) 都有。还有像 C/C++ 的 & 和 *, 如 &a : 取 a 变量的地址,和 *a: 指针变量。

还有几个特殊的运算符,位清零 &^,  x &^ y   x 上对应 y 位置上是 1 时取零。^ 一元操作时按位取反,二元操作为异或

字符串也是一个字符数组,如 len("abc"), "abc"[1]。拼接字符串,用加号  "aa" + "bb", mt.Sprintf(), strings.Joint([]string{"aa", "bb"}, ",") 或用 bytes.Buffer 来 WriteString("aa"), 或 string.Builder

字符串的处理包 bytes, strings, strconv 和  uncicode。

Go 的 switch...case 语句本身是默认每个 case  后都是 break 了的,除非加个 fallthrough 又能回到 Java 的 switch...case 的入口效果。Go 的 switch...case  也可以是个表达式,有返回值的。switch...case 的 default 可以写在任何位置。switch...case 也可没有表达式,就相当于一个 if/else 语句了, 还可以带个初始化语句,以下三种形式
 1switch bb {
 2case true: f1()
 3fallthrough
 4case false: f2()
 5}
 6
 7switch {
 8case x < y, x < t: f1()
 9case x < z: f2()
10default: f3()
11}
12
13switch x:= f(); {         // x:=f(); 为初始化语句
14case x<0: return -x
15default: return x
16}
17
18switch t:=t.(type) {   // 比较类型
19case bool: f1()
20case int: f2()
21case *int: f3()
22}
Go 针对异步通道操作有专门的 select 语句,select 会监听分支语句中的通道直到变为非阻塞状态,有多个分支可运行,随机选一个;没可运行分,且有 default 就执行 default; 既没可运行也没 default, 继续阻塞,直到出现可运行分支。

for 语句的格式是 for 初始化语句; 条件语句; 修饰语句 {}, 每部分都可省略,如果全省略就是  for ;;{}, 就是 for {} 了,变成无限循环了。

for-range 循环是 Go 特别的,可以迭代集合,能在迭代的时候获得索引和值,或 key(对于 map), 如
1for idx, val := range coll {}<br />
2for key := range amap {}
Go 的 if...else 的条件是不需要用括号的, if 布尔表达式 {} else if 布尔表达式 {} else {}, 表达式中还能用 ; 连接多个,最后的返回值为布尔值,如 if x:=f(); x<y {}

Go 的 break 还能跳到标签位置, 这有助于跳出多重循环。Go 也有 continue, goto, 同样不建议用  goto 语句。continue 和  goto 都能跳到标签位置。

可见性规则,标识符以大写字母开头的才能导出被外部包代码所用,所以工具方法名首字母都是大写。命令规范是公有函数用 Pascal 命名法(首字母大写的即大驼峰),私有函数用小驼峰式命名法。

Go 语句后的分号通常省略。左花括号不能独点一行,否则不能编译。Go 的注释方法与 Java 是一样的,// 或 /* */

Go 组织代码也用包,但不必像 Java 那样严格的与目录名对应。Go 的每个 .go 文件都必须属于某个用,用 package 声明,同一目录下的多个 .go 文件只能属于同一个包,同一包中的 .go 文件定义的全局变量和公有函数名(首字母大写的)不能冲突。

Go 程序入口必须是 main 包,并且还要 main() 入口函数。每个包可有 init() 函数,init() 函数的签名与 main() 一样,无参数无返回值,如
1func init() {
2  fmt.Println("init main package")
3}
在包被导入式首先被执行,一个包可有多个 init() 函数,如
 1package main
 2
 3import "fmt"
 4
 5func init() {
 6  fmt.Println("1 init main package")
 7}
 8
 9func init() {
10  fmt.Println("2 init main package")
11}
12
13func main() {
14  fmt.Println("hello world")
15}
居然是合法的,两个 init() 函数在导入式都会被执行,顺序不是确定的

Go 导入(import) 包是使用目录名作为包的路径而非 package 声明的包名,实际应用中还是应像 Java 那样尽量保持包名与目录包一致。import "match/big" 会导入 $GOROOT 目录下的 src/match/big 目录,使用 big.Int 时的 big 才是 .go 文件中定义的包名

几种特殊的 import 方式

  1. import  ."fmt": 使用时可省略包名,如 Println(),而不用 fmt.Println(), 像 Java 的静态引入
  2. import f"fmt":  别名引入,f.Println()
  3. import _"fmt": _ 操作引入包只为了调用包的 init()  函数 

Go 语言的标准库在 $GOROOT/src 中,如 Mac OS X 下的 /usr/local/Cellar/go/1.16.6/libexec/src

如果想安装  GitHub 上的项目到本地,可执行
go get -u github.com/ffhelicopter/tmm
然后在代码中就可以像下面那样导入了
import "github.com/ffhelicopter/tmm"
go get 下载到的源文件会放到 ~/go/src 目录中,编译生成的 .a 二进制放到 ~/go/pkg 目录中,如果有 main, 生成的二进制执行文件放到  ~/go/src 目录中

如果有指定了 GOPATH 环境变量, go get 下载和编译生成的就会放到 $GOPATH 对应的 src, pkg, 和 bin 目录中

Go 的包依赖管理工具有 glide, godep, govendor, GoModules。

go install github.com/gocolly/colly@latest 和  go get .... 差不多的功能, 区别好像是 go install 不克隆源代码,还有一个  go build

Go 项目的基本结构是 $GOPATH/{src,pkg,bin}, 有 main.main 函数可用
go install src/test.go
会成 $GOPATH/bin 目录中生成可执行的 test 文件

godoc 命令已经从 go 1.1.13 中移除,要安装它的话可用命令 
go get golang.org/x/tools/cmd/godoc
安装,然后运行
bin/godoc -http=localhost:6060
启动 godoc web 服务,就能在浏览器中打开 http://localhost:6060

Go 编译构建相关的命令有 go run, go build, 和 go install. 如果发布的编译文件不希望别人看到源代码,用下面的参数构建
go build -ldflags "-w -s" src/test.go
Go 自 1.11 新增了对模块的支持, 通过 GO111MODULE=auto|on|off 来控制关闭与开启, go env 可查看所有 Go 环境变量。
  1. GO111MODULE=off 时,从 $GOPATH 和 vender 目录中寻找包
  2. GO111MODULE=on 时,忽略 $GOPATh 和 vender 目录,根据 go.mod 下载依赖
  3. GO111MODULE=auto 时,判断是否有 $GOPATH/go.mod 文件来开启与否
Go 的环境变量可用 go env -w GO111MODULE=auto 设置

使用 go mod 系列命令
$ go env -w GO111MODULE=auto
$ go mod init hello
$ unset GOPATH
$ go mod tidy
$ ...
Go 模块的使用仍然像是 Java 的模块一样糊涂,先不管它了。 永久链接 https://yanbin.blog/go-language-notes-1/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。