声明、类型、语句与控制结构
16 理解 Go 语言的包导入
Go 语言是使用包(package)作为基本单元来组织源码的,编译速度快是这种“先进性”的一个突出表现,即使每次编译都是从零开始。Go 语言的这种以包为基本构建单元的构建模型使依赖分析变得十分简单,避免了 C 语言那种通过头文件分析依赖的巨大开销。Go 编译速度快的原因体现在:
- Go 要求每个源文件在开头处显式地列出所有依赖的包导入,这样 Go 编译器不必读取和处理整个文件就可以确定其依赖的包列表。
- Go 要求包之间不能存在循环依赖,这样一个包的依赖关系便形成了一张有向无环图,由于无环,包可以被单独编译,也可以并行编译。
- 已编译的 Go 包对应的目标文件(file_name.o 或 package_name.a)中不仅记录了该包本身的导出符号信息,还记录了其依赖包的导出符号信息。这样 Go 编译器在编译某包 P 时,针对 P 依赖的每个包导入(比如导入包 Q),只需读取一个目标文件即可(比如:Q 包编译成的目标文件中已经包含 Q 包的依赖包的导出信息),而无需再读取其他文件中的信息。
Go 程序构建过程:简单来讲也是由编译(compile)和链接(link)两个阶段组成的。
-
一个非 main 包在编译后会对应生成一个 .a 文件,该文件可以理解为 Go 包的目标文件,该目标文件实际上是通过 pack 工具对 .o 文件打包后形成的。默认情况下,在编译过程中 .a 文件生成在临时目录下。如果是构建可执行程序,那么 .a 文件会在构建可执行程序的链接阶段起作用。
-
标准库包的源码文件在 GOROOT/pkg/darwin_amd64(以 macos 为例,linux 是linux_amd64)。默认情况下,对于 Go 标准库中的包,编译器直接链接的是 $GOROOT/pkg/darwin_amd64 下的 .a 文件。
-
在使用第三方包的时候,在第三方包源码存在且对应的 .a 已安装的情况下,编译器链接的仍是根据第三方包最新源码编译出的 .a 文件,而不是之前已经安装到 $GOROOT/pkg/darwin_amd64 下的目标文件。所谓的使用第三方包源码,实际上是链接了以该最新包源码编译的、存放在临时目录下的包的 .a 文件。
路径名还是包名:
-
编译器在编译过程中必然要使用的是编译单元(一个包)所依赖的包的源码。而编译器要找到依赖包的源码文件,就需要知道依赖包的源码路径。这个路径由两部分组成:基础搜索路径和包导入路径。
-
基础搜索路径的规则:
- 所有包的源码基础搜索路径都包括 $GOROOT/src;
- 在 1 的基础上,不同版本 Go 包含的其他搜索路径有不同:
- 经典的 gopath 模式下(GO111MODULE=off):$GOPATH/src;
- module-aware 模式下(GO111MODULE=on/auto):$GOPATH/pkg/mod;
-
搜索路径的第二部分就是位于每个包源码文件头部的包导入路径。基础搜索路径与包导入路径结合在一起,Go 编译器便可确定一个包的所有依赖包的源码路径的集合,这个集合构成了 Go 编译器的源码搜索路径空间。
-
惯用法:
-
包导入路径的最后一段目录名最好与包名一致。
-
当包名与包导入路径中的最后一个目录名不同时,最好用下面的语法将包名显式放入包导入语句。
import package_name "your_package_import_path"
-
-
包名冲突问题:同一源码文件的依赖包在同一源码搜索路径空间下的包名冲突问题可以由显式指定包名的方式解决。
往期回顾
关注我
参考
《Go 语言精进之路:从新手到高手的编程思想、方法和技巧》——白明