Jochen的golang小抄-进阶篇-package

小抄系列进阶篇涉及的概念较多,知识点重要,故每块知识点独立成篇,方便日后笔记的查询

本篇的主题是:包

包(package)

写项目时,如果把所有的代码写到一个go文件中,那么这个文件就会变得臃肿难看,难以维护,此时我们就需要引入包使用

包本质上就是个目录,里面有很多个go文件

定义包

src目录是以代码包的形式组织并保存Go源代码文件的,每个包在src目录下都有相应的包名目录与之一一对应(go mod模式下,代码不需要放在在src目录下,随处可以创建项目,这里说的还是原来gopath内 创建项目的管理方式)

代码包包名和文件目录名可以不一致,但是相同目录下的每个源文件第一行所定义的所属包必须一致

在同一个包目录下,所有的.go文件的第一行需要添加包的定义,程序根据此来标记该源码文件的所属,包的定义使用package关键字:

package 包名

建议:同一个包目录下的源码文件的包定义为目录名,方便管理

main包

main包:

  • go语言的程序的入口main()函数所在的包
  • 在main包下,如果想要引用别的包下定义的代码,需要使用import关键字,此操作称之为导包

需要注意的是,main包为程序的入口包,其他包是不能使用的

使用包

要引用其他包内定义的代码,需要使用improt关键字,如果使用的是goland IDE这过程会帮我们自动化导入,在前面的过程应该见得挺多了,其用法如下:

import "package" //导入单个包
import (
	"package1"
    "pack2age"
	) //导入多个包

在导入包时,会从GOROOT和GOPATH两个环境变量设置的目录中去搜索src/下是否有这个包在

GOROOT:GO内置的包所在路径

GOPATH:自定义包所在路径

如果导入的是GOPATH中的包,导入的包名需要包含为其在src目录下的路径

如src下有如下目录结构:

1_package 
├── main.go 
└── utills 
   └── util.go
  • 1_package为工程目录
  • utils为包目录
  • util.go中定义了一个库函数
package utills

import "fmt"

func Count() {
	fmt.Println("utills包下的Count函数")
}

在main.go中要调用Count函数,则需要导入utills包,其导入的规则为”src下开始写的包名所在绝对路径“(不包含src)

package main

import "1_package/utills"

func main() {
	utills.Count()
}

拓展:

  • 导包时,我们可以通过在前面加.的方式导入包,这时,在代码中调用该包的内容时,可以不需要带包名,如fmt.Println()=>Prinln()
  • 导包时,可以在前面为包其别名,如import f fmt,这是,在代码中调用该包的内容时候,通过包的别名去操作p.Println()

init与main函数

init()main()是go语言中的两个保留函数,init用于初始化信息,main用于作为程序入口

init()会在导包时被触发,主要用于初始化一些包所需要的数据

例如:某个包中定义的init()函数,在main中该包在被import后,该Init()函数会在main函数执行前被执行

如下代码:在utills包下定义一个init函数,在main函数中导入utills函数,并调用其内定义的Count方法

utill:

package utills

import "fmt"

func Count() {
	fmt.Println("utills包下的Count函数")
}

func init() {
	fmt.Println("utills包下的init函数被执行啦")
}

main:

package main

import (
	"1_package/utills"
)

func main() {
	utills.Count()
}

/*
	输出:
		utills包下的init函数被执行啦
		utills包下的Count函数
*/

init和main的异同之处

  • 相同:
    • 两个函数在定义时不能有任何的参数和返回值
    • 两个函数均只能由go程序自动调用,不可被引用
  • 不同:
    • init可以定义在任何包中,且在一个源码文件中可以重复定义多个
    • main函数只能定义在main包中,且在一个源码文件中只能定义一个

init和main的执行顺序

main:

  • main包中的go文件中的main函数默认总是会被执行,main包总是最后一个被初始化

init:

  • 对于同一个go文件:
    若存在多个init函数,init按定义顺序从上至下依次调用

    package utills
    
    import "fmt"
    
    func Count() {
       fmt.Println("utills包下的Count函数")
    }
    //被import时,从上至下依次调用Init
    func init() {
       fmt.Println("utills包下的init函数第一次被执行啦") //1
    }
    func init() {
       fmt.Println("utills包下的init函数第二次被执行啦2") //2
    }
    func init() {
       fmt.Println("utills包下的init函数第三次被执行啦3") //3
    }
    
  • 对于同一个包下的不同go文件:
    如果这些文件中多处定义init,将按文件名的字符串进行“从小到大(单个字符比较,第一个字符能比出大小就认为某个字符串比另外一个字符串大)”排序顺序进行调用

  • 对于不同的包:

    • 不互相依赖的前提下:
      按main包中import的顺序调用包中的Init函数
    • 包互相依赖的情况下:
      如main导入A -> B -> C, 则初始化顺序为C -> B -> A,即最后被依赖的最先被执行(有点递归函数结果回归时那味儿)

要避免出现循环import的情况,这会出现死循环

如果一个包在调用链中被多次import,则其只会初始化一次

包的其他用法

匿名导包

当我们导入一个包时候如果只需要执行包的初始化操作而不需要其内其他资源时,可以使用匿名导包的方式(如果一个包正常被导入但是又不去使用其中的资源,就会报错):

import (
	_ "1_package/utills" //在前面加个_ 表示匿名导包
)

管理外部包

go若要导入外部的包(github或其他外部源的包),可以使用go get命令,它会将源码下载下来并放到GOPATH对应的目录中去

如:go链接mysql数据库需要使用mysql的数据包

go get github.com/go-sql-driver/mysql

#下载后的文件结构:
github.com
└── go-sql-driver
    └── mysql
        ├── auth.go
        ├── AUTHORS
        ├── auth_test.go
        ├── benchmark_test.go
        ├── buffer.go
        ├── CHANGELOG.md
        ├── collations.go
        ├── conncheck_dummy.go
        ├── conncheck.go
        ├── conncheck_test.go
        ├── connection.go
        ├── connection_test.go
        ├── connector.go
        ├── connector_test.go
        ├── const.go
        ├── driver.go
        ├── driver_test.go
        ├── dsn.go
        ├── dsn_test.go
        ├── errors.go
        ├── errors_test.go
        ├── fields.go
        ├── fuzz.go
        ├── go.mod
        ├── infile.go
        ├── LICENSE
        ├── nulltime.go
        ├── nulltime_go113.go
        ├── nulltime_legacy.go
        ├── nulltime_test.go
        ├── packets.go
        ├── packets_test.go
        ├── README.md
        ├── result.go
        ├── rows.go
        ├── statement.go
        ├── statement_test.go
        ├── transaction.go
        ├── utils.go
        └── utils_test.go

上出操作也可以直接去github上下载代码,然后解压到GOPATH路径去,也就是会所GO并不关系该包来自内部还是外部,它只去搜索GOPATH或GOROOT路径下没有你要的包

ps:GOPATH是我么存储项目源代码的位置,GOPATH是可以配置多个的,如果使用go get下载包,那个包会下载到第一个GOPATH下

拓展

go install命令可以用来编译包文件

非main编译(通过go build命令编译)后会生产.a的库文件,该文件一般情况下是在临时目录去生成然后程序再去临时目录去链接的,这一过程我们是看不到.a文件的

使用go install命令可以将编译的库文件安装到$GOROOT/pkg$GOPATH/pkg下,可用于后续可执行程序链接使用,可以加快程序的编译过程

如go的标准库中的各个包,源码部分就在$GOROOT/src下,而其编译后的.a文件路径在$GOROOT/pkg/linux_amd64

如果编译的是main文件,一般情况下是在当前编译目录下产生的可执行文件,若使用go install该文件生成的可执行程序会放于$GOROOT/bin$GOPATH/bin目录下

可能会遇到的错误

  1. 我们在运行main.go文件时经常会遇到一个这样的错误
    build command-line-arguments: cannot find module for path xxxxxxxxx

    原因:你使用的是$GOPATH,却开启了GO111MODULE=on模式,而没有go.mod文件

    关于GO111MODULE:

    • 用环境变量 GO111MODULE 开启或关闭模块支持,它有三个可选值:off、on、auto,默认值是 auto
    • GO111MODULE=off 无模块支持,go 会从 GOPATHvendor 文件夹寻找包
    • GO111MODULE=on 模块支持,go 会忽略GOPATHvendor 文件夹,只根据 go.mod 下载依赖
    • GO111MODULE=auto 在 $GOPATH/src 外面且根目录有 go.mod 文件时,开启模块支持
    • 在使用模块的时候,GOPATH 是无意义的,不过它还是会把下载的依赖储存在 $GOPATH/src/mod 中,也会把 go install 的结果放在 $GOPATH/bin

    解决方式:终端执行:go env -w GO111MODULE=auto

本系列学习资料来自:千锋的茹姐

posted @ 2021-02-13 05:32  .Jochen  阅读(224)  评论(0)    收藏  举报