[关闭]
@Otokaze 2018-05-27T06:00:49.000000Z 字数 3750 阅读 434

golang 笔记

golang

入门

Go 和 Java 一样,使用 package 来组织代码。

Java 的组织方式
使用环境变量 CLASSPATH,这个变量存储一个或多个文件夹路径(或者 jar 文件,jar 文件当作文件夹使用,会自动解包),多个文件夹或 jar 之间使用冒号隔开。假设,现有一包,com.zfl9.tool,那么它对应的存储路径就是 $CLASSPATH/com/zfl9/tool/。Java 会在 CLASSPATH 目录列表中搜索,直到找到此文件夹为止,如果遍历完了还没找到就报错,提示找不到包。com.zfl9.tool 包下有一个类 HttpServer,那么这个类实际对应的路径就是:$CLASSPATH/com/zfl9/tool/HttpServer.class。每个 class 文件都是一个 Java 类,如果一个 class 文件能够运行,那么它必须包含 main() 静态方法。它是程序的入口函数。每个 *.java 文件中,必须以 package com.zfl9.tool; 开头(忽略注释),这个包名必须与所处的路径对应(将斜杠换为英文句号),如果不一致,那么无法正常使用这个类。而对于 HttpServer 类来说,他有一个唯一的类名,叫做全限定类名,即 com.zfl9.tool.HttpServer。我们在别处想使用该类时,可以直接使用全限定类名来引用它,如果感觉太长,也可以先 import 这个类。如 import com.zfl9.tool.HttpServer;,然后我们就可以直接使用 HttpServer 这个名称来引用它了,当然 import 后也是可以用全限定类名来引用的。也可以使用通配符,一次性引入某个包下的所有类,import com.zfl9.tool.*; 即可。

Golang 的组织方式
先来了解 Go 的两个环境变量:GOROOT、GOPATH。
GOROOT 指定 Go 的安装目录,比如 /usr/local/golang。
GOPATH 指定 Go 的工作目录,比如 ~/golang(可以有多个)。

GOROOT 和 GOPATH 下都有 3 个子文件夹,如下:
bin:二进制可执行文件
pkg:静态库文件(*.a
src:Go 源码文件(*.go

还是以 com/zfl9/tool 包为例(注意是斜杠,与 Java 不同),显然,我们需要进入 $GOPATH/src 目录($GOROOT/src 也是可以的,不过不建议在标准库中搞事)。然后我们新建文件夹 mkdir -p com/zfl9/tool,目前来看和 Java 是差不多的,然后进入 tool 目录。

然后,我们新建一个 go 文件,hello.go,如下:

  1. package tool
  2. func Hello() string {
  3. return "hello, world!"
  4. }

然后,编译并安装它 go install(进入 tool 目录)。
此时,你可以找到函数库文件 $GOPATH/pkg/linux_amd64/com/zfl9/tool.a
注意观察它们的命名规则,直接将尾目录替换为归档文件,即添加 .a 后缀。
pkg 的直接子文件夹,命名规则为 $GOOS_$GOARCH,前面是系统,后面是架构。

现在,我们来试试 main 包,一个简单的 helloworld。
因为一个目录下的源文件必须同属一个包,所以我们去别的文件夹。
这里我们使用 $GOPATH/src/com/zfl9/main 文件夹,进入,新建 main.go:

  1. package main
  2. import "fmt"
  3. func main() {
  4. fmt.Println("hello, world!");
  5. }

然后,编译并安装它 go install(当前目录位于 main 目录)。
此时,我们可以看到 $GOPATH/bin/main 可执行文件,运行它试试!
这里说明一点,生成的可执行文件名称与包所处的文件夹相同,与 *.go 名称无关。

这里说明一下两个常用的编译/安装命令:go buildgo install

当然,我们也可以不进入对应的目录,而是直接在任意目录下,运行 go build/install [import-path]import-path 就是导入路径,就是 import 后面的参数,也就是一个源码文件夹。导入路径只能是相对路径,可以相对于 $GOROOT/src$GOPATH/src,也可以相对于当前目录,上级目录(...),但不能是绝对路径。

*.go 源文件中的 import 语句只能使用相对于 $GOROOT/src$GOPATH/src 目录,不能使用其他路径,比如 "fmt""helloworld"

注意,go 在编译时,如果发现源文件中有 import 导包语句,那么 go 只会去 $GOROOT/src$GOPATH/src 目录下找到对应文件夹,然后编译成 *.a 文件(但是不会保存到 pkg 目录下),然后链接它们,最后生成可执行文件。这点也比较重要。

Go 编译产生的临时文件均保存到 $GOCACHE 目录,默认为 ~/.cache/go-build

这里说明一点,go 中的 import 后面的参数是文件夹路径,和包名没有半点关系。比如 import aa/bb/cc,仅仅是将 $GOPATH/src/aa/bb/cc$GOROOT/src/aa/bb/cc 目录下的包导入进来,这个包的包名可以是其他任意合法标识符,比如 tool。我们在使用这个包时,只需使用 tool.Xxx() 即可。而不是使用 cc.Xxx(),只不过,一般情况下,我们都使用与末端文件夹同名的包名而已。记住了,这点很重要。

那么,如果我有两个文件夹,helloworld、hello_world。并且,其中的包名都使用 helloworld,会怎样?那就是命名冲突了,比如,我在某个 main 包中导入它们,编译就会报错,因为 go 不知道使用哪个包:

  1. package main
  2. import (
  3. "fmt"
  4. "helloworld"
  5. "hello_world"
  6. )
  7. func main() {
  8. fmt.Println("hello, world!")
  9. fmt.Println(helloworld.Hello())
  10. }

使用 go install 编译,会提示包名冲突,导致编译失败。此时我们可以分别给它们起个别名,就可以避免这个问题了,如下:

  1. package main
  2. import (
  3. "fmt"
  4. a "helloworld"
  5. b "hello_world"
  6. )
  7. func main() {
  8. fmt.Println("hello, world!")
  9. fmt.Println(a.Hello())
  10. fmt.Println(b.Hello())
  11. }

编译后运行,输出如下:

  1. $ main
  2. hello, world!
  3. hello, world! [@dir helloworld]
  4. hello, world! [@dir hello_world]

进阶

Go 可执行文件瘦身
Go 编译的可执行文件一般都是静态链接的,而且每个 Go 可执行文件都自带了一个 runtime(Go 运行时,负责内存分配,垃圾回收,反射等工作,和 JVM 概念类似),因为这两个原因,Go 编译生成的可执行文件比 C/C++ 编译的可执行文件大得多(即使是静态编译也比 Go 编译的小得多)。

那要如何瘦身呢?使用 go build -ldflags "-s -w" 就可以了,-ldflags 是传递给链接器的参数,-s 删除符号信息,-w 删除调试信息(注意:仅适用于可执行文件)。

强制设置链接方式
动态链接-ldflags "-linkmode=external"(不建议,可能出错)
静态链接export CGO_ENABLED=0(设置环境变量,禁用 CGO)

Go 交叉编译
不同于 C/C++ 交叉编译,Go 自带了绝大多数平台的交叉编译工具链,交叉编译的步骤也很简单,和 C/C++ 比起来简直是太容易了。那么我们该如何进行交叉编译呢?

进行交叉编译只需要设置 3 个环境变量,分别是:
CGO_ENABLED=0:禁用 CGO(CGO 可以用来调用 C/C++ 函数库)
GOOS=linux:设置目标系统,如 linuxdarwin(MAC)、windows
GOARCH=arm:设置目标架构,如 386(x86)、amd64armarm64

在 shell 中,可以这么使用(临时设置环境变量)

  1. CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
  2. CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go install main.go

完整的 GOOS、GOARCH 参考列表:GOOS/GOARCH list

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注