• Go语言 包管理


    Go语言

    8. 包管理

    Go语言包的主要作用是把功能相似或相关的代码组织在同一个包中,以方便查找和使用。

    8.1 工作区

    Go语言中没有工程文件的概念,而是通过目录结构来体现工程的结构关系。Go代码必须放在工作区中。

    8.1.1 工作区结构

    工作区其实就是一个对应于特定工程的目录,它应包含三个子目录:src目录、pkg目录和bin目录。

    GO工作区:
     ├─bin
     │ ...
     ├─pkg
     │ └─windows_amd64
     │ ├─github.com
     │ ...
     └─src
     ├─github.com
     ...
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    各个目录作用

    • src目录

      用于以代码包的形式组织并保存Go源码文件(如.go、.c、.h、.s等),同时也是Go编译时查找代码的地方

    • pkg目录

      用于存放经由go get/install命令构建安装后的代码包的“.a”归档文件,也是编译生成的lib文件存储的地方。

    • bin目录

      与pkg目录类似,在通过go get/install命令完成安装后,保存由Go命令源码文件生成的可执行文件。

    目录src用于包含所有的源代码,是Go命令行工具一个强制的规则,而pkg和bin则无须手动创建,必要时Go命令行工具在构建过程中会自动创建这些目录。

    8.1.2 GOPATH

    GOPATH可以理解为工作目录或工作区,是平时接触最多的一个环境变量。它可以是一个目录,也可以是多个目录路径,每个目录代表一个工作区。

    Go的所有操作(编码、依赖管理、构建、测试、安装等)基本上都是围绕GOPATH来进行的。GOPATH必须要设置,但并不是固定不变的,GOPATH的目的是为了告知Go需要代码的时候去哪里查找。需要注意的是,这里的代码包括本项目和引用外部项目的代码。GOPATH可以随着项目的不同而重新设置。

    在实际开发环境中,工作目录往往有多个。这些工作目录的目录路径都需要添加至GOPATH。

    为了能够构建这个工程,需要先把所需工程的根目录加入到环境变量GOPATH中。否则,即使处于同一工作目录(工作区),代码之间也无法通过绝对代码包路径完成调用。

    使用命名行添加GOPATH

    查看当前的GOPATH

    在这里插入图片描述

    临时添D:\NewProject目录到GOPATH可使用set命令,Windows下使用分号作为分隔符:

    在这里插入图片描述

    使用set添加GOPATH,添加的是临时变量,当cmd窗口结束时,该变量就会结束。如果想要永久添加一个GOPATH路径,可以使用setx,只需要将如上命令的“set”更换成“setx”即可。

    如果GOPATH设置了多个工作区,那么查找依赖包时是以怎样的顺序进行呢?例如包a依赖包b,包b依赖包c,那么会先查找c包。那在工作区是如何查找这个依赖包c的呢?

    → 首先,在查找依赖包的时候,总是会先查找GOROOT目录,也就是Go语言的安装目录,如果没有找到依赖的包,才到工作区去找相应的包。在工作区中是按照设置的先后顺序来查找的,也就是会从第一个开始依次查找,如果找到就不再继续,如果没有找到就报错。

    go get会下载代码包到src目录,但是只会下载到第一个工作区目录。

    8.1.3 GOROOT

    GOROOT是Go语言的程序安装目录,并非必须要设置的。GOROOT的目的就是告知Go当前的安装位置,编译的时候从GOROOT去找SDK的system library。

    Windows下查看GOROOT

    在这里插入图片描述

    8.1.4 GOBIN

    GOBIN目录一般为GOPATH的可执行文件放置目录,一般指bin。默认安装的Go语言是没有设置GOBIN目录的。

    只有当环境变量GOPATH中只包含一个工作区的目录路径时,go install命令才会把命令源码安装到当前工作区的bin目录下。若环境变量GOPATH中包含多个工作区的目录路径,像这样执行go install命令就会失效,此时必须设置环境变量GOBIN。

    8.2 包的声明

    包是结构化代码的一种方式:每个程序都由包(通常简称为pkg)的概念组成,可以使用自身的包或者从其他包中导入内容。

    • Go语言的源代码以代码包为基本的组织单位
    • 在文件系统中,代码包是与文件目录一一对应的,文件目录的子目录也就是代码包的子包。
    • 在工作区中,一个代码包的导入路径实际上就是从src子目录到该包的实际存储位置的相对路径。

    每一个Go源文件的第一行都需要声明包的名称,声明一个包使用关键字package。

    每个Go文件都属于且仅属于一个包。一个包可以由许多以“.go”为扩展名的源文件组成,因此文件名和包名一般来说都是不相同的。

    代码包的名称一般会与源码文件所在的目录同名,如果不同名,在构建和安装的过程中会以代码包的名称为准。

    在Go语言中,代码包中的源码文件名可以是任意的。这些任意名称的源码文件都必须以包声明语句作为文件中的第一行,每个包都对应一个独立的名字空间。

    包中成员以名称首字母大小写决定访问权限:

    • public:首字母大写,可被包外访问
    • private:首字母小写,仅包内成员可以访问

    【注意】同一个目录下不能定义不同的package。

    8.3 包的导入

    在Go语言程序中,每个包都有一个全局唯一的导入路径。

    8.3.1 导入声明

    导入包需要使用关键字import,它会告诉编译器你想引用该位置的包中的代码,包的路径可以是相对路径,也可以是绝对路径。

    // 声明方式一
    import "fmt"
    import "time"
    // 声明方式二
    import (
     "fmt"
     "time"
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如上所示的标准包会在Go语言的安装位置下的src目录中找到,而其他包都需要在GOPATH环境变量中的目录下寻找,GOPATH指定的这些目录就是开发者的个人工作空间。

    如果在GOPATH中仍然没有找到所需的包,编译器在编译或运行时就会报错。

    8.3.2 远程导入

    Go语言不仅支持调用本地包,还支持调用远程服务器的包,例如托管在GitHub上的一个非常热门的Web框架gin,可以通过远程导入的方式将其导入使用。

    使用go get 命令下载远程包

    在这里插入图片描述

    package main
    
    import "github.com/gin-gonic/gin"
    
    func main() {
       r := gin.Default()
       r.GET("/ping", func(c *gin.Context) {
          c.JSON(200, gin.H{
             "message": "pong",
          })
       })
       r.Run()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    在这里插入图片描述

    成功返回

    在这里插入图片描述

    说明有人请求了服务

    8.3.3 别名导入

    在导入包时,可以指定包成员的访问方式,例如对包进行重命名,也就是使用别名的方式,避免导入两个相同包名的不同包而发生冲突。

    import print "fmt"
    
    • 1
    package main
    
    import (
       print "fmt"
       date "time"
    )
    
    func main() {
       print.Println("当前日期为:", date.Now().Day())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    8.3.4 匿名导入

    在Go语言中,如果导入了一个包而不使用,在编译时会产生“unused import”的编译错误。有时可能需要导入一个包,但不会引用到这个包的标识符,在这种情况下可以使用包的匿名导入的方式,就是使用下划线“_”来重命名该包

    import _“fmt”
    
    • 1

    性能分析案例

    package main
    
    import (
       "net/http"
       _ "net/http/pprof"
    )
    
    func ping(writer http.ResponseWriter, request *http.Request) {
       writer.Write([]byte("{\"message\":\"pong\"}"))
    }
    
    func main() {
       http.HandleFunc("/ping", ping)
       http.ListenAndServe(":8080", nil)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    8.4 main包

    在Go语言中,命名为main的包具有特殊的含义。Go语言的编译程序会试图把这种名字的包编译为二进制可执行文件。所有用Go语言编译的可执行程序都必须有一个名叫main的包,main包有且仅有一个。

    当编译器发现某个包的名字为main时,它一定也会发现名为main()的函数,否则不会创建可执行文件。main()函数是程序的入口,所以,如果没有这个函数,程序就没有办法开始执行。程序编译时,会使用声明main包的代码所在的目录名称作为可执行文件的文件名。

    一个应用程序可以包含不同的包,而且即使你只使用main包也不必把所有的代码都写在一个巨大的文件里,你可以用一些较小的文件,并且在每个文件非注释的第一行都使用package main来指明这些文件都属于main包。如果你打算编译包名不为main的源文件,如mypack,编译后产生的对象文件将会是mypack.a而不是可执行程序。

    注意,所有的包名都应该使用小写字母。

    8.5 包的初始化

    在Go语言里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。

    建议用户在一个package中,每个文件只写一个init函数

    每个package中的init函数都是可选的,但package main就必须包含一个main函数。

    每个包可以包含任意多个init(),这些函数都会在程序执行开始的时候被调用。所有被编译器发现的init()都会安排在main()之前执行。init()用于设置包、初始化变量或者其他要在程序运行前优先完成的引导工作。

    程序的初始化和执行都起始于main包。如果main包还导入了其他的包,那么就会在编译时将它们依次导入。

    当一个包被导入时,如果该包还导入了其他的包,那么会先将其他的包导入进来,然后再对该包的包级常量和变量进行初始化,最后执行init函数(如果存在)。

    8.6 包的命名

    每个包都有一个包名,包名一般是短小的名字,在包的声明处指定。通常来说,默认的包名就是包导入路径名的最后一段,因此即使两个包的导入路径不同,它们依然可能有一个相同的包名。

    包的命名规则:

    • 同目录下的源码文件的代码包声明语句要一致。
    • 源码文件声明的代码包的名称可以与其所在的目录的名称不同。

    8.7 依赖包管理

    Go在1.5版本引入了vendor属性(默认关闭,需要设置Go环境变量GO15VENDOREXPERIMENT=1),并在1.6版本中默认开启了vendor属性。

    vendor属性就是让Go在编译时,优先从项目源码树根目录下的vendor目录查找代码(可以理解为GOPATH),如果vendor中有,则不再去GOPATH中查找。

    有时候依赖包多了,逐个拷贝是非常费时费力的,因此在Go 1.11之后,官方发布了支持的版本管理工具mod。使用go mod命令即可运行mod工具。

    mod命令:

    download下载依赖的module到本地cache
    edit编辑go.mod文件
    graph打印模块依赖图
    init在当前文件夹下初始化一个新的module,创建go.mod文件
    tidy增加丢失的module,去掉未用的module
    vendor将依赖复制到vendor下
    verify校验依赖
    why解释为什么需要依赖

    当需要将所有外部依赖拷贝到vendor目录下时,只需要运行一条命令:

    go mod vendor
    
    • 1

    8.8 Go语言命名规范

    8.8.1 驼峰式命名法

    Go语言中,我们推荐使用驼峰式命名法来对各类标识符(变量、常量和结构体等)和文件进行命名。

    • 小驼峰命名法

      第一个单词以小写字母开始,其他单词的首字母则需要大写

      var sumNum int
      var firstName string
      var isValid bool
      var printValue func()
      
      • 1
      • 2
      • 3
      • 4
    • 大驼峰命名法

      每一个单词的首字母都采用大写

      var SumNum int
      var FirstName string
      var IsValid bool
      var PrintValue func()
      
      • 1
      • 2
      • 3
      • 4

    在进行测试用例的编写时,文件的命名不遵循驼峰式命名法,而是以xx_test.go的格式进行命名。

    测试文件scan_test.go中的TestScan函数。

    8.8.2 导出标识符

    Go语言中标识符根据首字母的大小写来确定可以访问的权限。

    如果首字母大写,则表示可以被其他包中的代码访问(可被导出);如果首字母小写,则表示该标识符只能在本包中使用(不可导出)。

    通俗认识:首字母大写的标识符是公有的,首字母小写的标识符是私有的。

    8.10 知识拓展(标准包简介)

    8.10.1 unsafe

    unsafe包含了一些打破Go语言“类型安全”的命令,一般的程序不会使用它,可用于C/C++ 程序的调用中。

    8.10.2 os-os/exec-syscall

    os:提供一个平台无关性的操作系统功能接口,采用类Unix设计,隐藏了不同操作系统间的差异,让不同的文件系统和操作系统对象表现一致。
    os/exec:提供给我们运行外部操作系统命令和程序的方式。
    syscall:底层的外部包,提供了操作系统底层调用的基本接口。

    8.10.3 archive/tar和/zip-compress

    压缩(解压缩)文件功能。

    8.10.4 fmt-io-bufio-path/filepath-flag

    fmt:提供了格式化输入输出功能。
    io:提供了基本输入输出功能,大多数是围绕系统功能的封装。
    bufio:缓冲输入输出功能的封装。
    path/filepath:用来操作在当前系统中的目标文件名路径。
    flag:对命令行参数的操作。

    8.10.5 strings-strconv-unicode-regexp-bytes

    strings:提供对字符串的操作。
    strconv:提供将字符串转换为基础类型的功能。

    unicode:为unicode型的字符串提供特殊的功能。
    regexp:正则表达式功能。
    bytes:提供对字符型分片的操作。

    8.10.6 math-math/cmath-math/rand-sort-math/big

    math:基本的数学函数。
    math/cmath:对复数的操作。
    math/rand:伪随机数生成。
    sort:为数组排序和自定义集合。
    math/big:大数的实现和计算。

    8.10.7 container-/list-ring-heap

    实现对集合的操作。
    list:双链表。
    ring:环形链表。

    8.10.8 time-log

    time:日期和时间的基本操作。
    log:记录程序运行时产生的日志。

    8.10.9 encoding/Json-encoding/xml-text/template

    encoding/Json:读取并解码和写入并编码JSON数据。
    encoding/xml:简单的XML 1.0解析器。
    text/template:生成像HTML一样的数据与文本混合的数据驱动模板。

    8.10.10 net-net/http-html

    net:网络数据的基本操作。
    http:提供了一个可扩展的HTTP服务器和客户端,解析HTTP请求和回复。
    html:HTML5解析器。

    8.10.11 runtime

    Go程序运行时的交互操作,例如垃圾回收和协程创建。

    8.10.12 reflect

    实现通过程序运行时反射,让程序操作任意类型的变量。

  • 相关阅读:
    CLion搭建Qt开发环境,并解决目录重构问题(最新版)
    动态规划——416. 分割等和子集
    题目 1053: 二级C语言-平均值计算
    javase----java基础面试题01-05
    JVM 基础篇:类加载器
    ffmpeg 任意文件读取漏洞/SSRF漏洞 (CVE-2016-1897/CVE-2016-1898)
    九、SpringMVC(3)
    linux使用ros打开奥比中光astra相机,查看红外图像
    leetcode 286 墙与门
    rancher部署nginx服务
  • 原文地址:https://blog.csdn.net/weixin_44226181/article/details/125899044