Go基础|第2章:package

  • 原创
  • Madman
  • /
  • /
  • 0
  • 5803 次阅读

Golang.jpg

Synopsis: 每个 Go 程序都是由包(package)组成,每个包的代码都可以作为很小的复用单元,被其他项目引用。源代码中可以通过 import 来导入官方提供的标准库中的包(standard library)或别人写的第三方包(比如 github.com/gorilla/mux )

1. 如何组织 Go 代码

Go 代码被组织成 包(packages),包是同一目录中一起编译的源文件的集合。比如内建标准库(位于 %GOROOT%\src 目录)的 net 包的组织结构:

|---net  // net 包
     |---dial.go
     |---net.go
     |---http  // http 子包,导入路径为 net/http
          |---http.go
          |---client.go
          |---server.go
          |---httputil  // httputil 子包,导入路径为 net/http/httputil
                |---dump.go
                |---httputil.go

模块(module) 是一起发布的相关 Go 包的集合,模块下的 go.mod 文件中定义了 模块路径(module path),比如 github.com/google/go-cmp:

module github.com/google/go-cmp  // 模块路径(module path)

go 1.8

require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543

模块路径是该模块下所有包的 导入路径(import path) 的前缀。The module contains the packages in the directory containing its go.mod file as well as subdirectories of that directory, up to the next subdirectory containing another go.mod file (if any).

比如 https://github.com/grpc/grpc-go 这个项目,模块根目录下有个 go.mod 文件,其内容的第一行表示这个模块名为 google.golang.org/grpc。此模块下有很多包,还有一个 examples 目录,它里面又有一个 go.mod 文件,那么 examples 目录就是另一个单独的模块 google.golang.org/grpc/examples

模块路径不仅是作为其包的导入路径前缀,而且还指示 go 工具应该去哪下载这个模块的源代码,比如要下载 golang.org/x/tools 模块,那么 go 工具会访问 https://golang.org/x/tools (如果我们设置了 GOPROXY,将由代理服务器去下载模块)。模块和它的依赖都被自动地下载到 %GOPATH%\pkg\mod 目录下,执行 go clean -modcache 命令可以清空所有下载的模块

执行 go get 命令下载指定版本的模块,比如 go get github.com/gorilla/mux@v1.6.2

导入路径是用于导入包的字符串。包的导入路径是包的模块路径加上包所在模块中的子目录名,比如模块 github.com/google/go-cmp 下有一个包位于 cmp/ 目录,那么这个包的导入路径为 github.com/google/go-cmp/cmp

标准库中的包的导入路径没有模块路径前缀,比如标准库 fmt 包的导入路径为 fmt

2. 包命名

按照惯例,给包及其目录命名时,应该使用简洁清晰、全小写的、单个单词(不建议使用下划线连接多个单词或驼峰式多个单词)的名字,这有利于开发者频繁输入包名,比如 Go 标准库中的 fmtlogos

包名只是用于导入的名称,它不需要在所有源代码中都是唯一的,如果真的命名冲突了,用户在导入包时可以选择在本地为导入的包设置一个别名,具体参考本文第 6 章

按照约定,包名是它的源目录的基名,即包名与导入路径的最后一个元素一致。The package in src/encoding/base64 is imported as "encoding/base64" but has name base64, not encoding_base64 and not encodingBase64

3. 包声明

一个代码包可以由多个 Go 源文件组成,且源文件必须都位于同一个目录下。每个源文件都是以包的 声明语句 开头,用来指定包的名字,同一个目录(不包含子目录)下的所有源文件必须声明为同一个包名,比如内建标准库中的 fmt 包中的源文件都以 package fmt 开头(包目录下还可能有单元测试文件,比如 package fmt_test)。可执行的 Go 程序源文件必须以 package main 开头,执行入口为 main() 函数

一个包只需要在其中一个 .go 文件的最前面添加 包注释 即可

4. 包的导出名

Go 命名约定:包级别定义的名称(比如 variable、constant、function、method 等)在包外的可见性取决于其第一个字符是否大写!如果名字是大写字母开头的(必须是在函数外部定义的包级名字,注意包级函数名本身也是包级名字),那么它是 导出的(exported),也就是说可以被 外部的包 访问,例如 fmt 包的 Printf 函数就是导出的,可以在 fmt 包的外部被访问。在导入一个包时,你只能引用其中已导出的名字,任何未导出的名字在该包外均无法被访问

如果一个名字被定义在函数内部,那么它就只能在函数内部有效。任何在函数外部(也就是包级语法域)声明的名字可以在 同一个包 的任何源文件中被访问

综上所述:

  • An identifier is exported if it begins with a capital letter.
  • All identifiers defined within a package are visible throughout that package.
  • When importing a package you can access only its exported identifiers.

包的导入者将使用包名来引用其内容,因此可以利用这个特点来定义简单直观的包中的导出名称。For instance, the buffered reader type in the bufio package is called Reader, not BufReader, because users see it as bufio.Reader, which is a clear, concise name. 另外,bufio.Readerio.Reader 并不冲突

5. 包导入

源代码中必须先使用 import 关键字来导入 Go 标准库或第三方包,才能使用其中导出的名称

Go 编译器会去 %GOROOT%\src%GOPATH%\src%GOPATH%\pkg\mod 或模块根目录下的 vendor 目录中查找是否有对应的包:

包名 导入 包位置
net import "net" 标准库: %GOROOT%\src\net
http import "net/http" 标准库: %GOROOT%\src\net\http
httputil import "net/http/httputil" 标准库: %GOROOT%\src\net\http\httputil
cmp import "github.com/google/go-cmp/cmp" 外部包: https://github.com/google/go-cmp/cmp,被下载到本地 %GOPATH%\pkg\mod\github.com\google\go-cmp@v0.4.0

第三方包的导入路径一般都包含托管源代码的主机名、组织名称等,比如 github.com/gin-gonic/gingolang.org/x/net

可以以分组的形式导入多个包:

import (
    "fmt"
    "math"
)

6. 解决包名冲突

假设我要同时导入标准库中两个不同的 rand 包,可以分别为导入的包设置一个别名,这样后续可以用别名来调用各自包中的函数:

package main

import (
  crand "crypto/rand"
  mrand "math/rand"

  "fmt"
)

func main() {
  a := mrand.Int() // 伪随机数
  b := make([]byte, 8)
  crand.Read(b) // 通过加密算法生成的安全的伪随机数
  fmt.Println(a, b)
}

事实上,一个引入声明语句的完整形式为:

import importname "path/to/package"

其中引入名 importname 是可选的,它的默认值为被引入的包的包名

强烈建议:不要使用 import . "fmt" 这种形式来导入包!

7. 匿名引入

import a package only for its side effects, without any explicit use

有时候,你可能需要导入一个包,但是并不会使用这个包中所导出的变量或函数等,只是想在导入时自动先执行它的 init() 函数,那么可以使用 空白标识符(_) 来重命名这个包,否则 Go 编译器会编译失败。为了避免代码变得臃肿,Go 不允许你导入(非匿名引入时)一个包却不使用它

import (
    "database/sql"
    "fmt"

    _ "github.com/lib/pq"  // go connect to postgresql
)

8. 包的初始化: init 函数

包中的每个文件都可以包含多个 init() 初始化函数来简化初始化工作:

func init() { /* ... */ }

这样的 init 初始化函数除了没有形参、没有返回值、不能被调用或引用外,其他行为和普通函数类似。每个文件中的 init 初始化函数,在程序开始执行时按照它们声明的顺序被自动调用。每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。因此,如果一个 p 包导入了 q 包,那么在 p 包初始化的时候可以认为 q 包必然已经初始化过了。初始化工作是自下而上进行的,main 包最后被初始化。以这种方式,可以确保在 main 函数执行之前,所有依赖的包都已经完成初始化工作了

9. 查看包文档

9.1 go doc 与 godoc

你可以使用 go doc 命令在本地查看 Go 标准库的文档:

D:\GoCodes\hello>go doc fmt
D:\GoCodes\hello>go doc fmt.Println
D:\GoCodes\hello>go doc builtin append

可以访问 HTML 格式的在线文档:国际官网国内官网

或者,安装 godoc 工具,它可以启动一个 Web 服务器,并以网页形式显示文档:

D:\GoCodes\hello>go get -u -v golang.org/x/tools/cmd/godoc

启动 Web 服务器:

D:\GoCodes\hello>godoc -http=:6060

然后,打开浏览器,访问 http://127.0.0.1:6060/ 即可!

9.2 GoDoc

GoDoc 网站可以为存放在 Github / Bitbucket / Launchpad / Google Project Hosting 等上面的开源 Go 包提供文档查询功能,比如 https://godoc.org/github.com/gorilla/mux

改为新域名 pkg.go.dev 了,比如: https://pkg.go.dev/github.com/gorilla/mux

分类: Go Basic
标签: go doc go get godoc package
未经允许不得转载: LIFE & SHARE - 王颜公子 » Go基础|第2章:package

分享

作者

作者头像

Madman

如需 Linux / Python 相关问题付费解答,请按如下方式联系我

0 条评论

暂时还没有评论.