在〈来个 Hello, World〉中,你已经看到 Go 开发中,一个 workspace 的基本样貌,你可以看到,里头会有 src、pkg、bin 目录,你会设置GOPATH
环境变量指向这个目录,这些都是规范好的,也是强制的,正如〈How to Write Go Code〉中说到的:
The go tool is designed to work with open source code maintained in public repositories. Although you don't need to publish your code, the model for how the environment is set up works the same whether you do or not.
在〈来个 Hello, World〉已经稍微了解了package
与GOPATH
的关系,源码会是在GOPATH
中设定的目录之 src 中,并有着对照于package
设定名称之目录包括着它,当 Go 的工具(go build
、go install
等)需要源码时,会到GOROOT
底下,或者是GOPATH
底下,查看是否有相应于包的源码存在,编译出来的结果,会是在相对应的 pkg 或 bin 底下。
本地包
在当时,为了简化说明,源码文件名故意与package
设定的名称同名,这不是必要的,一个相应于package
的目录底下,可以有许多个源码,而每个源码开头,只要package
设定的名称都与目录相符就可以了。例如,你可以有个源码是 hello.go,位于 src/goexample 底下:
package goexample
import "fmt"
func Hello() {
fmt.Println("Hello")
}
还可以有个 hi.go,位于 src/goexample 底下:
package goexample
import "fmt"
func Hi() {
fmt.Println("Hi")
}
也就是说,一个package
可以有数个源码文件,各自组织自己的任务,在执行go install goexample
之后,上面两个源码会在 pkg 目录的$GOOS
_$GOARCH
目录中产生 goexample.a 文件。这包括了goexample
包编译后的结果,如果想使用goexample
包的功能,只需要编写个 main.go:
package main
import "goexample"
func main() {
goexample.Hi()
goexample.Hello()
}
你可以在包目录之前增加父目录,例如,可以创建一个 src/cc/openhome 目录,然后将方才的 hello.go 与 hi.go 移至该目录之中,接着执行go install cc/openhome/goexample
,那么,在 pkg 目录的 $GOOS_$GOARCH 目录中,会产生对应的 cc/openhome 目录,其中放置着 goexample.a 文件,想要使用这个包的话,可以编写个 main.go:
package main
import "cc/openhome/goexample"
func main() {
goexample.Hi()
goexample.Hello()
}
远端包
由于 Go 的 workspace 设置,都必须是如此规范,因此,若你想将源码发布给他人使用时就很方便,例如,你可以创建 src/github.com/JustinSDK 目录,然后将方才的 goexample 目录移到 src/github.com/JustinSDK 当中,这么一来,显然地,你的 main.go 就要改成:
package main
import "github.com/JustinSDK/goexample"
func main() {
goexample.Hi()
goexample.Hello()
}
也就是说,你可以直接将 /src/github.com/JustinSDK/goexample 当作文件库(repository)发布到 Github,那么,其他人需要你的源码时,有个很方便的go get
指令可以用,我将这个范例发布在 Github 的JustinSDK/goexample了,因此,你可以执行以下指令:
go get github.com/JustinSDK/goexample
go get
会自行判断该使用的协定,以这边的例子来说,就会使用git
来复制文件库至 src 目录底下,结果就是 src/github.com/JustinSDK 底下,会有个 goexample 目录,其中就是源码,go get
在下载源码之后,就会开始进行编译,因此,你也会在 pkg 目录中的 $GOOS_$GOARCH 目录底下,github.com/JustinSDK 中找到编译好的 .a 文件。
接着,你就可以如上头的程序编写import "github.com/JustinSDK/goexample"
来使用这个包。
当然,执行go install main
的话,你的 pkg 目录中的$GOOS_$GOARCH
目录,会有个 github.com/JustinSDK 目录,里头放置着 goexample.a 文件,而编译出来的可执行文件,则会放置在 bin 目录之中,此时,你的目录应该会像是:
go-exercise
├─bin
│ main.exe
│
├─pkg
│ └─windows_amd64
│ └─github.com
│ └─JustinSDK
│ goexample.a
│
└─src
├─github.com
│ └─JustinSDK
│ └─goexample
│ .gitignore
│ hello.go
│ hi.go
│ LICENSE
│ README.md
│
└─main
main.go
GOPATH 中多个路径
如果你在GOPATH
中设定多个路径,那么,在哪个路径底下的 src 找到包的源码,编译出来的 .a 文件就会放在哪个路径底下的 pkg 目录之中。
如果是包括程序进入点的main
包,那么执行go install main
的话,默认会放在找到main
包源码的 bin 目录之中。你可以设定GOBIN
,指定编译出来的可执行文件放置的目录。
如果你在GOPATH
中设定多个路径,那么,go get
复制回来的源码,会被放置在GOPATH
中设置的第一个目录 src 之中,同理,对应的 .a 文件,也会是GOPATH
中设置的第一个目录的 pkg 之中。
有关 import
在import
时默认会使用包名称作为调用包中函数等的前置名称,你可以在import
时指定别名。例如:
package main
import f "fmt"
func main() {
f.Println("哈啰!世界!")
}
若指定别名时使用.
,就不需要包名称作为前置名称,例如:
package main
import . "fmt"
func main() {
Println("哈啰!世界!")
}
你不能只是import x "x"
来试图只执行包的初始化函数,因为 Go 编译器不允许import
了某个包而不使用,然而若指定别名时使用_
,则不会导入包,只会执行包的初始化函数,也就是包中使用func init()
定义的函数。
每个包可以有多个init
定义在各个不同的原始文件中,包被import
时会执行,若是main
包,则会在所有init
函数执行完毕后,再执行main
函数,Go 执行包初始化化时,不会保证包中多个init
的执行顺序。