Go:包管理 & 包使用

目录

前言

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

1.1 工作区(Workspace)

1.2 GOROOT & GOBIN

1.3 什么是GOPATH?

1.4 GOPATH的使用

1.5 GOPATH的现状&缺陷

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

2.1 Go Modules 的概念

2.2 Go Modules > GOPATH 

2.3 go.mod & go.sum

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

2.4.1 工作区模式(Workspace mode)

2.4.2 go.work 文件

三、包的介绍

3.1 包的概念

3.2 包的声明 & 初始化

3.2.1 包的声明

3.2.2 包的初始化

3.2.3 mian包

3.2.4 init() & main()

3.3 包的加载顺序

四、包的导入

4.1 导入声明

4.2 GO标准库

4.3 本地导入

4.4 远程导入

4.5 别名导入

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目 录。如下所示:

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 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路劲:

go env GOPATH

1.4 GOPATH的使用

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

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

go get github.com/user/project

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

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 项目开发变得更加可控和高效。

GOPATHGo 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.modgo.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 库和工具库。

my-project/
  ├── app/
  │   └── go.mod
  ├── api-lib/
  │   └── go.mod
  ├── utils/
  │   └── go.mod
  └── go.work

go.work 内容:

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包:

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)。

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

go_example/
├── main.go
├── pkg1/pkg1.go
├── pkg2/pkg2.go
├── pkg3/pkg3.go
└── go.mod  # 无需手动创建
  • go_example 目录中初始化 Go module:
go mod init go_example
go mod tidy
  • 创建包文件
// 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 run main.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关键字导入其他包。
  • 可以使用相对路径或完整的包路径导入。
// 声明方式一
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文件

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

在 "main" 文件夹中创建一个名为 "main.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" 的文件

// 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:
go mod init go_example
go mod tidy

构建并运行程序

  1. 在命令提示符或终端中,确保您仍在 "go_example" 文件夹中
  2. 运行以下命令来构建和运行程序:
go run main/main.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 mod init your_project_name

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

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

3. 在导入之前还需要使用go get命令下载远程包

go get github.com/gin-gonic/gin

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

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服务器,并且可以看到输出了如下提示信息:

[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框架,这里只作简要说明)

{
    "message": "pong"
}

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

4.5 别名导入

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

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

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())
  • 注册包的副作用
import _ "package_name"

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

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/ 就可以查看相关的性能参数

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 get -u github.com/go-sql-driver/mysql

注意事项:

  • 确保替换dbname中的用户名、密码、主机地址和数据库名为你的实际配置。
  • 如果连接成功,你会看到 "成功连接到MySQL数据库!" 的输出。
  • 记得处理可能出现的错误。
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数据库!")

	// 这里可以添加其他数据库操作
	// 例如: 查询、插入、更新等
}
  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值