go语言学习(第九章,包结构)(go 语言学习笔记)

9.1 工作空间

依照规范,工作空间由src、bin、pkg三个目录组成。通常需要将空间路径添加到GOPATH环境变量列表中,以便相关工具能正常工作。
在这里插入图片描述
在空间目录中,包括子包在内的所有源码文件都保存在src目录下。至于bin、pkg两个目录,其主要影响go install/get 命令,它们会将编译结果(可执行文件或静态库)安装到这两个目录下,以实现增量编译。

环境变量

编译器等相关工具按GOPATH设置的路径搜索目标。也就是说在导入目标库时,排在列表前面的路径会比当前工作空间优先级别高。另外,go get默认将下载的第三方包保存到列表的第一个工作空间内。
正因为搜索优先级和默认下载位置等原因,社区对于是否为每个项目单独设置环境变量,还是将所有项目组织到同一个工作空间内存在争议。
环境变量GOROOT用于指示工具链和标准库的存放位置。在生成工具链时,相关路径就已嵌入到可执行文件中,故无需额外设置。但如果出现类似下面的错误提示,请检查路径是否一致。

在这里插入图片描述
除通过设置GOROOT环境变量覆盖内部路径外,还可移动目录(改名、符号链接等),
或重新编译工具链来解决。
至于GOBIN,则是强制代替工作空间的bin目录,作为go install 目标保存路径。这可避免将所有工作空间的bin路径添加到PATH环境变量中。

9.2 导入包

使用标准库或第三方包前,须import导入,参数是工作空间中以src为起始的绝对路径。编译器从标准库开始搜索,然后依次搜索GOPATH列表中的各个工作空间。

import “net/http”

除使用默认包名外,还可使用别名,以解决同名冲突问题。

import  osx “github.com/apple/osx/lib”

注意:import导入参数是路径,而非包名。尽管习惯将包和目录名保持一致,但这不是强制规范。在代码中引用包成员时,使用包名而非目录名。
归纳起来,有四种不同的导入方式:

import	 "github.com/quhen/test"		默认方式:test.A
import X   "github.com/quhen/test"		别名方式:X.A
import .  "github.com/quhen/test"		简便方式:A
import _  "github.com/quhen/test"		初始化方式:无法引用,仅用来初始化包(执行保内init函数)

简便方式常用于单元测试代码中,不推荐在正式项目代码中使用。另外,初始化方式仅是为了让目标包的初始化函数得以执行,而非引用其成员。
不能直接或间接导入自己,不支持任何形式的循环导入
未使用的导入(不包括初始化的方式)会被编译器视为错误

相对路径

除工作空间和绝对路径外,部分工具支持相对路径。可在非工作目录下,直接运行、编译一些代码。
相对路径:当前目录,或以“./”,"…/"开头

9.3 组织结构

包由一个或多个保存在同一目录下(不含子目录)的源码文件组成。包的用途类似名字空间,是成员作用域与访问权限的边界。
包名与目录名无关,不要求保持一致
src/myservice

package service 

包名通常使用单数形式。
源码文件必须使用UTF-8格式,否则会导致编译错误
同一目录下所有源码文件必须使用相同包名称。因为导入时使用绝对路径,所以在搜索路径下,包必须有唯一路径,但无需是唯一名字。

  • main:可执行入口
  • all:标准库及GOPATH中能找到的所有包
  • std,cmd:标准库及工具链
  • documentation:存储文档信息,无法导入(和目录名无关)
权限

所有成员在包内均可访问,无论是否在同一源码文件中。但只有首字母大写的为可导出成员,在包外可视
该规则适用于全局变量、全局常量、类型、结构字段、函数、方法等。
可通过指针转换等方式绕过该限制。
lib/lib.go

package lib

type data struct{
	x int
	Y int
}


func NewData() *data{
	return new(data)
}

main.go

package main

import (
	"fmt"
	"unsafe"

	"../lib"
)

func main() {
	data := lib.NewData()
	data.Y = 200
	p := (*struct{ x int })(unsafe.Pointer(data))
	p.x = 100
	fmt.Printf("%+v\n", *data)
}

初始化

包内每个源文件都可以定义一个到多个初始化函数,但编译器不保证执行次序。
实际上,所有这些初始化函数(包括标准库和导入的第三方包)都由编译器自动生成的一个包装函数进行调用,因此可保证在单一线程上执行,且仅执行一次
从当前版本实现来看,执行次序与依赖关系、文件名和定义次序有关。只是这种次序非常不便于维护,容易引起混乱,应杜绝任何与此有关的逻辑。
初始化函数之间不应有逻辑关系,最好仅处理当前文件的初始化操作。
编译器首先确保完成所有全局变量初始化,然后才开始执行初始化函数。直到这些全部结束后,运行时才正式进入main.main入口函数。

var x = 100

func init(){
	println(x)
	x ++
}

func main(){
	println(x)
}

可在初始化函数中创建goroutine,或等到它执行结束。

func init() {
	done := make(chan struct{})
	go func() {
		defer close(done)
		fmt.Println("init,", time.Now())
		time.Sleep(time.Second * 2)
	}()
	<-done
}

func main() {
	fmt.Println("main:", time.Now())
}
内部包

在进行代码重构时,我们会将一些内部模块陆续分离出来,以独立包形式维护。此时,基于首字母大写的访问权限控制就显得过于粗犷。因此,我们希望这些包导出成员仅在特定范围内访问,而不是向所有用户公开。
内部包机制相当于增加了新的访问权限控制:所有保存在internal目录下的包(包括自身)仅能被其父目录下的包(含所有层次的字目录)访问。
在这里插入图片描述

9.4 依赖管理

如何管理和保存第三方包,一直存在争议。将项目所有的第三方包都放到一个独立工作空间中,可能会导致版本冲突。但放到项目工作空间,又会把工作目录搞得面目全非。为此,引入名为vendor的机制,专门存放第三方包,实现源码和依赖完整打包分发。
在这里插入图片描述main.go
在这里插入图片描述
在main.go中导入github.com/qyuhen/test时,优先使用vendor/github.com/qyuhen/test
导入vendor中的第三方包,参数是以vendor/为起点的绝对路径。这就避免了vendor目录位置带来的麻烦,让导入无论使用vendor,还是GOPATH都能保持一致。
当多个vendor目录嵌套时,如何查找正确目标?要知道引入的第三方包可能也有自己的vendor依赖目录。
在这里插入图片描述
规则:从当前源文件所在目录开始,逐级向上构造vendor全路径,直到发现路径匹配的目标为止。匹配失败时,则依旧搜索GOPATH
对于main.go,可构造的路径是src/server/vendor/p 也就是p1 .而对于test.go最先构造的路径是src/server/vendor/x/vendor/p ,所以选择p2
要使用vendor机制,须开启“GO15VENDOREXPERIMENT=1”环境变量开关,且必须是设置了GOPATH的工作空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值