返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

10. 包

发布于 2024-10-12 19:15:49 字数 4223 浏览 0 评论 0 收藏 0

包(package)由同一目录下的多个源文件构成。

  • .go.c.s 等文件。
  • .go 头部通过 package 定义所属包。
  • 源文件必须是 UTF-8 格式。
  • 包是成员作用域边界,包内成员可相互访问。
  • 名称首字母大写为 导出成员 (exported),可外部访问。

包名可与目录名不同,通常小写单数模式。同一目录下源文件必须使用相同包名。

package mylib                  // 包名通常与目录一致。

func ddd(x, y int) int {       // 私有成员。
	return x + y
}

func Hello() {                 // 导出成员,可外部访问。
	println("Hello, World!")
}

另有几个特殊含义的包名:

  • main : 用户可执行文件入口包。
  • all : 所有包,包括标准库和依赖项。
  • std : 标准库。
  • cmd : 工具链。
  • documentation : 文档。(工具链忽略,无法导入)

为命令行提供包名为参数时,有以下几种方式。

  • 直接提供包名,如标准库。( go list math
  • ... 开始的相对路径。( go build ./mylib
  • ... 作为通配符,表示任意字符串。( go build ./...

提示: net/... 表示 net 和其所有子包,但不包括 vendor

除非特别指定,如 ./vendor/..../mylib/vendor/...

访问权限

同一包内不同源文件的成员可相互访问,但只有首字母大写为导出成员。

此规则适用于全局变量、全局常量、函数、类型、字段和方法等。

// test/mylib/add.go

package mylib

func add(x, y int) int {
	return x + y
}
// test/main.go

package main

import (
	"test/mylib"
)

func main() {
	z := mylib.add(1, 2)
	           ~~~ undefined: mylib.add
	println(z)
}

某些时候,需临时访问私有成员,可参考如下手段。

// test/mylib/data.go

package mylib

type data struct {
    x   int
    y   int
}

// --- tmp ------------

func NewData() *data {
	return &data{1, 2}
}
package main

import (
	"fmt"
	"unsafe"
	"test/mylib"
)

func main() {
	p := mylib.NewData()
	
	// p.x = 100
	// ~~~ p.x undefined

	d := (*struct{ 
		_ int
		y int              // 仅需要访问的字段,注意对齐。
	})(unsafe.Pointer(p))

	d.y = 100
	fmt.Println(*d)        // {1, 100}
}

还可用别名。

// test/mylib/data.go

package mylib

type data struct {
    x   int
    y   int
}

func (d *data) test() {
	println(d.x, d.y)
}

// --- tmp ------------

type DataTmp = data

func (d *DataTmp) SetY(y int) {
	d.y = y
}

func (d *DataTmp) Test() {
	d.test()
}
// test/main.go

package main

import (
	"test/mylib"
)

func main() {
	d := mylib.DataTmp{}
	d.SetY(100)
	d.Test()
}

临时提升访问权限,应避免直接修改原目标。

可将临时代码单独放在 _tmp.go 文件内,随后直接删除即可。

初始化

包内任意 .go 文件内都可定义一到多个 init 初始化函数。初始化函数由编译器生成代码自动执行(仅执行一次),不能被其他代码调用。

所有初始化函数被编译器整合到一个特殊数据结构内。在程序启动初始化阶段,在同一 goroutine 内依次执行。详情参考《源码剖析》。

包内全局变量按照依赖关系,逐步初始化(或零值)。每个初始化函数只完成一组相关逻辑,且相互之间不应该有执行次序依赖。

自 1.21 起,包初始化次序有了正式规范,但依然不建议对其有所依赖。

package main

var x = 100

func init() {
	println("a", x)  // a 100
}

func init() {
	println("b")
}

func main() {
	// init()
	// ~~~~ undefined: init    
}

可使用 GODEBUG 查看初始化执行情况。

$ GODEBUG=inittrace=1 ./test

init internal/bytealg @0.008 ms, 0.008 ms clock, 0 bytes, 0 allocs
init runtime          @0.040 ms, 0.048 ms clock, 0 bytes, 0 allocs
init main             @0.31  ms, 0.014 ms clock, 0 bytes, 0 allocs

#    包名             启动时刻     执行耗时         堆内存分配数量和次数。

内部包

代码重构时,将一些内部模块陆续分离出来,以独立包形式维护。此时,首字母大小写访问控制就过于粗旷。因为我们希望其 导出成员 仅限特定范围内访问,而不是向所有用户公开。

内部包 (internal package)机制相当于增加了新访问权限控制:

  • 内部包(含自身)只能被其父目录(含所有层次子目录)访问。
  • 内部包私有成员,依然只能在自己包内访问。
test/
  |
  +-- main.go
  |
  +-- mylib/              # 内部包 internal、a、b 的导出成员仅能
        |                 # 被 mylib、mylib/x、mylib/x/y 访问。
        |
        +-- internal/     # 内部包之间可相互访问。
        |      |          # 可导入外部包。
        |      +-- a/
        |      |
        |      +-- b/
        |
        +-- x/
            |
            +-- y/
package main

import (
	"test/mylib"
	"test/mylib/x"
	"test/mylib/x/y"

	// "test/mylib/internal/a"
	// ~~~~~~~~~~~~~~~~~~~~~~ use of internal package not allowed
)

func main() {
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文