(9-1)包:包的基础知识

在Go语言中,“包”是一种组织代码的方式,它允许您将相关的代码和数据结构封装在一个单独的单元中。这些包可以被其他程序引用和重用,类似于Java中的类库。与Java中的“jar”文件不同,在Go语言中,每个包都会编译成一个独立的二进制文件,以便其他程序可以直接链接到它。在本章的内容中,将详细讲解Go语言包的知识,为读者步入本书后面知识的学习打下基础。

9.1  包的基础知识

Go语言是使用包来组织源代码的,包(package)是多个 Go 源码的集合,是一种高级的代码复用方案。Go语言中为我们提供了很多内置包,如 fmt、os、io 等。

9.1.1  包的作用

在计算机编程语言中,包指的是一组相关的模块或函数,通常用于实现某些特定的功能。包的主要作用如下:

  1. 提供代码重用:包中的模块和函数可以在不同的项目中被反复使用,从而提高了代码的重用性,减少了代码的冗余。
  2. 管理依赖:包可以作为一个独立的组件来管理各种依赖,尤其是第三方库的依赖,可以避免版本冲突等问题。Go语言的包管理工具——GoMod 可以帮助我们管理应用程序或库所依赖的所有包,并确保所有需要的包都已安装。这使得构建和部署应用程序更加简单和可靠。
  3. 提高代码可维护性:包可以使代码更加有条理,使得代码结构更加清晰,易于维护。
  4. 加快开发效率:包可以提供许多已经封装好的模块和函数,这些模块和函数可以被直接调用,从而大大提高了开发效率。
  5. 函数和类型查询:使用包,可以方便地查找函数和类型定义。由于所有有关函数和类型的信息都存储在一个单独的位置上,因此可以更轻松地了解代码库的整体结构。

注意:在 Go 语言中有“包”这一概念,跟Java语言中的类库相似。在Java中,“库”通常指的是一组相关的类和方法,可以被其他程序引用和重用。这些库通常作为“jar”文件分发,并可以通过Java的类路径加载到应用程序中。因此,尽管“包”和“库”都允许我们组织可重用的代码并提供抽象接口,但是在语法和实现上存在差异。在Go语言中,“包”是一个非常基础的概念,并且在语言层面上得到了广泛支持,而在Java中,“库”是更高级别的概念,并需要第三方工具或框架的支持来管理。

9.1.2  包的基本概念

一个Go程序由多个包组成,每个包都有自己的名称空间,不同的包之间可以相互引用。下面是Go语言包的一些基本概念:

  1. 包名:每个包都有一个唯一的名称,通常与包所在的目录名称相同。包名在Go语言中必须以字母或下划线开头,不能以数字开头,并且只能使用字母、数字和下划线。
  2. 包导入:在Go语言中,如果要使用其他包中定义的函数或变量,需要通过import语句将这个包导入。导入的包名可以是标准库中的包名,也可以是用户自定义的包名。
  3. 可见性:在Go语言中,如果一个标识符(如函数、变量、结构体等)以大写字母开头,则该标识符可以被外部包访问,否则只能在当前包中使用。
  4. 包初始化:当一个包被导入时,其中定义的init函数会被自动执行,函数init()可以用来完成一些初始化任务,如初始化全局变量等。
  5. 包文档:每个包都应该提供一份文档,文档应该包括该包的功能、使用方法以及对各种函数和变量的解释等。

9.1.3  包的组织形式和文件结构

Go语言的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称,虽然Go语言没有强制要求包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样结构更清晰。包可以定义在很深的目录中,包名的定义是不包括目录路径的,但是包在引用时一般使用全路径引用。比如在GOPATH/src/a/b/ 下定义一个包 c。在包 c 的源码中只需声明为package c,而不是声明为package a/b/c,但是在导入 c 包时,需要带上路径,例如import "a/b/c"。

在对包进行命名和组织时,习惯用法如下:

  1. 包名一般是小写的,使用一个简短且有意义的名称。
  2. 包名一般要和所在的目录同名,也可以不同,包名中不能包含- 等特殊符号。
  3. 包一般使用域名作为目录名称,这样能保证包名的唯一性,比如 GitHub 项目的包一般会放到GOPATH/src/github.com/userName/projectName 目录下。
  4. 包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件。
  5. 一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下。

Go语言包的组织形式非常灵活,可以根据项目的需要进行自由的组织和划分。在通常情况下,一个Go项目的文件结构如下:

project/
├── main.go
├── pkg/
│   ├── util/
│   │   ├── util.go
│   │   └── ...
│   └── ...
├── internal/
│   ├── api/
│   │   ├── api.go
│   │   └── ...
│   └── ...
├── vendor/
│   ├── module1/
│   │   ├── ...
│   ├── module2/
│   │   ├── ...
│   └── ...
├── config/
│   ├── config.toml
│   ├── ...
└── ...

9.1.4  包的基本操作

1. 包的导入

要在代码中引用其他包的内容,需要使用 import 关键字导入使用的包。具体语法格式如下:

import "包的路径"

在使用上述导入语句时需要注意如下3点:

  1. import 导入语句通常放在源码文件开头包声明语句的下面;
  2. 导入的包名需要使用双引号包裹起来;
  3. 包名是从GOPATH/src/ 后开始计算的,使用/ 进行路径分隔。

在Go语言中,有两种导入包的写法,分别是单行导入和多行导入。

(1)单行导入

单行导入的语法格式如下:

import "包 1 的路径"
import "包 2 的路径"

(2)多行导入

多行导入的语法格式如下:

import (
    "包 1 的路径"
    "包 2 的路径"
)

2. 包的导入路径

在Go语言中,包的引用路径有两种写法,分别是全路径导入和相对路径导入。

(1)全路径导入

包的绝对路径就是GOROOT/src/或GOPATH/src/后面包的存放路径,例如下面的代码:

import "lab/test"
import "database/sql/driver"
import "database/sql"

上面代码的含义如下:

  1. test 包是自定义的包,其源码位于GOPATH/src/lab/test 目录下;
  2. driver 包的源码位于GOROOT/src/database/sql/driver 目录下;
  3. sql 包的源码位于GOROOT/src/database/sql 目录下。

(2)相对路径导入

相对路径只能用于导入GOPATH下的包,标准包的导入只能使用全路径导入。例如包a的所在路径是GOPATH/src/lab/a,包b的所在路径为GOPATH/src/lab/b,如果在包b中导入包 a,则可以使用相对路径导入方式。演示代码如下:

// 相对路径导入
import "../a"

另外,也可以使用上面的全路径导入方式,演示代码如下:

// 全路径导入
import "lab/a"

3. 包的引用格式

在Go语言中,包有四种引用格式,下面以 fmt 包为例来分别演示一下这四种格式。

(1)标准引用格式

例如下面的代码,此时可以用fmt.作为前缀来使用 fmt 包中的方法,这是常用的一种方式。

import "fmt"

(2)定义别名引用格式

在导入包的时候,还可以为导入的包设置别名,例如下面的代码:

import F "fmt"

在上述代码中,F是包fmt的别名,在使用时可以用“F.”来代替标准引用格式的“fmt.”来作为前缀使用包fmt中的方法。例如下面的代码:

package main
import F "fmt"
func main() {
    F.Println("我爱Go语言")
}

(3)省略引用格式

例如下面的代码,种格式相当于把 fmt 包直接合并到当前程序中,在使用 fmt 包内的方法是可以不用加前缀fmt.,直接引用。

import . "fmt"

下面是省略引用格式的演示代码:

package main
import . "fmt"
func main() {
    //不需要加前缀 fmt.
    Println("我爱Go语言")
}

(4)匿名引用格式

在Go语言中引用某个包时,如果只是希望执行包的初始化函数init(),而不使用包内部的数据,此时可以使用匿名引用格式,例如下面的代码:

import _ "fmt"

匿名导入的包与其他方式导入的包一样都会被编译到可执行文件中。

如果使用标准格式引用包,但是在代码中却没有使用包,编译器会报错。如果包中有初始化函数init(),则通过import _ "包的路径" 这种方式引用包,仅执行包的初始化函数,即使包没有初始化函数init(),也不会引发编译器报错。例如下面的代码:

package main
import (
    _ "database/sql"
    "fmt"
)
func main() {
    fmt.Println("我爱Go语言")
}

注意:

  1. 一个包可以有多个init()函数,在包加载时会执行全部的init()函数,但是并不能保证执行顺序,所以不建议在一个包中放入多个init()函数,将需要初始化的逻辑放到一个init()函数里面。
  2. 包不能出现环形引用的情况,比如包 a 引用了包 b,包 b 引用了包 c,如果包 c 又引用了包 a,则编译不能通过。
  3. 包的重复引用是允许的,比如包 a 引用了包 b 和包 c,包 b 和包 c 都引用了包 d。这种场景相当于重复引用了 d,这种情况是允许的,并且 Go 编译器保证包 d 的init()函数只会执行一次。

4. 包的加载

在Go语言中,包的加载是在编译时进行的,当一个Go程序被编译时,编译器会自动寻找需要的包,并将其编译进最终的可执行文件中。Go语言包的加载过程通常包括以下几个步骤:

(1)寻找包:在编译一个Go程序时,编译器会首先从GOROOT和GOPATH环境变量指定的目录下寻找需要的包。如果找不到,则会报错。

(2)解析和编译包:一旦找到了需要的包,编译器就会把包中的源代码解析成AST(抽象语法树),并将其编译为二进制的包文件。在编译过程中,编译器会自动为包中的每个文件生成对应的.o文件,并将这些文件链接为最终的包文件。

(3)缓存包:编译器在编译完一个包后,会将其缓存起来,以便后续使用相同的导入路径时可以直接使用缓存中的结果,而无需重新编译。

(4)递归加载依赖包:在编译一个Go程序时,可能会涉及到多个包之间的依赖关系。在此情况下,编译器会递归地加载所有的依赖包,直到加载完所有需要的包为止。

在Go语言中,包的加载过程相对来说比较简单和直观。通过合理地组织和管理包,我们可以更加方便地实现各种功能,并提高代码的可读性和可维护性。

9.1.5  环境变量GOPATH

GOPATH是Go语言中一个非常重要的环境变量,用于指定工作区目录的绝对路径。在Go语言中,工作区目录通常包含如下三个子目录:

  1. src/:存放源代码文件。
  2. pkg/:存放编译后的包文件。
  3. bin/:存放可执行文件。

当我们使用go build、go install等命令时,Go语言会自动将程序构建到“$GOPATH/bin”目录下,并将依赖的第三方库存放到“$GOPATH/pkg”目录下。同时在开发过程中,我们也可以通过import语句从“$GOPATH/src”目录中引入其他包。

需要注意的是,在GOPATH中可以包含多个目录,各个目录之间使用操作系统的分隔符进行分隔(在 Windows 系统上为分号;,在 Unix/Linux 系统上为冒号:)。这种方式可以让我们同时管理多个项目的源码和依赖。

一般来说,我们可以按照以下步骤来创建和使用$GOPATH:

(1)在你的计算机上创建一个目录,用于存放所有的 Go 语言项目。例如,可以在你的 home 目录下创建一个名为 go 的目录:

$ mkdir ~/go

(2)设置 $GOPATH 环境变量,将它的值设置为你创建的 Go 语言项目的工作目录的绝对路径。例如在 Linux 和 macOS 系统上,可以在命令行中执行以下命令来设置 $GOPATH 环境变量:

$ export GOPATH=/path/to/your/go/projects

其中,/path/to/your/go/projects 应该替换为你创建的 Go 语言项目的工作目录的绝对路径。

(3)如果使用的是 Windows 系统,可以在“计算机”属性中添加一个名为 GOPATH 的系统环境变量,并将它的值设置为你创建的 Go 语言项目的工作目录的绝对路径。查看$GOPATH路径的方法是在Windows控制台中中输入“go env”命令,会显示前电脑中Go环境的信息,如图9-1所示。

图9-1 当前电脑的Go信息

(3)在 $GOPATH/src 目录下创建一个名为“your_project_name”的文件夹,用于存放你的 Go 语言项目的源代码。例如:

$ cd $GOPATH/src
$ mkdir your_project_name

(4)在文件夹“your_project_name”中创建一个名为 main.go 的文件,用于编写你的 Go 语言程序。

(5)接下来可以使用命令来运行Go 语言程序:

 $ go run main.go

注意:GOPATH是Go语言中非常重要的一个环境变量,它指定了Go语言工作区的根目录,并影响了Go编译器、安装器和运行时的行为。熟练掌握GOPATH的使用方法,有助于提高我们的开发效率和代码质量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

感谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值