@Otokaze
2018-05-27T14:00:49.000000Z
字数 3750
阅读 837
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
,如下:
package tool
func Hello() string {
return "hello, world!"
}
然后,编译并安装它 go install
(进入 tool 目录)。
此时,你可以找到函数库文件 $GOPATH/pkg/linux_amd64/com/zfl9/tool.a
。
注意观察它们的命名规则,直接将尾目录替换为归档文件,即添加 .a
后缀。
pkg 的直接子文件夹,命名规则为 $GOOS_$GOARCH
,前面是系统,后面是架构。
现在,我们来试试 main 包,一个简单的 helloworld。
因为一个目录下的源文件必须同属一个包,所以我们去别的文件夹。
这里我们使用 $GOPATH/src/com/zfl9/main
文件夹,进入,新建 main.go:
package main
import "fmt"
func main() {
fmt.Println("hello, world!");
}
然后,编译并安装它 go install
(当前目录位于 main 目录)。
此时,我们可以看到 $GOPATH/bin/main
可执行文件,运行它试试!
这里说明一点,生成的可执行文件名称与包所处的文件夹相同,与 *.go
名称无关。
这里说明一下两个常用的编译/安装命令:go build
、go install
。
go build
:编译,如果当前目录的包不是 main,那么运行此命令不会生成任何文件,go 仅仅检测该目录下的 *.go
文件是否可正常编译;如果当前目录的包是 main,那么运行此命令会在当前文件夹下产生一个可执行文件,可执行文件名为当前文件夹的名称。go install
:安装,实际上此命令是 go build
的超集,它会先执行 go build
。如果当前目录的包不是 main,那么运行此命令会在 $GOPATH/pkg/$GOOS_$GOARCH
文件夹下生成对应的 *.a
静态链接库文件;如果当前目录的包是 main,那么运行此命令会在 $GOPATH/bin
文件夹下生成与当前文件夹同名的可执行文件。当然,我们也可以不进入对应的目录,而是直接在任意目录下,运行 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 不知道使用哪个包:
package main
import (
"fmt"
"helloworld"
"hello_world"
)
func main() {
fmt.Println("hello, world!")
fmt.Println(helloworld.Hello())
}
使用 go install
编译,会提示包名冲突,导致编译失败。此时我们可以分别给它们起个别名,就可以避免这个问题了,如下:
package main
import (
"fmt"
a "helloworld"
b "hello_world"
)
func main() {
fmt.Println("hello, world!")
fmt.Println(a.Hello())
fmt.Println(b.Hello())
}
编译后运行,输出如下:
$ main
hello, world!
hello, world! [@dir helloworld]
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
:设置目标系统,如 linux
、darwin
(MAC)、windows
GOARCH=arm
:设置目标架构,如 386
(x86)、amd64
、arm
、arm64
在 shell 中,可以这么使用(临时设置环境变量)
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go install main.go
完整的 GOOS、GOARCH 参考列表:GOOS/GOARCH list