Go

15.Go语言包管理

2025-09-06  本文已影响0人  Surpassme

15.1 模块化

    用任何语言开发,如果软件规模扩大,会编写大量的函数、结构体、接口等。而这些代码不可能全部写在同一个文件中,因此就会产生大量的文件。如果这些文件是杂乱无章,则会造成名称冲突、重复定义、难以检索、无法引用、共享不便、版本管理、代码如何复用等一系列问题。因此需要有方案来解决类似问题,Go里面的方案是采用模块化管理来解决前述问题。

15.2 包

    模块,有时也称为包,它是某个功能或某个框架的基本单位。任何编程语言都会有一些内置包,它将一些基本功能封装为包形式提供给开发者使用,Go语言内置包可以在安装目录src文件夹查看。其主要特点如下所示:

    一般来说开发项目时,可以把功能相关的代码集中放置一个包里面。同一个目录就是同一个包,该包里面的变量、函数、结构体均互相可见,也可以直接使用。而跨目录则是跨包,使用时需要导入相应的包。

15.3 包管理

15.3.1 GOPATH

    Go 1.11版本之前,项目依赖包存于GOPATH。GOPATH是一个环境变量,它指向一个目录,用于存放项目依赖包的源码。其默认值为$HOME/go。开发的代码在GOPATH/src目录中,编译这个目录的代码,生成的二进制文件放置GOPATH/bin目录中。但存在以下问题:

15.3.2 GOPATH+vendor机制

    Go 1.5 引入Vendor机制。Vendor是将项目依赖包复制到项目中的vendor目录,在编译时使用项目中vendor目录的包进行编译,但依然不能解决不同项目依赖不同包版本问题。其中包的搜索顺序如下所示:

15.3.3 Go Modules

    Go Modules 是从Go 1.11版本之后引入,到1.13版本之后已经成熟,因此Go Modules已经成为官方的依赖包管理解决方案。其优势如下所示:

    我们通过GO111MODULE配置来控制Go Module模式是否开启,有以下三个选项

Go 1.13版本之后,默认开启。

15.4 常用内置包

    Go语言为我们提供了很多内置包,以下为一些日常开发常用的包,如下所示:

15.5 包命名

    在Go语言中,包是以文件夹形式表现的。其语法如下所示:

package 包名

    包命名规则如下所示:

在Go语言中有一个非常特殊的包main包,它不能被其他包导入,且里面必须存在main函数,否则程序没有入口,则无法运行。

15.6 包导入

    在Go语言中使用import导入包。导入的包只能是该包里面的导出标识符。其中导出标识符可以是变量、常量、类型、函数或方法、接口等。

    每个包在项目中都有唯一的导入路径,导入路径是告诉Go语言从哪里找到包,导入路径和包名称之间没有必然联系。如果一个程序需要导入多个包,每个包可以单独使用关键字importimport + 小括号 实现,包与包之间必须处于不同的行,示例如下所示:

// 导入包示例一:
import "fmt"
import "math"

// 导入包示例二:
import (
    "fmt"
    "math"
)

一般推荐方式二,代码比较简洁

    下面来演示一下,目录结构如下所示:

1501-导入包目录结构示例.png

    示例代码如下所示:

package calc

import "fmt"

func Add[T int | float32 | float64 | string](x, y T) T {
    return x + y
}

func Div[T int | float32 | float64](x, y T) T {
    if y == 0 {
        fmt.Println("除数为0")
    }
    return x / y
}
package log

import "log"

func PrintInfo(message any) {
    log.Print(message)
}

func PrintError(message any) {
    log.Fatal(message)
}

15.6.1 绝对导入

package main

import (
    "fmt"

    "surpass.net/tools/calc"
)

func main() {
    fmt.Println(calc.Add(10, 20))
}

15.6.2 别名导入

    如果在导入的包里面存在同名的包,则可以使用别名导入来避免冲突。在对包进行命名别名时,新名字必须在包前面,且两者之间使用空格间隔,示例使用方法如下所示:

package main

import (
    "fmt"

    c "surpass.net/tools/calc" // 将导入的包重新命名为 c
)

func main() {
    fmt.Println(c.Add("100", "200")) // 通过别名调用包里面的导出标识符
}

注意事项: 别名导入的包仅在当前go文件中有效,其他go文件在导入时还是以原有名称导入

15.6.3 相对导入

    这种方式不推荐使用

package main

import (
    "fmt"

    c "./tools/calc"
)

func main() {
    fmt.Println(c.Add("100", "200"))
}

如果是在启用了go.mod的环境使用相对导入,会提示main.go:6:2: "./tools/calc" is relative, but relative import paths are not supported in module mode

15.6.4 点导入

    这种方式适用于将包里面所有导出成员直接导入到本地,即在调用包里面的导出标识符时无须通过包名调用,但有可能会导致导入的标识符存在冲突,且同一个包在一行时不能同时使用别名导入和点导入。仍然不推荐使用

package main

import (
    "fmt"

    . "surpass.net/calc"
)

func main() {
    fmt.Println(Add("100", "200"))
    fmt.Println(Add(12, 13))
    fmt.Println(Div(12, 6))
}

15.6.5 匿名导入

    这种方式类似于别名导入,区别就是该别名为_,通过这种方式导入的包,则意味着该包无法使用。通常适用于在导入的包仅执行导入包里面的init()函数,其主要作用是做包的初始化。示例如下所示:

import (
    "fmt"

    _ "surpass.net/tools/calc"
)

15.6.6 导入本地其他项目

    如果将calc包放到其他项目时,如何导入呢?例如将calc放置到x:\calc,同时在calc目录使用go mod init surpass.net/calc。其go.mod内容如下所示:

module surpass.net/calc

go 1.22.5
module surpass.net

go 1.22.5


require (
    surpass.net/calc v1.0.0 // 后面的版本号可以随便定义,满足格式要求即可
)

replace surpass.net/calc => "F:\\calc" // replace 指令指定包的搜索路径,而不是去GOPATH/pkg/mod搜索

    go.mod一些参数的详细说明如下所示:

参考文档:https://golang.google.cn/ref/mod#go-mod-file-require

    在main.go中用法如下所示:

package main

import (
    "fmt"

    "surpass.net/calc"
)

func main() {
    fmt.Println(calc.Add("100", "200"))
    fmt.Println(calc.Add(12, 13))
    fmt.Println(calc.Div(12, 6))
}

    代码运行结果如下所示:

$ go run main.go
100200
25
2

15.6.7 导入第三方包

    日常开发过程中,除了自己写的包,也还有可能用到其他第三方的包,可以通过 https://pkg.go.dev/ 进行搜索和下载(例如:go get -u github.com/gin-gonic/gin),导入第三方包的示例代码如下所示:

package main

import (
  "net/http"

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

    在下载第三方包之后,会生成一个go.sum文件,在里面详细列举当前项目直接或间接依赖的所有模块版本,同时也带有详细的SHA-256值,以确保Go在售后的操作中保证项目所依赖的模块版本不会被修改。

15.7 init函数

    Go语言中有一个特殊的函数init(),它的执行优先级比main()函数要高,主要实现包的初始化。其具备以下特征:

    由于init()函数主要功能是初始化一些操作,但由于同一个包里面init()函数执行顺序是无序的。因此,除非有必要,不要在同一个包里面定义多个init()函数。另外,init()函数和main()函数也不一定在同一个文件夹里面。

    示例代码如下所示:

package calc

import "fmt"

func init() {
    fmt.Println("package calc 中的 init()函数")
}

func Add[T int | float32 | float64 | string](x, y T) T {
    return x + y
}

func Div[T int | float32 | float64](x, y T) T {
    if y == 0 {
        fmt.Println("除数为0")
    }
    return x / y
}
package log

import (
    "fmt"
    "log"
)

func init() {
    fmt.Println("package log 中的 init()函数")
}

func PrintInfo(message any) {
    log.Print(message)
}

func PrintError(message any) {
    log.Fatal(message)
}
package main

import (
    "fmt"

    _ "surpass.net/tools/calc"
    _ "surpass.net/tools/log"
)

func main() {
    fmt.Println("main - 测试init()函数")
}

    代码运行结果如下所示:

package calc 中的 init()函数
package log 中的 init()函数
main - 测试init()函数
上一篇 下一篇

猜你喜欢

热点阅读