Go:包管理 & 包使用

目录

前言

[一、GOPATH 时代 (Go 1.0 - Go 1.10)](#一、GOPATH 时代 (Go 1.0 - Go 1.10))

[1.1 工作区(Workspace)](#1.1 工作区(Workspace))

[1.2 GOROOT & GOBIN](#1.2 GOROOT & GOBIN)

[1.3 什么是GOPATH?](#1.3 什么是GOPATH?)

[1.4 GOPATH的使用](#1.4 GOPATH的使用)

[1.5 GOPATH的现状&缺陷](#1.5 GOPATH的现状&缺陷)

[二、Go Modules 引入至成熟 (Go 1.11~1.13及以后)](#二、Go Modules 引入至成熟 (Go 1.11~1.13及以后))

[2.1 Go Modules 的概念](#2.1 Go Modules 的概念)

[2.2 Go Modules > GOPATH](#2.2 Go Modules > GOPATH)

[2.3 go.mod & go.sum](#2.3 go.mod & go.sum)

[2.4 现代Go开发中的工作区使用](#2.4 现代Go开发中的工作区使用)

[2.4.1 工作区模式(Workspace mode)](#2.4.1 工作区模式(Workspace mode))

[2.4.2 go.work 文件](#2.4.2 go.work 文件)

三、包的介绍

[3.1 包的概念](#3.1 包的概念)

[3.2 包的声明 & 初始化](#3.2 包的声明 & 初始化)

[3.2.1 包的声明](#3.2.1 包的声明)

[3.2.2 包的初始化](#3.2.2 包的初始化)

[3.2.3 mian包](#3.2.3 mian包)

[3.2.4 init() & main()](#3.2.4 init() & main())

[3.3 包的加载顺序](#3.3 包的加载顺序)

四、包的导入

[4.1 导入声明](#4.1 导入声明)

[4.2 GO标准库](#4.2 GO标准库)

[4.3 本地导入](#4.3 本地导入)

[4.4 远程导入](#4.4 远程导入)

[4.5 别名导入](#4.5 别名导入)

[4.6 匿名导入](#4.6 匿名导入)


前言

对于大部分编程语言来说,代码包都是最有效的代码管理方式,Go语言也是使用包来管理代码的。如同其他语言一样,Go语言包的主要作用是把功能相似或相关的代码组织在同一个包中, 以方便查找和使用。

本章会详细介绍Go语言工程结构和包的使用。熟练掌握包管理是Go语言编码的基础,由于这部分内容的版本变革比较大,所以了解包管理的发展史是很有必要的,同时也要清楚现在最新使用包的方式。


一、GOPATH 时代 (Go 1.0 - Go 1.10)

  • 工作区由GOPATH环境变量定义
  • 所有Go代码必须位于GOPATH/src下
  • 包的导入路径直接对应src下的目录结构

1.1 工作区(Workspace)

Go工作区(Workspace)是Go语言中组织代码的一种方式,Go语言中没有工程文件的概念,而是通过目录结构来体现工程的结构关系。在传统的GOPATH模式下,Go代码必须放在工作区中。工作区是一个目录,其中包含了特定的目录结构来组织Go源代码、包和二进制文件。

传统的工作区其实就是一个对应于特定工程的目录,它应包含三个子目录:src目录、pkg目录和bin目 录。如下所示:

bash 复制代码
GOPATH/
│
├── src/
│   ├── github.com/
│   │   └── user/
│   │       └── project/
│   │           └── *.go
│   └── myproject/
│       └── *.go
│
├── pkg/
│   └── ${GOOS}_${GOARCH}/
│       └── github.com/
│           └── user/
│               └── project.a
│
└── bin/
    └── project

其中各目录的作用如下:

  • src目录:用于以代码包的形式组织并保存Go源码文件(如.go、.c、.h、.s等),同时也是 Go编译时查找代码的地方。
  • pkg目录:用于存放经由go get/install命令构建安装后的代码包的".a"归档文件,也是编译 生成的lib文件存储的地方。
  • bin目录:与pkg目录类似,在通过go get/install命令完成安装后,保存由Go命令源码文件生 成的可执行文件。

目录src用于包含所有的源代码,是Go命令行工具一个强制的规则,而pkg和bin则无须手动创 建,必要时Go命令行工具在构建过程中会自动创建这些目录。

.a文件是Go语言中的静态库文件(archive file)。这些文件包含预编译的Go包代码,可以被链接到其他Go程序中。

1.2 GOROOT & GOBIN

GOROOT是Go语言的安装路径。它指向Go开发包的根目录,包含Go的标准库、工具和源代码。

  • 定位Go标准库和工具的位置
  • 通常不需要手动设置,Go安装程序会自动设置
Go 复制代码
go env GOROOT

GOBIN指定了go install命令安装可执行文件的目录。设置GOBIN可以让你更灵活地控制可执行文件的安装位置。

  • 存放通过go install编译的可执行文件
  • 如果未设置,默认为GOPATH/binGOROOT/bin

查看所有Go环境变量 打开命令提示符,输入:go env

1.3 什么是GOPATH?

GOPATH是Go语言中用来指定工作空间位置的环境变量。在Go 1.11版本之前,它是Go开发的核心概念,用于解析import语句,管理源代码、依赖包和编译后的文件。GOPATH是Go语言开发中一个非常重要的概念,尽管在Go Modules引入后其重要性有所降低,但理解GOPATH对于掌握Go的工作机制仍然很有帮助。------ GOPATH定义了Go查找、编译和安装包的方式。它为Go提供了一个统一的工作空间概念。

  • 在Go Modules出现前,它是管理依赖的主要方式
  • 存储项目源码、包文件和可执行文件

在windows用于查看GOPATH路劲:

bash 复制代码
go env GOPATH

1.4 GOPATH的使用

1. 导入包: 在GOPATH模式下,import路径是相对于GOPATH/src的。

2. 获取包: 在 GOPATH 模式下,这会将包下载到GOPATH/src目录。

Go 复制代码
go get github.com/user/project

3. 编译和安装: 这会编译myproject并将二进制文件放在GOPATH/bin目录。

Go 复制代码
go install myproject

GOPATH的目的是为了告知Go需要代码的时候去哪里查找。需要注意的是,这里的代码包括本项目和引用外部项目的代码。GOPATH可以随着项目 的不同而重新设置。

在实际开发环境中,工作目录往往有多个。这些工作目录的目录路径都需要添加至GOPATH。 为了能够构建这个工程,需要先把所需工程的根目录加入到环境变量GOPATH中。否则,即使处于同一工作目录(工作区),代码之间也无法通过绝对代码包路径完成调用。

如果GOPATH设置了多个工作区,那么查找依赖包时是以怎样的顺序进行呢?例如包a依赖包 b,包b依赖包c,那么会先查找c包。那在工作区是如何查找这个依赖包c的呢?

首先,在查找依赖包的时候,总是会先查找GOROOT目录,也就是Go语言的安装目录,如果 没有找到依赖的包,才到工作区去找相应的包。在工作区中是按照设置的先后顺序来查找的,也 就是会从第一个开始依次查找,如果找到就不再继续,如果没有找到就报错。 如果多个工作区中存在导入路径相同的代码包会产生冲突吗?不冲突,因为按顺序找到所需 要的包就不往后继续找了。

而现代的Go Modules 提供了更灵活和精确的依赖管理方式,不依赖于 GOPATH。在现代 Go 开发中(尤其是使用 Go Modules 时),通常不需要关心多个 GOPATH 工作区。

1.5 GOPATH的现状&缺陷

现状

  • 弱化的重要性:自Go 1.11引入Go Modules后,GOPATH的重要性大幅降低。不再是组织Go代码的唯一或主要方式。在使用Go Modules的项目中,不再需要设置GOPATH。
  • 兼容性保留:仍然存在,主要用于向后兼容。在非模块模式下仍然发挥作用。
  • **新的用途:**作为默认的模块缓存位置(GOPATH/pkg/mod)。用于存储全局安装的Go工具(GOPATH/bin)。

缺陷

  • 版本管理困难:难以在同一个项目中使用同一包的不同版本,增加了项目间的依赖冲突风险。所有项目共享同一个GOPATH,容易造成版本冲突。
  • 可移植性差:项目严重依赖于GOPATH的设置,难以在不同环境间移植。
  • 包导入路径复杂 :导入路径必须是完整的,包括版本控制系统的信息。例如:github.com/user/project而不是简单的project
  • 依赖更新麻烦:没有内置的版本控制机制,更新依赖时可能影响到其他项目。
  • 全局状态:GOPATH是一个全局设置,影响所有Go项目。不利于在同一机器上同时处理使用不同Go版本的项目。
  • 与现代包管理实践不符:不支持语义化版本控制,缺乏显式的依赖声明机制。
  • 学习曲线:对新手来说,理解和正确使用GOPATH可能具有挑战性。新开发者入门困难,需要正确设置GOPATH。

语义化版本控制(Semantic Versioning)

语义化版本控制是一种版本号命名规范,通常格式为 X.Y.Z(主版本号.次版本号.修订号)。

  • 主版本号:做了不兼容的 API 修改
  • 次版本号:做了向下兼容的功能性新增
  • 修订号:做了向下兼容的问题修正

显式的依赖声明机制

在项目中明确列出所有直接依赖及其版本,通常在一个专门的配置文件中

了解了传统的包管理方式,接下来我们就需要了解现代的包管理方式


二、Go Modules 引入至成熟 (Go 1.11~1.13及以后)

  1. Go Modules引入(Go 1.11)
    • 引入模块概念,减少对GOPATH的依赖
    • 项目可以位于GOPATH之外
    • 每个项目可以有自己的依赖管理
  2. Go Modules成熟(Go 1.13及以后)
    • 模块成为默认的代码组织方式
    • GOPATH的重要性大大降低

2.1 Go Modules 的概念

  1. 定义: Go Modules 是 Go 语言的依赖管理系统,从 Go 1.11 版本引入,并在 Go 1.13 成为默认的依赖管理方式。它用于管理 Go 项目的依赖关系,确保项目的可重复构建和版本控制。
  2. 核心组件:
    • go.mod 文件:定义项目的模块路径、Go 版本和依赖关系。
    • go.sum 文件:包含依赖模块的加密校验和,用于验证下载的模块内容。
  3. 版本管理: 使用语义化版本(Semantic Versioning)来管理依赖版本,形如 v1.2.3。
  4. 依赖解析: Go Modules 可以自动下载、验证和缓存依赖,无需手动管理。

2.2 Go Modules > GOPATH

通过解决这些问题,Go Modules 极大地改善了 Go 语言的包管理和项目组织方式,使得大规模、复杂的 Go 项目开发变得更加可控和高效。

|-----------------|---------------------------------------------------|-----------------------------------------------------------------|
| | GOPATH | Go Modules |
| 版本管理 | 缺乏对包版本的明确控制 难以在同一项目中使用同一包的不同版本 | 引入 go.mod 文件,明确指定每个依赖的版本 支持语义化版本控制 允许在同一项目中使用同一包的不同版本 |
| 项目隔离 | 所有项目共享同一个 GOPATH,容易造成版本冲突 难以为不同项目维护不同的依赖版本 | 每个项目有自己的 go.mod 文件,实现了项目级的依赖隔离 不同项目可以使用同一包的不同版本而不冲突 |
| 可重现的构建 | 缺乏对依赖版本的精确记录,难以保证不同环境下的构建一致性 | 使用 go.sum 文件记录所有依赖的精确版本和校验和 确保在不同机器上可以重现完全相同的构建结果 |
| 依赖管理的显式性 | 依赖关系隐含在代码中,难以一目了然地了解项目依赖 | 在 go.mod 文件中明确列出所有直接依赖 提供 go mod why 等工具来解释依赖关系 |
| 包的位置灵活性 | 强制要求所有代码位于 GOPATH 中,限制了项目组织的灵活性 | 允许项目位于 GOPATH 之外的任何位置 简化了项目的共享和协作 |
| 中心化依赖问题 | 依赖 central public server(如 GitHub)的可用性 企业内部包的管理困难 | 引入了 proxy 和 checksum 数据库的概念 支持私有模块和企业内部模块仓库 |
| 依赖更新和回滚 | 更新或回滚依赖版本操作复杂,容易影响其他项目 | 提供简单的命令(如 go get -ugo mod tidy)来更新依赖 可以轻松回滚到特定版本而不影响其他项目 |
| vendor 目录管理 | vendor 目录的使用和管理不够优雅和高效 | 改进了 vendor 的支持,使其成为可选功能 提供 go mod vendor 命令来创建和管理 vendor 目录 |
| 全局状态依赖 | 依赖全局 GOPATH 设置,不利于项目的可移植性 | 移除了对 GOPATH 的依赖(除了用作默认的模块缓存位置) 提高了项目的可移植性和共享便利性 |
| 工具链集成 | 与现代 IDE 和 CI/CD 工具的集成不够顺畅 | 提供了更好的工具链集成能力 简化了在 CI/CD 环境中的依赖管理 |

2.3 go.mod & go.sum

go.mod 和 go.sum 文件共同工作,为 Go 项目提供了强大、安全和可重现的依赖管理机制。go.mod 定义了项目的依赖关系,而 go.sum 则确保了这些依赖的完整性和一致性。

|----|----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| | go.mod | go.sum |
| 用途 | * 定义模块的身份和依赖关系 * 指定项目使用的 Go 版本 | * 记录项目所有直接和间接依赖的加密校验和 * 确保依赖的完整性和一致性 |
| 结构 | module github.com/yourusername/yourproject go 1.16 require ( github.com/pkg/errors v0.9.1 golang.org/x/text v0.3.6 ) | github.com/pkg/errors v0.9.1 h1:FEg... github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp... golang.org/x/text v0.3.6 h1:aRYxNxv6... golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
| 特点 | * 人类可读且易于编辑 * 自动由 Go 命令维护 * 可以手动编辑,但通常通过 go 命令管理 | * 自动生成和维护 * 包含每个依赖模块的两个哈希值:一个用于整个模块,一个用于 go.mod 文件 * 不应手动编辑 |

2.4 现代Go开发中的工作区使用

  1. 单项目开发
    • 通常不需要关心传统的工作区概念
    • 使用go.mod文件管理项目依赖
  2. 多模块项目
    • 可以使用Go 1.18引入的工作区模式
    • 创建go.work文件来管理多个相关模块

2.4.1 工作区模式(Workspace mode)

Go 1.18引入的工作区模式工作区模式旨在简化多模块项目的开发。允许开发者在单个目录中同时处理多个相关的 Go 模块,而无需修改这些模块的 go.mod 文件。这对于开发涉及多个相互依赖的模块的大型项目特别有用。

主要特点:

  • 简化了多模块项目的开发和测试
  • 允许在本地修改依赖模块而不需要发布新版本
  • 提供了更灵活的项目结构

2.4.2 go.work 文件

go.work 文件是工作区模式的核心。它定义了工作区的结构和包含的模块。

优点:

  • 简化依赖管理:无需修改 go.mod 来使用本地版本的依赖
  • 改善开发体验:更容易在相关模块间切换和测试
  • 保持模块独立:每个模块仍然保持其独立的 go.mod 文件

注意事项:

  • go.work 文件通常不应该提交到版本控制系统
  • 它主要用于本地开发,不影响发布和构建过程

示例:

假设有一个项目包含三个模块:主应用、API 库和工具库。

Go 复制代码
my-project/
  ├── app/
  │   └── go.mod
  ├── api-lib/
  │   └── go.mod
  ├── utils/
  │   └── go.mod
  └── go.work

go.work 内容:

Go 复制代码
go 1.18

use (
    ./app
    ./api-lib
    ./utils
)

三、包的介绍

3.1 包的概念

包是结构化代码的一种方式:每个程序都由包(通常简称为pkg)的概念组成,可以使用自身 的包或者从其他包中导入内容。我们先来了解一下Go语言源码的组织方式:

  • Go语言的源代码以代码包为基本的组织单位。
  • 在文件系统中,代码包是与文件目录,每个Go文件都属于且仅属于一个包。一一对应的,文件目录的子目录也就是代码包的子包。
  • 在工作区中,一个代码包的导入路径实际上就是从src子目录到该包的实际存储位置的相对路径。(这是对于GOPATH,Go Modules可忽略)
  • 包名通常与目录名相同,但并非强制要求。另外要注意的是,所有的包名都应该使用小写字母

3.2 包的声明 & 初始化

3.2.1 包的声明

每一个Go源文件的第一行都需要声明包的名称,声明一个包使用关键字package。如声明一个 main包:

Go 复制代码
package main

关键点:

  • 同一目录下的所有 Go 文件必须属于同一个包。
  • 包名通常与目录名相同,但这不是强制的(除了 main 包)。
  • main 包是特殊的,它定义了一个可执行程序而不是一个库。

3.2.2 包的初始化

包的初始化过程比较复杂,按以下顺序进行:

a) 导入的包初始化 b) 包级变量初始化 c) init() 函数执行

3.2.3 mian包

在Go语言中,命名为main的包具有特殊的含义。Go语言的编译程序会试图把这种名字的包编 译为二进制可执行文件。所有用Go语言编译的可执行程序都必须有一个名叫main的包,main包有 且仅有一个。

当编译器发现某个包的名字为main时,它一定也会发现名为main()的函数,否则不会创建可执行文件。

main()函数是程序的入口:所以,如果没有这个函数,程序就没有办法开始执行。程序编译时,会使用声明main包的代码所在的目录名称作为可执行文件的文件名。

一个应用程序可以包含不同的包,而且即使你只使用main包也不必把所有的代码都写在一个 巨大的文件里,你可以用一些较小的文件,并且在每个文件非注释的第一行都使用package main来指明这些文件都属于main包。如果你打算编译包名不为main的源文件,如mypack,编译后产生的对象文件将会是mypack.a而不是可执行程序。

3.2.4 init() & main()

在Go语言里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。

概念:

|------------------------------------------------------------------|-----------------------------------------------------------------------|
| mian函数 | init函数 |
| * 只能在 package main 中定义 * 无参数,无返回值 * 一个程序只能有一个 main() 函数 * 程序的入口点 | * 可以在任何包中定义 * 无参数,无返回值 * 每个包可以有多个 init() 函数 * 自动调用,不能手动调用 * 用于包的初始化工作 |

注意事项:

  • 虽然可以定义多个 init(),但无论是对于可读性还是以后的可维护性来说,但建议每个文件只写一个
  • 包的初始化只执行一次:有时一个包会被多个包同时导入,但它只会被导入一次,即使被多个包导入(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)
  • init() 函数都在 main() 函数之前执行,这是因为init()用于设置包、初始化变量或者其他要在程序运行前优先完成的引导工作。
  • 每个package中的 init函数都是可选的,main 包必须包含一个 main() 函数
  • 这些函数都会在程序执行开始的时候被调用。

3.3 包的加载顺序

  1. 包的导入顺序:
    • 从 main 包开始,依次导入 pkg1、pkg2、pkg3。
    • 导入是递归的,每个包都可能导入其他包。
  2. 初始化顺序:
    • 初始化从最深层的包(这里是 pkg3)开始,然后逐层返回到 main 包。
  3. 每个包的初始化过程: a) 常量初始化(const...) b) 变量初始化(var...) c) init() 函数执行
  4. 包之间的依赖关系:
    • 箭头表示依赖关系,例如 pkg1 依赖 pkg2,pkg2 依赖 pkg3。
    • 每个包只会被初始化一次,即使被多个包引用。
  5. main 包的特殊性:
    • main 包是最后初始化的。
    • 在 main 包初始化完成后,main() 函数才会执行。
  6. 执行顺序: pkg3 -> pkg2 -> pkg1 -> main 对每个包:const -> var -> init() -> (next package)
  7. 程序结束:
    • main() 函数执行完毕后,程序退出(Exit)。

代码示例,以下为示例的目录结构

bash 复制代码
go_example/
├── main.go
├── pkg1/pkg1.go
├── pkg2/pkg2.go
├── pkg3/pkg3.go
└── go.mod  # 无需手动创建
  • go_example 目录中初始化 Go module:
bash 复制代码
go mod init go_example
go mod tidy
  • 创建包文件
Go 复制代码
// pkg3/pkg3.go
package pkg3

import "fmt"

const Pkg3Const = "pkg3 constant"

var Pkg3Var = "pkg3 variable"

func init() {
    fmt.Println("pkg3 init")
    fmt.Printf("pkg3: Const = %s, Var = %s\n", Pkg3Const, Pkg3Var)
}

func Pkg3Func() {
    fmt.Println("pkg3 function called")
}

// pkg2/pkg2.go
package pkg2

import (
    "fmt"
    "go_example/pkg3"
)

const Pkg2Const = "pkg2 constant"

var Pkg2Var = "pkg2 variable"

func init() {
    fmt.Println("pkg2 init")
    fmt.Printf("pkg2: Const = %s, Var = %s\n", Pkg2Const, Pkg2Var)
    fmt.Printf("pkg2: Using pkg3.Pkg3Var = %s\n", pkg3.Pkg3Var)
}

func Pkg2Func() {
    fmt.Println("pkg2 function called")
    pkg3.Pkg3Func()
}

// pkg1/pkg1.go
package pkg1

import (
    "fmt"
    "go_example/pkg2"
)

const Pkg1Const = "pkg1 constant"

var Pkg1Var = "pkg1 variable"

func init() {
    fmt.Println("pkg1 init")
    fmt.Printf("pkg1: Const = %s, Var = %s\n", Pkg1Const, Pkg1Var)
    fmt.Printf("pkg1: Using pkg2.Pkg2Var = %s\n", pkg2.Pkg2Var)
}

func Pkg1Func() {
    fmt.Println("pkg1 function called")
    pkg2.Pkg2Func()
}

// main.go
package main

import (
    "fmt"
    "go_example/pkg1"
)

const MainConst = "main constant"

var MainVar = "main variable"

func init() {
    fmt.Println("main init")
    fmt.Printf("main: Const = %s, Var = %s\n", MainConst, MainVar)
    fmt.Printf("main: Using pkg1.Pkg1Var = %s\n", pkg1.Pkg1Var)
}

func main() {
    fmt.Println("main function started")
    pkg1.Pkg1Func()
    fmt.Println("main function ended")
}

导航到go_example目录下运行 main.go:

Go 复制代码
go run main.go

运行这个程序,将看到类似以下的输出:

Go 复制代码
pkg3 init
pkg3: Const = pkg3 constant, Var = pkg3 variable
pkg2 init
pkg2: Const = pkg2 constant, Var = pkg2 variable
pkg2: Using pkg3.Pkg3Var = pkg3 variable
pkg1 init
pkg1: Const = pkg1 constant, Var = pkg1 variable
pkg1: Using pkg2.Pkg2Var = pkg2 variable
main init
main: Const = main constant, Var = main variable
main: Using pkg1.Pkg1Var = pkg1 variable
main function started
pkg1 function called
pkg2 function called
pkg3 function called
main function ended

四、包的导入

在Go语言程序中,每个包都有一个全局唯一的导入路径。导入语句中类似""fmt""的字符串 对应包的导入路径。 Go语言的规范并没有定义这些字符串的具体含义或包来自哪里,它们是由构建工具来解释 的。一个导入路径代表一个目录中的一个或多个Go源文件。

4.1 导入声明

  • 使用import关键字导入其他包。
  • 可以使用相对路径或完整的包路径导入。
Go 复制代码
// 声明方式一
import "fmt"
import "time"
 // 声明方式二
import (
   "fmt"
   "time"
 )

标准库源码位置:Go标准库(包括fmt等包)的源代码位于Go安装目录的src文件夹中。

4.2 GO标准库

|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 基础包 | fmt:格式化I/O,主要用于格式化输出和输入。 os:操作系统功能接口,提供平台无关的操作系统功能,如文件操作、环境变量等。 io:基本的输入输出原语,提供基本的I/O接口。 bufio:带缓冲的I/O,包装了io包,提供带缓冲的I/O操作。 strconv:字符串和基本数据类型的转换。 time:时间和日期的功能,提供时间的表示和测量功能。 |
| 字符串处理 | strings:字符串操作的常用函数,如拼接、分割、替换等。 unicode:Unicode字符的支持,包含unicode字符集相关的函数。 regexp:正则表达式的实现,提供正则表达式搜索和替换功能。 |
| 数学运算 | math:基本的数学函数,如三角函数、对数、指数等。 math/rand:伪随机数生成器。 |
| 容器 | container/heap:堆操作。 container/list:双向链表。 container/ring:环形链表 |
| 并发 | sync:提供基本的同步原语,如互斥锁、读写锁等。 sync/atomic:提供原子操作。 |
| 文件操作 | path/filepath:文件路径操作。 io/ioutil:一些常用的I/O操作,如读取文件到内存等。 |
| 网络编程 | net:底层的网络接口,提供网络相关的底层操作,如TCP、UDP等。 net/http:HTTP客户端和服务端的实现,提供构建HTTP服务的功能。 net/rpc:RPC(远程过程调用)的实现。 |
| 加密 | crypto:通用的加密包,包含常见的加密算法接口。 crypto/md5crypto/sha256等:具体的加密算法实现。 |
| 数据编码 | encoding/json:JSON数据编码和解码。 encoding/xml:XML数据编码和解码。 encoding/base64:Base64编码和解码。 |
| 数据库 | database/sql:SQL数据库的接口。 database/sql/driver:SQL驱动的接口规范。 |
| 压缩 | compress/gzip:gzip格式的压缩和解压缩。 compress/zlib:zlib格式的压缩和解压缩。 |
| 日志 | log:简单的日志记录包,提供基本的日志记录功能。 |
| 测试 | testing:用于编写测试代码。 testing/quick:用于编写快速测试的包。 |
| 工具 | flag:命令行参数解析。 reflect:反射,允许在运行时检查类型和变量。 runtime:与Go运行时系统交互的操作,如垃圾回收和协程操作。 |
| 其他... | text/template:用于生成文本输出的模板。 html/template:用于生成安全的HTML输出的模板,防止XSS攻击。 pprof:性能分析工具,用于CPU、内存、goroutine的分析。 goget :Go内置的依赖管理工具,通过 go get 下载和安装包。 plugin:动态加载Go插件(*.so 文件),允许在运行时加载共享对象并使用其中的符号。 |

4.3 本地导入

导出标识符规则

在Go语言中,标识符(如变量名、函数名、结构体名等)的首字母大小写非常重要,它决定了该标识符的可见性:

  • 首字母大写: 表示该标识符是公开的(public),可以被其他包访问和使用。
  • 首字母小写: 表示该标识符是私有的(private),只能在定义它的包内部使用。

这个规则适用于包级别的变量、函数、结构体、接口等。

例如,下方的目录结构中,main文件夹下有main.go文件,mypackage文件夹下有mypackage.go文件

bash 复制代码
go_example/
├── main/main.go
└── mypackage/mypackage.go
└── go.mod  # 无需手动创建
  • 创建包文件

在 "main" 文件夹中创建一个名为 "main.go" 的文件

Go 复制代码
// main/main.go
package main

import (
	"fmt"
	"go_example/mypackage"
)

func main() {
	mypackage.SayHello("Alice")
	fmt.Println(mypackage.PublicVar)

	// 下面的代码会导致编译错误,因为它们是私有的
	// mypackage.sayPrivate()
	// fmt.Println(mypackage.privateVar)
}

在 "mypackage" 文件夹中创建一个名为 "mypackage.go" 的文件

Go 复制代码
// mypackage/mypackage.go
package mypackage

import "fmt"

// SayHello is an exported function
func SayHello(name string) {
	fmt.Printf("Hello, %s! This is a function from mypackage.\n", name)
	sayPrivate()
}

// sayPrivate is a private function
func sayPrivate() {
	fmt.Println("This is a private function in mypackage.")
}

// PublicVar is an exported variable
var PublicVar = "I'm a public variable in mypackage"

// privateVar is a private variable
var privateVar = "I'm a private variable in mypackage"
  • 初始化 Go Module:
  1. 打开命令提示符或终端
  2. 导航到 "go_example" 文件夹
  3. 运行以下命令初始化 Go Module:
bash 复制代码
go mod init go_example
go mod tidy

构建并运行程序

  1. 在命令提示符或终端中,确保您仍在 "go_example" 文件夹中
  2. 运行以下命令来构建和运行程序:
Go 复制代码
go run main/main.go

如果一切设置正确,应该看到类似以下的输出:

Go 复制代码
Hello, Alice! This is a function from mypackage.
This is a private function in mypackage.
I'm a public variable in mypackage

4.4 远程导入

我们经常使用的包都是标准库中的包,也就是本地包。Go语言不仅支持我们调用本地包,还 支持调用远程服务器的包,例如托管在GitHub上的一个非常热门的Web框架gin,我们就可以通过 远程导入的方式将其导入使用。

前提条件

  • 确保你的Go版本 >= 1.11(为了使用Go Modules)
  • 项目位于GOPATH之外(推荐,但不是必须)

1. 初始化Go模块 如果你的项目还没有使用Go Modules,首先初始化:这会在你的项目根目录创建一个go.mod文件。

Go 复制代码
go mod init your_project_name

2. 在代码中导入远程包 在你的Go文件中,使用完整的包路径导入远程包:

bash 复制代码
import "github.com/gin-gonic/gin"
  1. 在导入之前还需要使用go get命令下载远程包
bash 复制代码
go get github.com/gin-gonic/gin

以下是gin官方提供的一个框架入门的例子:

Go 复制代码
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

运行后,就启动了一个HTTP服务器,并且可以看到输出了如下提示信息:

bash 复制代码
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

浏览器访问http://localhost:8080/ping后可以看到服务器返回如下消息(关于gin的这个Web框架,这里只作简要说明)

bash 复制代码
{
    "message": "pong"
}

此时控制台会输出如下信息,表示有人请求了我们的http服务:

4.5 别名导入

  • 当包名很长或不够直观时,提供更简短或更有意义的名称
  • 用于解决包名冲突:当两个包有相同的名称时,为导入的包指定一个别名
Go 复制代码
import alias_name "package_name"

fmt包的别名print和time包的别名date。需要注意的是,定义了别名后,就不能再使用该包原本的名字来调用它,只能使用它的别名来调用,运行代码示例如下:

Go 复制代码
package main

import (
	print "fmt"
	date "time"
)

func main() {
	now := date.Now()
	print.Printf("当前日期为: %d.%02d.%02d %02d:%02d:%02d\n",
		now.Year(), now.Month(), now.Day(),
		now.Hour(), now.Minute(), now.Second())
}

4.6 匿名导入

在Go语言中,如果导入了一个包而不使用,在编译时会产生"unused import"的编译错误。有 时我们可能需要导入一个包,但不会引用到这个包的标识符,在这种情况下可以使用包的匿名导 入的方式,就是使用下划线"_"来重命名该包。

  • 导入包但不直接使用其功能
  • 触发包的初始化函数(init())
  • 注册包的副作用
Go 复制代码
import _ "package_name"

不使用包里的函数,主要是为了调用该包中的init函数。 例如当我们想分析一个Web应用程序的性能时,导入net/http/pprof包即可,该包的init函数主要 做了路由匹配,具体如下:

Go 复制代码
func init() {
    // 设置 pprof 路由
    http.HandleFunc("/debug/pprof/", Index)
    http.HandleFunc("/debug/pprof/cmdline", Cmdline)
    http.HandleFunc("/debug/pprof/profile", Profile)
    http.HandleFunc("/debug/pprof/symbol", Symbol)
    http.HandleFunc("/debug/pprof/trace", Trace)

匿名导入了性能分析包pprof后就可以对该Web 程序进行性能分析;之后浏览器访问**http://localhost:8080/debug/pprof/**就可以查看相关的性能参数

Go 复制代码
package main

import (
	"encoding/json"
	"log"
	"net/http"
	_ "net/http/pprof" // 匿名导入 pprof
)

func main() {
	// 处理 /ping 请求
	http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
		response := map[string]string{"message": "hello world"}
		w.Header().Set("Content-Type", "application/json")
		if err := json.NewEncoder(w).Encode(response); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	// 启动服务器
	log.Println("Server starting on :8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatalf("Server failed to start: %v", err)
	}
}

Go语言中的数据库操作也使用了相同的方法,让用户导入程序所必需的驱动,例如MySQL的 驱动包, 该包的init函数所做的事就是注册MySQL驱动。

安装MySQL驱动 在终端执行:

Go 复制代码
go get -u github.com/go-sql-driver/mysql

注意事项:

  • 确保替换dbname中的用户名、密码、主机地址和数据库名为你的实际配置。
  • 如果连接成功,你会看到 "成功连接到MySQL数据库!" 的输出。
  • 记得处理可能出现的错误。
Go 复制代码
package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql" // 匿名导入MySQL驱动
)

func main() {
	// 配置数据库连接信息
	dbname := "root:password@tcp(127.0.0.1:3306)/dbname"

	// 打开数据库连接
	db, err := sql.Open("mysql", dbname)
	if err != nil {
		fmt.Println("数据库连接失败:", err)
		return
	}
	defer db.Close() // 确保在函数结束时关闭数据库连接

	// 测试连接
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库ping失败:", err)
		return
	}

	fmt.Println("成功连接到MySQL数据库!")

	// 这里可以添加其他数据库操作
	// 例如: 查询、插入、更新等
}
相关推荐
Am心若依旧40910 分钟前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
明月看潮生12 分钟前
青少年编程与数学 02-004 Go语言Web编程 20课题、单元测试
开发语言·青少年编程·单元测试·编程与数学·goweb
大G哥22 分钟前
java提高正则处理效率
java·开发语言
VBA633732 分钟前
VBA技术资料MF243:利用第三方软件复制PDF数据到EXCEL
开发语言
轩辰~34 分钟前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
小_太_阳43 分钟前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
向宇it44 分钟前
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
开发语言·unity·c#·编辑器·游戏引擎
智慧老师1 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
古希腊掌管学习的神1 小时前
[LeetCode-Python版]相向双指针——611. 有效三角形的个数
开发语言·python·leetcode
赵钰老师1 小时前
【R语言遥感技术】“R+遥感”的水环境综合评价方法
开发语言·数据分析·r语言