1. 包 (package)

Go言语是运用包来组织源代码的,包 (package) 是多个 Go 源码的调集,是一种高档的代码复用计划

Go言语中为咱们供给了许多内置包,如 fmt、os、io 等

任何源代码文件有必要归于某个包,一起源码文件的榜首行有效代码有必要是 package packageName 句子,经过该句子声明自己地点的包

1.1 包的基本概念

Go言语的包借助了目录树的组织形式,一般包的称号便是其源文件地点目录的称号,虽然Go言语没有强制要求包名有必要和其地点的目录名同名,但还是主张包名和地点目录同名,这样结构更明晰

包能够界说在很深的目录中,包名的界说是不包括目录途径的,可是包在引证时一般运用全途径引证

包的习惯用法:

  • 包名一般是小写的,运用一个简略且有意义的称号
  • 包名一般要和地点的目录同名,也能够不同,包名中不能包括- 等特殊符号
  • 包名为 main 的包为应用程序的进口包,编译不包括 main 包的源码文件时不会得到可履行文件
  • 一个文件夹下的一切源码文件只能归于同一个包,相同归于同一个包的源码文件不能放在多个文件夹下

1.2 包的导入

要在代码中引证其他包的内容,需求运用 import 关键字导入运用的包。详细语法如下:

import "包的途径"

留意事项:

  • import 导入句子通常放在源码文件最初包声明句子的下面
  • 导入的包名需求运用双引号包裹起来

包的导入有两种写法,分别是单行导入和多行导入

单行导入

import "包 1 的途径"
import "包 2 的途径"

多行导入

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

1.3 包的导入途径

包的绝对途径便是 GOROOT/src/GOPATH 后面包的存放途径,如下所示:

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

上面代码的含义如下:

  • test 包是自界说的包,其源码坐落 GOPATH/lab/test 目录下
  • driver 包的源码坐落 GOROOT/src/database/sql/driver 目录下
  • sql 包的源码坐落 GOROOT/src/database/sql 目录下

1.4 包的引证格局

包的引证有四种格局,下面以 fmt 包为例来分别演示一下这四种格局。

  1. 规范引证格局

    import "fmt"
    

    此刻能够用fmt.作为前缀来运用 fmt 包中的办法,这是常用的一种办法

    package main
    import "fmt"
    func main() {
        fmt.Println("Chris Liu")
    }
    
  2. 自界说别号引证格局

    在导入包的时候,咱们还能够为导入的包设置别号,如下所示:

    import F "fmt"
    

    其中 F 便是 fmt 包的别号,运用时咱们能够运用F.来代替规范引证格局的fmt.来作为前缀运用 fmt 包中的办法

    package main
    import F "fmt"
    func main() {
        F.Println("Chris Liu")
    }
    
  3. 省略引证格局

    import . "fmt"
    

    这种格局相当于把 fmt 包直接合并到当时程序中,在运用 fmt 包内的办法是能够不必加前缀fmt.,直接引证

    package main
    import . "fmt"
    func main() {
        //不需求加前缀 fmt.
        Println("Chris Liu")
    }
    
  4. 匿名引证格局

    在引证某个包时,假如仅仅期望履行包初始化的 init 函数,而不运用包内部的数据时,能够运用匿名引证格局,如下所示:

    import _ "fmt"
    

    匿名导入的包与其他办法导入的包相同都会被编译到可履行文件中

    运用规范格局引证包,可是代码中却没有运用包,编译器会报错

    假如包中有 init 初始化函数,则经过import _ "包的途径" 这种办法引证包,仅履行包的初始化函数,即使包没有 init 初始化函数,也不会引发编译器报错

    package main
    import (
        _ "database/sql"
        "fmt"
    )
    func main() {
        fmt.Println("Chris Liu")
    }
    

留意:

  • 一个包能够有多个 init 函数,包加载时会履行悉数的 init 函数,但并不能保证履行次序,所以不主张在一个包中放入多个 init 函数,将需求初始化的逻辑放到一个 init 函数里边
  • 包不能出现环形引证的状况,比方包 a 引证了包 b,包 b 引证了包 c,假如包 c 又引证了包 a,则编译不能经过
  • 包的重复引证是答应的,比方包 a 引证了包 b 和包 c,包 b 和包 c 都引证了包 d。这种场景相当于重复引证了 d,这种状况是答应的,而且 Go 编译器保证包 d 的 init 函数只会履行一次

2. init函数

Go 言语中 init 函数用于包 (package) 的初始化,该函数是 Go 言语的一个重要特性

2.1 init函数的特性

  • init 函数先于 main 函数主动履行
  • 每个包中能够有多个 init 函数,每个包中的源文件中也能够有多个 init 函数
  • init 函数没有输入参数、回来值,也未声明,所以无法引证
  • 不同包的 init 函数依照包导入的依靠关系决定履行次序
  • 不管包被导入多少次,init 函数只会被调用一次,也便是只履行一次

2.2 init函数的履行次序

Go语言基础必备知识点(九) 包(package)、init函数、go mod篇

这张图片很明晰的反应了init函数的加载次序:

  • 包加载优先级排在榜首位,先层层递归进行包加载
  • 每个包中加载次序为:const > var > init,首要进行初始化的是常量,然后是变量,最终才是init函数

一句话总结:

从当时包开端,假如当时包包括多个依靠包,则先初始化依靠包,层层递归初始化各个包,在每一个包中,依照源文件的字典序早年往后履行,每一个源文件中,优先初始化常量、变量,最终初始化init函数,当出现多个init函数时,则依照次序早年往后顺次履行,每一个包完结加载后,递归回来,最终再初始化当时包!

2.3 init函数的运用场景

init 函数的运用场景还是挺多的,比方进行服务注册、进行数据库或各种中间件的初始化衔接等

Go 的规范库中也有许多地方运用到了 init 函数,比方常常运用的 pprof 工具就运用到了 init 函数,在 init 函数里边进行路由注册:

//go/1.15.7/libexec/src/cmd/trace/pprof.go
func init() {
    http.HandleFunc("/io", serveSVGProfile(pprofByGoroutine(computePprofIO)))
    http.HandleFunc("/block", serveSVGProfile(pprofByGoroutine(computePprofBlock)))
    http.HandleFunc("/syscall", serveSVGProfile(pprofByGoroutine(computePprofSyscall)))
    http.HandleFunc("/sched", serveSVGProfile(pprofByGoroutine(computePprofSched)))
    http.HandleFunc("/regionio", serveSVGProfile(pprofByRegion(computePprofIO)))
    http.HandleFunc("/regionblock", serveSVGProfile(pprofByRegion(computePprofBlock)))
    http.HandleFunc("/regionsyscall", serveSVGProfile(pprofByRegion(computePprofSyscall)))
    http.HandleFunc("/regionsched", serveSVGProfile(pprofByRegion(computePprofSched)))
}

3. *go mod

go module 是 Go 言语从 1.11 版别之后官方推出的版别办理工具,而且从 Go 1.13 版别开端,go module 成为了 Go 言语默许的依靠办理工具

Modules 官方界说为:

Modules 是相关 Go 包的调集,是源代码交换和版别操控的单元 Go言语指令直接支持运用 Modules,包括记录和解析对其他模块的依靠性Modules 替换旧的基于 GOPATH 的办法,来指定运用哪些源文件

Go 最新版别运用 go module 现已不需求设置环境变量

go mod 有以下指令:

指令 说明
download download modules to local cache(下载依靠包)
edit edit go.mod from tools or scripts (编辑go.mod)
graph print module requirement graph (打印模块依靠图)
init initialize new module in current directory (在当时目录初始化mod)
tidy add missing and remove unused modules(拉取缺少的模块,移除不必的模块)
vendor make vendored copy of dependencies(将依靠复制到vendor下)
verify verify dependencies have expected content (验证依靠是否正确)
why explain why packages or modules are needed(解释为什么需求依靠)
  • 常用的有 init tdiy edit

运用go get指令下载指定版别的依靠包:

履行go get 指令,在下载依靠包的一起还能够指定依靠包的版别

  • 运转go get -u指令会将项目中的包升级到最新的次要版别或许修订版别
  • 运转go get -u=patch指令会将项目中的包升级到最新的修订版别
  • 运转go get [包名]@[版别号]指令会下载对应包的指定版别或许将对应包升级到指定的版别

提示: go get [包名]@[版别号]指令中版别号 能够是 x.y.z 的形式,例如 go get foo@v1.2.3 也能够是 git 上的分支或 tag,例如 go get foo@master 还能够是 git 提交时的哈希值,例如 go get foo@e3702bed2

3.1 项目中运用

  1. 在 GOPATH 目录下新建一个目录,并运用go mod init初始化生成 go.mod 文件

    go.mod 文件一旦创立后,它的内容将会被 go toolchain 全面掌控,go toolchain 会在各类指令履行时,比方go getgo buildgo mod等修改和保护 go.mod 文件

    go.mod 供给了 module、require、replace 和 exclude 四个指令:

    • module 句子指定包的姓名 (途径)
    • require 句子指定的依靠项模块
    • replace 句子能够替换依靠项模块
    • exclude 句子能够忽略依靠项模块

    初始化生成的 go.mod 文件如下所示:

    module mypro
    go 1.19
    
  2. 增加依靠

    新建一个 main.go 文件,写入以下代码:

    package main
    import (
        "net/http"
        "github.com/labstack/echo"
    )
    func main() {
        e := echo.New()
        e.GET("/", func(c echo.Context) error {
            return c.String(http.StatusOK, "Hello, World!")
        })
        e.Logger.Fatal(e.Start(":1323"))
    }
    

    履行go mod tidy运转代码会发现 go mod 会主动查找依靠主动下载

    go: finding module for package github.com/labstack/echo
    go: found github.com/labstack/echo in github.com/labstack/echo v3.3.10+incompatible
    go: finding module for package github.com/stretchr/testify/assert
    go: finding module for package github.com/labstack/gommon/log
    go: finding module for package github.com/labstack/gommon/color
    go: finding module for package golang.org/x/crypto/acme/autocert
    go: found github.com/labstack/gommon/color in github.com/labstack/gommon v0.3.1
    go: found github.com/labstack/gommon/log in github.com/labstack/gommon v0.3.1
    go: found golang.org/x/crypto/acme/autocert in golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
    go: found github.com/stretchr/testify/assert in github.com/stretchr/testify v1.7.0
    

    go.mod中的内容:

    module mypro
    go 1.19
    require github.com/labstack/echo v3.3.10+incompatible
    require (
    	github.com/labstack/gommon v0.3.1 // indirect
    	github.com/mattn/go-colorable v0.1.11 // indirect
    	github.com/mattn/go-isatty v0.0.14 // indirect
    	github.com/valyala/bytebufferpool v1.0.0 // indirect
    	github.com/valyala/fasttemplate v1.2.1 // indirect
    	golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
    	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
    	golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
    	golang.org/x/text v0.3.6 // indirect
    )
    

    go module 安装 package 的原则是先拉取最新的 release tag,若无 tag 则拉取最新的 commit

    go 会主动生成一个 go.sum 文件来记录 dependency tree

    履行脚本go run main.go,就能够运转项目

    能够运用指令go list -m -u all来检查能够升级的 package,运用go get -u need-upgrade-package升级后会将新的依靠版别更新到 go.mod

    比方:go get -u github.com/labstack/gommon

    也能够运用go get -u升级一切依靠

  3. 一般运用包之前,是首要履行go get指令,先下载依靠 比方 github.com/labstack/echo

运用 replace 替换无法直接获取的 package:

由于某些已知的原因,并不是一切的 package 都能成功下载,比方:golang.org 下的包

modules 能够经过在 go.mod 文件中运用 replace 指令替换成 github 上对应的库,比方:

replace (
    golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)

或许

replace golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a

go install指令将项目打包安装为可履行文件,在安装在GOPATH的bin目录下,go install履行的项目 有必要有main办法