Contents

golang-学习笔记-包

go官方文档 go语言参考

在写过很多go代码之后,感觉自己并没有完全掌握go语言,还有很多知识盲区,所以有了这个go学习笔记系列,本系列是作者跟着电子书重新复习go语言相关内容的笔记

包和文件

Go语言中的包和其他语言的库或模块的概念类似,目的都是为了支持模块化、封装、单独编译和代码重用。

一个包的源代码保存在一个或多个以.go为文件后缀名的源文件中,通常一个包所在目录路径的后缀是包的导入路径

每个包都对应一个独立的名字空间

包还可以让我们通过控制哪些名字是外部可见的来隐藏内部实现信息:大写可见

包简介

导入路径

每个包是由一个全局唯一的字符串所标识的导入路径定位(支持相对路径和绝对路径以及网络资源)

默认的包名就是包导入路径名的最后一段,因此两个包的导入路径不同,可能有一个相同的包名。

也有三种例外情况:

  • main包本身的导入路径是无关紧要的。名字为main的包是给go build(§10.7.3)构建命令一个信息,这个包编译完之后必须调用连接器生成一个可执行程序
  • 以_test为后缀包名的测试外部扩展包都由go test命令独立编译,普通包和测试的外部扩展包是相互独立的。
  • 一些依赖版本号的管理工具会在导入路径后追加版本号信息,例如"gopkg.in/yaml.v2"。这种情况下包的名字并不包含版本号后缀,而是yaml。

导入声明

1
2
3
4
import (
    "crypto/rand"
    mrand "math/rand" // alternative name mrand avoids conflict
)

不允许循环导入,比如a导入b,b又导入a

包的匿名导入

如果只是导入一个包而并不使用导入的包将会导致一个编译错误。匿名导入执行包级变量的初始化表达式和执行导入包的init初始化函数。

1
import _ "image/png" // register PNG decoder

包和命名

包名一般采用单数的形式。标准库的bytes、errors和strings使用了复数形式,这是为了避免和预定义的类型冲突,同样还有go/types是为了避免和type关键字冲突。

当设计一个包的时候,需要考虑包名和成员名两个部分如何很好地配合。下面有一些例子:

bytes.Equal    flag.Int    http.Get    json.Marshal

工具

工作区结构

GOPATH/
    src/
        gopl.io/
            .git/
            ch1/
                helloworld/
                    main.go
                dup/
                    main.go
                ...
        golang.org/x/net/
            .git/
            html/
                parse.go
                node.go
                ...
    bin/
        helloworld
        dup
    pkg/
        darwin_amd64/
        ...

go mod 包管理方式使用同一个GOPATH变量存储下载的包和编译的可执行文件,并支持在任意位置创建项目。

internal包

Go语言1.4版本后增加了 Internal packages 特征用于控制包的导入,即internal package只能被特定的包导入。

内部包的规范约定:导入路径包含internal关键字的包,只允许internal的父级目录及父级目录的子包导入,其它包无法导入。

标准库概述

  • unsafe: 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。
  • syscall-os-os/exec:
    • os:提供给我们一个平台无关性的操作系统功能接口
    • os/exec: 提供我们运行外部操作系统命令和程序的方式
    • syscall: 底层的外部包,提供了操作系统底层调用的基本接口
  • archive/tar 和 /zip-compress:压缩(解压缩)文件功能
  • fmt-io-bufio-path/filepath-flag
    • fmt: 提供了格式化输入输出功能
    • io: 提供了基本输入输出功能
    • bufio: 缓冲输入输出功能的封装
    • path/filepath: 用来操作在当前系统中的目标文件名路径
    • flag: 对命令行参数的操作
  • strings-strconv-unicode-regexp-bytes
    • strings: 提供对字符串的操作
    • strconv: 提供将字符串转换为基础类型的功能
    • unicode: 为 unicode 型的字符串提供特殊的功能
    • regexp: 正则表达式功能
    • bytes: 提供对字符型分片的操作
  • math-math/cmath-math/big-math/rand-sort
    • math: 基本的数学函数
    • math/rand: 伪随机数生成
    • sort: 为数组排序和自定义集合
    • math/big: 大数的实现和计算
  • container-/list-ring-heap
    • list: 双链表

遍历一个链表(当 l 是 *List)

1
2
3
for e := l.Front(); e != nil; e = e.Next() {
    //do something with e.Value
}
  • ring: 环形链表
  • heap:二叉堆
  • time-log
    • time: 日期和时间的基本操作。
    • log: 记录程序运行时产生的日志,我们将在后面的章节使用它
  • encoding/json-encoding/xml-text/template
  • net-net/http-html
  • runtime: Go 程序运行时的交互操作,例如垃圾回收和协程创建
  • reflect: 实现通过程序运行时反射,让程序操作任意类型的变量

使用github开发自己的模块并发布

在本地新建项目,模块名为github.com/username/modelname,开发完后发布到github上

尽量不要在模块中使用-符号,如果使用了上传包到github没问题,引入包的时候必须得给包起别名,不然无法使用

1
import Oublie "github.com/oublie6/Oublie-Go/v2"

阿里云开发者社区 Golang模块版本管理

本地模块版本号与git tag相关联

https://ucc.alicdn.com/pic/developer-ecology/5e5a12d73d6b48e4a291a95238ad9579.png

CSDN ITqingliang 发布breaking changes

通过创建version分支和tag进行版本升级,直接创建一个新的分支,在新的分支中将go.mod文件的模块名修改为xxx/v2,xxx是原来的模块名,然后打上标签v2.x.x,再推送到远程仓库即可

CSDN 倦~ Go的多模块开发

本地开发多模块项目时,本地依赖的项目需要推送到远程仓库才能得到更新,相当于推送到仓库再下载到pkg里面

后来有了go mod replace,可以在开发时先将本地依赖修改为本地的包,后面再吧go.sum里面的replace删掉再发布

go1.18推出了go work,workspace空目录里面新建的模块,然后再workspace目录使用go work init初始化,并使用go work use 模块名添加需要本地开发的多模块,之后加入work的模块都会本地引用,goland也会自动识别。开发和发布可以无缝衔接。

无忌 github pages博客 go 官方博客go work最佳实践

在工作区目录(通常是GOPATH目录,因为其实工作区类似于开启一个新的GOPATH)维护一个go.work文件来管理所有依赖。go.work里的use和replace指令会覆盖工作区里指定的Go Module的go.mod文件,因此就无需修改Go Module的go.mod文件了。

go work 命令

  • go work init:创建一个go.work
  • go work use [-r] moddir:将本地模块添加到go.work,-r会递归添加,另外这个命令会对已经删除的模块取消use
  • go work sync:把go.work文件里的依赖同步到workspace包含的Module的go.mod文件中
  • go work edit:提供了用于修改go.work的命令行接口,常用选项有-use和-dropuse

还可以通过修改go.work文件来,go.work里面的内容有

  • go:go的版本
  • use:添加的本地模块,use的顺序表示上游模块到下游模块的
  • replace:和go.mod里面的replace类似,它会覆盖或添加到本地模块的go.mod

无法加载第三方包

polarisxu 通过一个例子让你彻底掌握 Go 工作区模式

使用go work sync命令

其实gowork仅仅是通过GOWORK这个环境变量来查看是否使用gowork,所以当我们想不使用gowork时可以将环境变量GOWORK设置为off

linux里面可以仅对一个命令传入环境变量:GOWORK=off go run main.go

windows里面使用set来设置在当前终端有效的临时环境变量:set GOWORK=off

或者显示环境变量:set GOWORK

或者取消环境变量:set GOWORK=

之后就可以:go run main.go

go mod包管理

脚本之家

go.mod和go.sum需要提交到git里面

历史

使用go path问题

  • 代码开发必须在go path src目录下,不然,就有问题。
  • 依赖手动管理
  • 依赖包没有版本可言

go path不算包管理工具

govendor

  • 解决了包依赖,一个配置文件就管理
  • 依赖包全都下载到项目vendor下,每个项目都把有一份。

CSDN 想变厉害的大白菜

从Go1.6起,默认开启 vendor 目录查找,vendor 机制就是在包中引入 vendor 目录,将依赖的外部包复制到 vendor 目录下,编译器在查找外部依赖包时,优先在 vendor 目录下查找

  • govendor add/update 命令从 $GOPATH 中复制依赖包
  • govendor fetch 添加或更新依赖包
  • govendor migrate 从其他 vendor 包管理工具中一键迁移到 govendor

gomod

go modules 是 golang 1.11 新加的特性

模块是相关Go包的集合。modules是源代码交换和版本控制的单元。 go命令直接支持使用modules,包括记录和解析对其他模块的依赖性。modules替换旧的基于GOPATH的方法来指定在给定构建中使用哪些源文件。

go.mod

Go.mod 记录了当前项目使用的第三方包,包名、版本号等关键信息。一个模块在go项目中的根目录上生成go.mod文件,go.mod包含四个指令 module, require, replace, exclude

  • Module 表明当前模块名称
  • Require 模块的依赖信息,包含直接、间接的依赖
  • Replace 指定依赖(后者)去代替前者
  • Exclude 用来忽略某些特定版本

go.sum

其中包含特定模块版本内容的预期加密哈希,Go命令使用go.sum文件确保这些模块的未来下载检索与第一次下载相同,以确保项目所依赖的模块不会出现意外更改,无论是出于恶意、意外还是其他原因。 go.mod和go.sum都应检入版本控制。

配置

GO111MODULE 有三个值:off, on和auto(默认值)。

  • GO111MODULE=off,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。
  • GO111MODULE=on,go命令行会使用modules,而一点也不会去GOPATH目录下查找。
  • GO111MODULE=auto,默认值,go命令行将会根据当前目录来决定是否启用module功能。这种情况下可以分为两种情形:当前目录在GOPATH/src之外且该目录包含go.mod文件,当前文件在包含go.mod文件的目录下面。

当modules功能启用时,依赖包的存放位置变更为$GOPATH/pkg,允许同一个package多个版本并存,且多个项目可以共享缓存的 module

go mod命令

完整见原文或go官网

常用的有:

  • init,初始化项目
  • tidy,自动清除不用的依赖添加使用的依赖
  • edit,编辑go.mod文件
  • vendor 当依赖copy到 vendor目录下
  • why 查看项目中为什么依赖某个包或者模块
  • graph 查看模块的依赖图

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

由于某些已知的原因,并不是所有的package都能成功下载,modules 可以通过在 go.mod 文件中使用 replace 指令替换成其他的库:

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

使用命令:

1
go mod edit -replace [old git package]@[version]=[new git package]@[version]

如果是没有被代码引用的包,我们需要用在go.mod包中使用exclude提示go编译器这些包可以不被代码引用

1
2
3
exclude(
  git.woa.com/zhiyan-monitor/sdk/go-sdk/v3 v3.0.7
)

本地开发库的时候可以使用这个命令将导入包改为本地包,或者用上面说的go work

1
replace code.byted.org/gopkg/gorm => /User/name/go/src/xxx/

清理go mod的缓存

1
go clean -modcache

go mod 额外信息

1
2
                                              #commit日期     commit id
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c

go get 命令可选version

  • v0.1.1 版本形式:go get code.byted.org/gopkg/gorm@v1.0.1
  • git commit hash:go get code.byted.org/gopkg/gorm@ead2320ad9
  • git分支:go get code.byted.org/gopkg/gorm@anybranch
  • < >, <=, >=用于超过/小于的版本包:go get code.byted.org/gopkg/gorm@<=1.2.1
  • version为latest时,对于有tags的package,会用最新的tag,如果package没有tags,则用master最新的commit

多个main函数

一个包里面不要有多个main函数

把不同的main函数放到不同的文件夹(新的包)下即可,他们可以共同依赖之前包

下载更新包

1
2
3
4
//下载
go get xxxxx@yyy
//更新
go get -u xxx@yyy

当go mod tidy出错时

通常情况如果你把原来的项目的代码添加到新的项目,运行go mod tidy出错时,通常是版本的问题

因为go mod tidy都是直接取的latest版本,所以我们需要看老项目使用的是哪个版本,然后用

1
go get xxx@yyy

来安装特定版本的包,之后再go mod tidy就行了

更新所有的依赖

1
go get -u
 |