Golang 包的概念、导入与可见性

分享 · 01-14
Golang 包的概念、导入与可见性

概念

包在 Go 语言中是组织和结构化代码的核心机制,每个程序都由包(通常简称为 pkg)的概念组成,可以使用自身的包或者从其它包中导入内容。如同其它一些编程语言中的类库或命名空间的概念,每个 Go 文件都属于且仅属于一个包。一个包可以由许多以 .go 为扩展名的源文件组成,因此文件名和包名一般来说都是不相同的。

我们必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main 表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
对于非 main 包,如名为 pack1 的包,当单独编译这些源文件时,结果并非生成可直接执行的二进制文件,而是产生一个归档文件(通常是 .a 文件格式)用于链接阶段。此外,遵循 Go 的命名规范,所有的包名应当使用小写字母,以表明它们对外部世界是公开可用且不具有私有性。这样的设计有助于促进模块化的开发与重用,同时确保了 Go 生态系统内的包能够保持良好的可见性和一致性。

在 Go 的安装文件里包含了一些可以直接使用的包,即标准库。Go 的标准库包含了大量的包(如:fmt 和 os),我们也可以创建自己的包。在编写 Go 程序时,我们要像搭积木一样把一个个“包”和它们内部的代码文件按照一定的顺序组合起来。每个“包”就像一个装着相关代码的小盒子。
如果一个盒子里(即同一个包内)有多个小零件(源代码文件),那么这些零件必须一起处理,不能分开。通常情况下,我们会将一个包放在一个单独的文件夹里,让这个文件夹里的所有文件都属于同一个包。
当某个盒子里的零件改了或者重新做了,所有使用了这个盒子(引用了这个包)的其他大玩具(客户端程序)也得重新组装一遍,以确保所有的部分都是最新的。
Go 语言设计了一种聪明的办法来快速地“拼装”这些零件:它会根据各个零件(.go 文件)之间的依赖关系决定编译顺序。编译器就像个细心的工匠,它从已编译好的零件(.o 文件)中提取需要的信息,而不是每次都从最原始的零件(.go 文件)开始。

举个例子,假如 A 零件要靠 B 零件工作,而 B 零件又离不开 C 零件,那么我们先编好 C,再编 B,最后编 A。有意思的是,在编 A 的时候,工匠(编译器)直接查看已经做好的 B 零件(B.o 文件),因为它已经包含了来自 C 零件的信息。这样,Go 编译器就能高效、准确地按需构建程序的各个部分了。

这种机制对于编译大型的项目时可以显著地提升编译速度。

导入

关键字:import

导入包的三种写法:
1.

import "fmt"
import "os"

2.

import "fmt"; import "os"

3.

import (
   "fmt"
   "os"
)

当在 Go 语言中引用一个包时,其查找规则如下:

1.如果包名不以 . 或 / 开头,例如 "fmt" 或者 "container/list" 这样的名称,Go 编译器会在全局的 GOPATH 或 GOMOD 指定的路径下搜索标准库或第三方依赖包。

2.若包名以 ./ 开头,比如 "./mylocalpackage",编译器会从当前源文件所在的相对目录开始查找该包。

3.若包名以 / 开头(在 Windows 系统中同样适用),如 "/home/user/myproject/mypackage",那么编译器将直接从指定的绝对路径中寻找包。

导入一个包就意味着包含了这个包内所有的公开代码对象。而在包内部,除特殊符号 "_" 外,所有代码对象(如变量、函数、类型等)的标识符都必须保持唯一,目的是防止命名冲突。尽管如此,在不同的包里,完全可以使用相同的标识符,因为每个包都有自己的命名空间,通过包名即可区分它们。
此外,Go 语言编译器有一条强制执行的规定:包中的代码对象是否可以被外部文件访问,取决于它们是否以大写字母开头。只有首字母大写的标识符所代表的对象才会对外部包可见,而全部小写字母开头的标识符则仅限于包内部使用。

注意事项:如果导入了一个包却没有使用它,则会在构建程序时引发错误,如 imported and not used: os,这正是遵循了 Go 的格言:没有不必要的代码!

可见性规则

当涉及到标识符(涵盖常量、变量、类型名、函数名称以及结构体字段等)时,若其名称以大写字母开始,比如 Group1,那么这类标识符所代表的对象便可以在外部包中被访问和使用。这一特性类似于面向对象编程中的“公共”成员(public)。相反,如果一个标识符以小写字母开头,则表示它仅在当前包内可见和可使用,对外部是不可见的,这与面向对象语言中的“私有”成员(private)概念相类似。

因此,在引入一个外部包后,能够且只能访问到该包中那些已经导出的对象。

举例来说,假定在名为 pack1 的包里有一个变量或函数名为 Thing(由于首字母为大写 'T',所以它是可导出的),那么在当前程序包中导入 pack1 后,可以通过点标记语法像操作面向对象属性那样调用 Thing,即:pack1.Thing。这里 pack1 是不可以省略的,因为它指定了对象所属的命名空间。

通过这种方式,Go 语言中的包起到了命名空间的作用,有效地防止了不同包之间因同名标识符导致的命名冲突问题。例如,即使两个不同的包 pack1pack2 中都存在名为 Thing 的变量或函数,它们可以通过完整的包名进行区分,如 pack1.Thingpack2.Thing,从而实现无歧义地引用各自包内的内容。

Theme Jasmine by Kent Liao