1. 工作空间
2. 导入包
3. 组织结构
4. 依赖管理
- 工作空间
工作空间(workspace)是一个由src,bin,pkg三个目录组成的目录,通常情况下需要把该路径加入到GOPATH环境变量中, 以便相关工具能够正常工作。
- src:工程源代码存放路径
- bin:可执行文件的安装路径
- pkg:包安装路径,按照操作系统和平台进行隔离
比如:
workspace/
├── bin
│ └── server
├── pkg
│ └── linux_amd64
│ └── service.a
└── src
├── server
│ ├── main.go
└── service
└── utils.go
在工作空间里,bin和pkg目录主要是受go install/get的命令影响,它们会把 编译结果(可执行文件/静态库)安装到这两个目录下,以实现增量编译。
- 环境变量:
GOPATH:编译器等相关工具会按照GOPATH环境变量中设置的路径进行目标搜索,比如在 导入目标库时,排在GOPAH环境变量列表前面的路径比当前工作空间优先级高,go get将下载的第三方包保存到GOPAH环境变量列表中第一个工作空间内。 GOROOT:用于指定工具链以及标准库的存放位置。
- 导入包 在使用标注库和第三方包之前,首先需要使用import导入,参数是以src目录为 始的绝对路径。编译器会先从标准库路径开始搜索,然后对GOPATH环境变量列表中的 各个路径进行搜索,比如:
import "path/filepath" //实际路径为/root/data/go/src/path/filepath/
为了避免引入的包名称冲突,可以使用包别名进行区分,比如:
import(
"net/http"
"text/template"
"html/template" //template redeclared as imported package name
)
import(
"net/http"
"text/template"
htemplate "html/template" //alias
)
总结四种包导入方式:
import "github.com/sanrenxing/service" //默认方式
import S "github.com/sanrenxing/service" //别名方式
import . "github.com/sanrenxing/service" //简便方式
import _ "github.com/sanrenxing/service" //初始化方式,不引用,只用于初始化目标包
- 相对路径:
在开发过程中,也可以使用相对路径进行包的引用,这并不影响进行项目的编译、运行和测试, 如果项目不在工作空间里的话,会影响执行go install安装。 反而如果在设置了GOPATH的工作空间中,相对路径将会导致编译失败。
- 组织结构
包(package) 是组织结构中最小单位,它是由一个或多个保存在同一个目录下的(不含子目录)的 源码文件组成。包的用法类似名字空间(namespace),它也是成员做用域和访问权限的边界。 比如:
#src/mycluster
package cluster
type ClusterInfo struct{}
func NewCluster() *ClusterInfo{}
相同目录下所有源码文件必须使用相同的包名称,并且包名与目录名并无关系,不要求保持一致因为导入时使用绝对路径,所以在搜索路径下,包 必须有唯一路径,但无须是唯一的名字,比如:
# go list net/...
net
net/http
net/http/cgi
net/http/cookiejar
net/http/fcgi
net/http/httptest
net/http/httptrace
net/http/httputil
net/http/internal
net/http/pprof
net/internal/socktest
- 权限
所有成员在相同包内都可以相互访问,无论是否在同一个源码文件中。
首写字母为大写的全局变量、常量、类型、结构体字段、函数和方法等,可以在包外访问。
不过可以使用指针绕开限制,比如:
/src/meta/data.go
package meta
type data struct{
x int
Y int
}
func New()*data{
return new(data)
}
---------------------------------------------
func main(){
m := meta.New()
m.Y = 100
p := (*struct{x int})(unsafe.Pointer(m))
p.x = 200
fmt.Printf("%+v\n",*m)
}
- 初始化(init)
源码包内每个源码文件都可以定义一个或多个init初始化函数,所有这些初始化函数以及标准库和导入第三方包都由编译器 自动生成的一个包装函数进行调用,因此可保证在单一线程上执行,且只执行一次, 但是编译器不能保证这些初始化函数的执行顺序。所以初始化函数中不应该有逻辑关联,最好只处理 当前文件的初始化操作。
编译器执行次序:首先确保所有全局变量初始化,然后执行初始化函数,最后进入main.main入口函数。
讨论如下代码执行情况:
//main.go
func init(){
println("main")
}
func main(){
init()
}
- 内部包(internal)
内部包机制相当于增加了新的访问控制权限,比如,所有保存在internal目录下的包,只能 被其父目录下的包访问。比如:
service/
├── internal
│ ├── a
│ │ └── a.go
│ └── b
│ └── b.go
└── utils.go
----------------------------------------------
/service/utils.go
package service
import "service/internal/a"
import "service/internal/b"
func SayHello(){
println("i am service")
a.A()
b.B()
}
-----------------------------------------------
/server/main.go
package main
import "service"
import "service/internal/a"
func main(){
service.SayHello()
a.A()
}
编译时输出:
imports service/internal/a: use of internal package not allowed
- 依赖管理
在项目中常常会引入第三方依赖包,而第三方包都放到一个独立的工作空间中,那么就有可能导致 版本冲突问题,但如果都放到项目目录中,久而久之,又可能会把项目目录搞得很乱,为此,Go 引入名为vendor的机制,专门存放和管理第三方包,实现将源码与依赖整体打包分发。
# cat server/vendor/github.com/sanrenxing/test/test.go
package test
func Test(){
println("test")
}
----------------------------------
# cat server/main.go
package main
import "service"
import "github.com/sanrenxing/test"
func main(){
service.SayHello()
test.Test()
}
在main.go中引入”github.com/sanrenxing/test”时,优先搜索vendor目录。
如果引入的第三方包中也带有vendor目录,那么编译器该如何搜索包呢?
server/
├── main.go
├── server
└── vendor
├── github.com
│ └── sanrenxing
│ └── test
│ ├── test.go
│ └── vendor
│ └── github.com
├── p
│ └── p.go
└── q
├── test.go
└── vendor
└── p
└── p.go
vendor/p/p.go
package p
func PP(){
println("pp")
}
------------------------
vendor/q/vendor/p/p.go
package p
func PP(){
println("PPP")
}
------------------------
main.go
package main
import "q"
import "p"
func main(){
p.PP()
q.QQ()
}
在main.go和test.go中分别import “p”, 则程序运行结果会怎样?