跟我一起来学golang之《包》| 8月更文挑战

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

在java语言中有package的概念,同样在golang中也有包的概念,在Go语言中使用包(package)这种语法元素来组织源码,所有语法可见性均定义在package这个级别。

image-20210810163653698

Go 语言的源码复用建立在包(package)基础之上。包通过 package, import, GOPATH 操作完成。

main包

Go 语言的入口 main() 函数所在的包(package)叫 main,main 包想要引用别的代码,需要import导入!

package

src 目录是以代码包的形式组织并保存 Go 源码文件的。每个代码包都和 src 目录下的文件夹一一对应。每个子目录都是一个代码包。

代码包包名和文件目录名,不要求一致。比如文件目录叫 hello,但是代码包包名可以声明为 “main”,但是同一个目录下的源码文件第一行声明的所属包,必须一致!

同一个目录下的所有.go文件的第一行添加 包定义,以标记该文件归属的包,演示语法:

package 包名
复制代码

包需要满足:

  • 一个目录下的同级文件归属一个包。也就是说,在同一个包下面的所有文件的package名,都是一样的。
  • 在同一个包下面的文件package名都建议设为是该目录名,但也可以不是。也就是说,包名可以与其目录不同名。
  • 包名为 main 的包为应用程序的入口包,其他包不能使用。

在同一个包下面的文件属于同一个工程文件,不用import包,可以直接使用

包可以嵌套定义,对应的就是嵌套目录,但包名应该与所在的目录一致,例如:

// 文件:/utils/common.go中
package utils
// 可以被导出的函数
func Md5(str string) string {
	data := []byte(str)
	rest := fmt.Sprintf("%x", md5.Sum(data))

	return rest
}
// 不可以被导出的函数
func md5V2(str string) string {
	h := md5.New()
	h.Write([]byte(str))
	return hex.EncodeToString(h.Sum(nil))
}
复制代码

包中,通过标识符首字母是否大写,来确定是否可以被导出。首字母大写才可以被导出,视为 public 公共的资源。

import

要引用其他包,可以使用 import 关键字,可以单个导入或者批量导入,语法演示:

A:通常导入

// 单个导入
import "package"
// 批量导入
import (
  "package1"
  "package2"
  )
复制代码

B:点操作 我们有时候会看到如下的方式导入包

import(
	. "fmt"
) 
复制代码

这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调

用的fmt.Println("hello world")可以省略的写成Println("hello world")

C:起别名

别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字。导入时,可以为包定义别名,语法演示:

import (
  p1 "package1"
  p2 "package2"
  )
// 使用时:别名操作,调用包函数时前缀变成了我们的前缀
p1.Method()
复制代码

D:_操作 如果仅仅需要导入包时执行初始化操作,并不需要使用包内的其他函数,常量等资源。则可以在导入包时,匿名导入。

这个操作经常是让很多人费解的一个操作符,请看下面这个import:

import (
   "database/sql"
   _ "github.com/ziutek/mymysql/godrv"
 ) 
复制代码

_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。也就是说,使用下划线作为包的别名,会仅仅执行init()。

导入的包的路径名,可以是相对路径也可以是绝对路径,推荐使用绝对路径(起始于工程根目录)。

GOPATH环境变量

import导入时,会从GO的安装目录(也就是GOROOT环境变量设置的目录)和GOPATH环境变量设置的目录中,检索 src/package 来导入包。如果不存在,则导入失败。 GOROOT,就是GO内置的包所在的位置。 GOPATH,就是我们自己定义的包的位置。

通常我们在开发Go项目时,调试或者编译构建时,需要设置GOPATH指向我们的项目目录,目录中的src目录中的包就可以被导入了。

init() 包初始化

下面我们详细的来介绍一下这两个函数:init()、main() 是 go 语言中的保留函数。我们可以在源码中,定义 init() 函数。此函数会在包被导入时执行,例如如果是在 main 中导入包,包中存在 init(),那么 init() 中的代码会在 main() 函数执行前执行,用于初始化包所需要的特定资料。例如: 包源码:

src/userPackage/tool.go

package userPackage
import "fmt"
func init() {
  fmt.Println("tool init")
}
复制代码

主函数源码:

src/main.go

package main
import (
  "userPackage"
  )
func main() {
  fmt.Println("main run")
  // 使用userPackage
  userPackage.SomeFunc()
}
复制代码

执行时,会先输出 “tool init”,再输出 “main run”。

下面我们详细的来介绍一下init()、main() 这两个函数。在 go 语言中的区别如下:

  • 相同点:

两个函数在定义时不能有任何的参数和返回值。 该函数只能由 go 程序自动调用,不可以被引用。

  • 不同点:

init 可以应用于任意包中,且可以重复定义多个。 main 函数只能用于 main 包中,且只能定义一个。

两个函数的执行顺序:

在 main 包中的 go 文件默认总是会被执行。

对同一个 go 文件的 init( ) 调用顺序是从上到下的。

对同一个 package 中的不同文件,将文件名按字符串进行“从小到大”排序,之后顺序调用各文件中的init()函数。

对于不同的 package,如果不相互依赖的话,按照 main 包中 import 的顺序调用其包中的 init() 函数。

如果 package 存在依赖,调用顺序为最后被依赖的最先被初始化,例如:导入顺序 main –> A –> B –> C,则初始化顺序为 C –> B –> A –> main,一次执行对应的 init 方法。main 包总是被最后一个初始化,因为它总是依赖别的包

983397D2-C706-4267-8EA6-507613565443

避免出现循环 import,例如:A –> B –> C –> A。

一个包被其它多个包 import,但只能被初始化一次

管理外部包

go允许import不同代码库的代码。对于import要导入的外部的包,可以使用 go get 命令取下来放到GOPATH对应的目录中去。

举个例子,比如说我们想通过go语言连接mysql数据库,那么需要先下载mysql的数据包,打开终端并输入以下命令:

localhost:~ $ go get github.com/go-sql-driver/mysql
复制代码

安装之后,就可以在gopath目录的src下,看到对应的文件包目录。

也就是说,对于go语言来讲,其实并不关心你的代码是内部还是外部的,总之都在GOPATH里,任何import包的路径都是从GOPATH开始的;唯一的区别,就是内部依赖的包是开发者自己写的,外部依赖的包是go get下来的。

更先进的依赖管理go mod

高版本的go提倡使用go mod进行包依赖管理,go mod可以理解为JAVA中的maven。

开启Go module

1.11和1.12版本

将下面两个设置添加到系统的环境变量中

GO111MODULE=on
GOPROXY=https://goproxy.io
复制代码

1.13版本之后

需要注意的是这种方式并不会覆盖之前的配置,有点坑,你需要先把系统的环境变量里面的给删掉再设置

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct
复制代码

goLand开启 go mod

image-20210723160952874

mod基本操作

初始化 MODULE

go mod init test(test为项目名)
复制代码

我们会发现在项目根目录会出现一个 go.mod 文件,注意,此时的 go.mod 文件只标识了项目名和go的版本,这是正常的,因为只是初始化了。

检测依赖
go mod tidy
复制代码

tidy会检测该文件夹目录下所有引入的依赖,写入 go.mod 文件。写入后你会发现 go.mod 文件有所变动。

例如:

module test

go 1.13

require (
    github.com/gin-contrib/sessions v0.0.1
    github.com/gin-contrib/sse v0.1.0 // indirect
    github.com/gin-gonic/gin v1.4.0
    github.com/go-redis/redis v6.15.6+incompatible
    github.com/go-sql-driver/mysql v1.4.1
    github.com/golang/protobuf v1.3.2 // indirect
    github.com/jinzhu/gorm v1.9.11
    github.com/json-iterator/go v1.1.7 // indirect
    github.com/kr/pretty v0.1.0 // indirect
    github.com/mattn/go-isatty v0.0.10 // indirect
    github.com/sirupsen/logrus v1.2.0
    github.com/ugorji/go v1.1.7 // indirect
    golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae // indirect
    gopkg.in/yaml.v2 v2.2.4
)
复制代码

此时依赖还是没有下载的

下载依赖

我们需要将依赖下载至本地,而不是使用 go get

go mod download
复制代码

如果你没有设置 GOPROXY 为国内镜像,这步百分百会卡住

此时会将依赖全部下载至 GOPATH 下,会在根目录下生成 go.sum 文件, 该文件是依赖的详细依赖, 但是我们开头说了,我们的项目是没有放到 GOPATH 下的,那么我们下载至 GOPATH 下是无用的,照样找不到这些包

导入依赖
go mod vendor
复制代码

执行此命令,会将刚才下载至 GOPATH 下的依赖转移至该项目根目录下的 vendor(自动新建) 文件夹下

image-20210723161457711

此时我们就可以使用这些依赖了.

如果我们使用goland工具进行开发goland默认是关闭该功能的,我们需要手动打开(不排除之后更新会不会改成默认开启)

image-20210810165328311

常用命令:

go mod init  # 初始化go.mod
go mod tidy  # 更新依赖文件
go mod download  # 下载依赖文件
go mod vendor  # 将依赖转移至本地的vendor文件
go mod edit  # 手动修改依赖文件
go mod graph  # 打印依赖图
go mod verify  # 校验依赖
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享