Skip to content

Files

Latest commit

9a65322 · Oct 10, 2022

History

History

lesson27

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
Nov 5, 2021
Nov 4, 2021
Nov 5, 2021
Jan 16, 2022
Oct 10, 2022

readme.md

包package和模块Module

package定义

package本质上就是一个目录,目录里包含有一个或者多个Go源程序文件,或者package。也就是说package里面还可以嵌套包含子package。

每个Go源文件都属于一个package,在源文件开头指定package名称

package package_name

package的代码结构示例如下:

image-20211104181754164

package里的变量、函数、结构体、方法等如果要被本package外的程序引用,需要在命名的时候首字母大写。

如果首字母小写,那就只能在同一个package里面被使用。

注意:这里说的是同一个package,不是同一个文件。同一个package下,如果有多个源程序文件是声明的该package,那这些源程序文件里的变量、函数、结构体等,即使不是首字母大写,也可以互相跨文件直接调用,不用额外import。

package的使用分为4类情况:

  • 使用Go标准库自带的package,比如fmt。
  • 使用go get获取到的第三方package/module
  • 使用项目内部的package
  • 使用其它项目的package/module

import语法示例

普通

import (
	"fmt"                           // 标准库
	"sync/atomic"                   // 标准库sync的atomic子package
	"package1"                      // 自开发的package
	"package2/package21"            // 自开发package,嵌套子package
	"package2/package22"            // 自开发package,嵌套子package
	"package3/package31/package311" // 自开发package,多重嵌套
)

使用import路径里面定义的package名称来访问package里的方法,结构体等,而不是路径名称。

举个例子,假设上面import的路径package2/package21这个目录下的Go源程序文件开头声明的package名称是realpackage,那访问这个package里的方法,结构体等要用realpackage.xxx来访问,而不是用package21.xxx来访问。

一句话总结:import的是路径,访问用package名称。最佳实践就是让两者保持一致。

别名

import (
    "fmt"
    newName "package2/package21"
)

可以用别名newName来访问package里的成员,newName.xxx。这个在包名很长或者包名有重复的时候可以用到。

点操作

import (
    "fmt"
    . "package2/package21"
)

.可以让后面的package里的成员注册到当前包的上下文,这样就可以直接调用成员名,不需要加包前缀了。

比如以前要用package21.Hello()来调用package21这个包里的函数Hello,用了点操作后,可以直接调用函数Hello(),前面不用跟package名称。

下划线

import (
    "fmt"
    _ "package2/package21"
)

下划线_的效果:只会执行包里各个源程序文件的init方法,没法调用包里的成员。

Go如何寻找import的package

在代码里import某个package的时候,Go是如何去寻找对应的package呢?这个和Go环境变量GO111MODULE有关系。GO111MODULE的值可以通过如下命令查到

go env | grep GO111MODULE

on表示开启,off表示关闭。GO111MODULE是从Go 1.11开始引入,在随后的Go版本中Go Modules的行为有一些变化,具体可以参考GO111MODULE and Go Modules

下面以Go1.16及以上版本详细讲下GO111MODULE关闭和开启的情况下,Go是如何寻找import的package的。

关闭GO111MODULE

  • 先从$GOROOT/src里找。$GOROOT是Go的安装路径,$GOROOT/src是Go标准库存放的路径,比如fmt, strings等package都存放在$GOROOT/src里。$GOROOT的路径可以通过下面的命令查看到:

    go env | grep ROOT // linux or mac
    go env | findstr ROOT // windows
  • 如果从$GOROOT/src找不到,再从$GOPATH/src里找。$GOPATH是安装Go后就会有的一个环境变量,Linux和Mac的默认路径是/Users/用户名/go,WIndows默认路径是C:/Users/用户名/go

    go env | grep PATH // linux or mac
    go env | findstr PATH // windows

    在Go 1.11之前,还没有Go Modules,如果想import一些自己开发的package,被import的package必须建在$GOPATH/src路径下。一般而言,一个工程项目一定会有自己写的若干个package,因此这也导致工程项目本身也通常建在了$GOPATH/src路径下。

开启GO111MODULE

Go 1.11开始,有了Go Modules,工程项目可以建在任何地方,代码在import某个package的时候,会按照如下顺序寻找package:

  • 先从$GOROOT/src/路径找。(Go标准库会在这个路径找到)

  • 再从$GOPATH/pkg/mod/路径找。(Go第三方库会在这个路径找到)

  • 如果都找不到,再看当前项目有没有go.mod文件,有的话就从go.mod文件里指定的模块所在路径往下找。如果没有go.mod文件,那就直接提示package xxx is not in GOROOT。(自己开发的本地库可以通过这个方式找到)

官方推荐使用Go Modules,从Go1.16版本开始,GO111MODULE环境变量默认开启为on模式。

使用示例

不开启GO111MODULES时import package

  1. 项目建在$GOPATH/src下面
  2. import package的时候路径从$GOPATH/src往下找

使用说明参考gopath package demo

开启GO111MODULES时import本项目里的package

  1. 项目可以建在任何地方

  2. 在项目所在根目录创建go.mod文件, module_name是模块名称

    go mod init module_name
  3. import项目本地的package时指定go.mod文件里的模块名称

    比如module_name叫project,在这个模块里,main.go使用了本项目里的util包,那在main.go里按照如下格式import这个package

    import "project/util" // project是模块名称, util是这个模块下的本地package

使用说明参考module package demo

开启GO111MODULES时import第三方开发的Module

  1. 项目可以建在任何地方

  2. 在项目所在根目录创建go.mod文件

    go mod init module_name
  3. 下载所需第三方Module,比如gin

    go get -u github.com/gin-gonic/gin
  4. 代码里import对应的Module

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

tips: go mod tidy命令可以更新go.mod里的依赖内容,比如go.mod里少写了依赖的module,就可以执行该命令自动更新go.mod,在go.mod所在目录执行如下命令即可:

go mod tidy

开启GO111MODULES时import本地的Module

首先,Go官方并不推荐这种用法。import本地的module需要借助replace指令来实现。

举个例子,本地有2个模块module1module2module1要使用module2里的Add函数,目录结构为:

replace_module_demo
|
|------module1
|        |---main.go
|        |---go.mod        
|------module2
|        |---func.go
|        |---go.mod

module1要使用module2里的Add函数,需要做2个事情:

  • module1代码里添加对 module2的import。具体表现为下面的代码示例里module1/main.go里import了module2
  • module1go.mod里添加requirereplace指令,把对module2的import通过replace指令指向本地的module2路径。具体参考module1/go.mod里的require和replace指令。

代码如下:

module1/main.go

package main

import (
	"fmt"
	// 模块module1要使用本地模块module2里的Add函数
	// 这里被import的本地模块的名称要和module2/go.mod里保持一致
	"module2"
)

func main() {
	a := 1
	b := 2
	sum := module2.Add(a, b)
	fmt.Printf("sum of %d and %d is %d\n", a, b, sum)
}

module1/go.mod,注意require后面的module必须指定版本号,版本号以 v开头,后面是由.分隔的3个整数组成,比如v1.0.0。3个整数从左往右,分别表示大版本号(major version),小版本号(minor version)和补丁版本(patch version)。

module module1

go 1.16

require module2 v1.0.0

replace module2 => ../module2

module2/func.go

package module2

func Add(a, b int) int {
	return a + b
}

module2/go.mod

module module2

go 1.16

在module1路径下执行go run main.go,结果为

sum of 1 and 2 is 3

代码开源地址:demo for using local module

init函数

init函数没有参数,没有返回值。

  • 一个package里可以有多个init函数(分布在单个源程序文件中或者多个源程序文件中),并且按照它们呈现给编译器的顺序被调用。
  • init函数不能被显式调用,在main()函数执行之前,自动被调用
  • 同一个pacakge里的init函数调用顺序不确定
  • 不同package的init函数,根据package import的依赖关系来决定调用顺序,比如package A里import了package B,那package B的init()函数就会比package A的init函数先调用。
  • 无论package被import多少次,package里的init函数只会执行一次

注意事项

  • package目录名和package目录下的Go源程序文件开头声明的包名可以不一样,不过一般还是写成一样,避免出错。

  • 禁止循环导入package。

  • Go Module版本号规则:https://go.dev/ref/mod#versions,版本号不符合规则就是非法版本,会导致编译失败。

References