Go语言基础
介绍
基础
介绍
- 本文介绍Go语言中包(包声明、包导入与调用、包成员访问权限、init函数、包管理(Go modules)等)、defer关键字、panic 与 recover、go doc 工具等相关知识。
基础
包
- Go语言源码高复用是以包的形式划分源代码。
- 包就是源文件(函数、数据等组成的*.go文件)的集合(目录),将各自相关的功能源码放到同一个目录下,以独立的模块维护,提供给其它项目使用。
包声明
- Go源文件中,使用 package 声明包的路径,编译器依据包路径找到对应包源代码并编译库文件,包中可包含多个原文件。
- 包声明使用 package 包名称。
- 包名称一般与其源文件所在目录的名称(没有强制要求)。
- 包名称命名使用简短明确的小写字母,同一个包中的源文件必须使用相同的包名。
// 自定义了两个包,整个应用目录结构如下
-- test_go01
|__ src // 源文件相关信息
|__ main // main包
|__ main.go
|__ pkg_my_add // 自定义包
|__ add.go
|__ pkg_my_sub // 自定义包
|__ sub.go
// main.go
package main
import (
"fmt"
"pkg_my_add" // 导入包 pkg_my_add,注意需要配置GOPATH
"pkg_my_sub" // 导入包 pkg_my_sub,注意需要配置GOPATH
)
func main() {
var a int = 10
var b int = 20
var c float32 = 30.3
var d float32 = 40.5
fmt.Println("a + b = ", pkg_my_add.Add(a, b))
fmt.Println("c + d = ", pkg_my_add.Add(c, d))
fmt.Println("a - b = ", pkg_my_sub.Sub(a, b))
fmt.Println("c - d = ", pkg_my_sub.Sub(c, d))
}
// add.go
package pkg_my_add // 声明包
// 普通函数
// func Add(a int, b int) int {
// return a + b
// }
// 使用泛型,支持 int, float32类型
func Add[T int | float32](a, b T) T {
return T(a + b)
}
// sub.go
package pkg_my_sub // 声明包
// 普通函数
// func Sub(a int, b int) int {
// return a - b
// }
// 使用泛型,支持 int, float32类型
func Sub[T int | float32](a, b T) T {
return T(a - b)
}
输出结果
a + b = 30
c + d = 70.8
a - b = -10
c - d = -10.200001
包导入与调用
- Go语言中使用 import 按路径名导入需要使用的包,通过包名称访问包中对外可见内容。
- Go 语言不允许交叉导入包,import 导入方式有多种,随后分别介绍。
- 工作目录结构:
bin目录: 放置发布的二进制程序
pkg目录: 放置发布的库文件
src目录: 放置源代码
- 配置工程路径到GOPATH环境变量中,默认从($GOPATH/src路径开始)。
// 在终端使用指令 go env 查看go环境变量
C:\Users\Administrator>go env
set GO111MODULE=off
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Administrator\AppData\Local\go-build
set GOENV=C:\Users\Administrator\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=D:\GoProject\test_go01\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\Administrator\go;
set GOPRIVATE=
set GOPROXY=https://goproxy.cn
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.22.0
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=0
set GOMOD=
set GOWORK=
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\Users\Administrator\AppData\Local\Temp\go-build4166079327=/tmp/go-build -gno-record-gcc-switches
// 看到本机默认 GOPATH=C:\Users\Administrator\go;
// 工程路径在 D:\GoProject\test_go01 目录下
// (Windows10为例)选择桌面此电脑 -> 右键鼠标 -> 属性 -> 高级系统设置 -> 环境变量 -> 用户变量栏中会看到GOPATH变量 -> 选中并点击编辑 -> 添加工程路径 D:\GoProject\test_go01 -> 依次确定退出配置
// 重新打开终端运行 go env 查询 GOPATH
set GOPATH=D:\GoProject\test_go01;C:\Users\Administrator\go;
// 配置完GOPATH,在 main.go 中可导入包使用
-
编译运行
- 在终端窗口进入目录 D:\GoProject\test_01\main 目录。
- 执行指令 go build main.go 编译并在当前目录下生成 main.exe 二进制可执行程序。
- 若要指定路径和指定二进制程序名称,执行指令 go build -o 路径文件名称 main.go。
- 执行指令 go run main.exe 运行程序。
- 执行指令 go install main 编译并发布运行程序,将当前目录下 main.exe 拷贝到 test_go01 目录下的 bin 目录,会自动创建 bin 目录。
- 执行指令 go install …\pkg_my_add 发布单个库,会自动创建 pkg 目录,与 bin 目录同级。
- 执行指令 go install … 发布此路径下的所有库,会自动创建 pkg 目录,与 bin 目录同级。
-
使用绝对路径导入包(GOPATH 是路径前缀, D:\GoProject\test_go01)。
// D:\GoProject\test_go01\src\pkg_my_add
// D:\GoProject\test_go01\src\pkg_my_sub
import (
"fmt"
"pkg_my_add"
"pkg_my_sub"
)
- 使用相对路径导入包(在当前文件所在的目录查找)。
// D:\GoProject\test_go01\src\main\..\pkg_my_add
// D:\GoProject\test_go01\src\main\..\pkg_my_sub
import (
"fmt"
"../pkg_my_add"
"../pkg_my_sub"
)
- 使用点导入包(使用包中的成员时可以省略包名)。
package main
import (
. "fmt"
)
func main() {
Println("fmt println") // 省略包名
}
- 使用别名导入(可以给包重命名)。
package main
import (
"fmt"
format "fmt"
)
func main() {
format.Println("formt println")
fmt.Println("fmt println")
}
- 使用下划线导入(可以导入包但不使用,这种情况下包的初始化函数正常执行)。
package main
import (
"fmt"
_ "pkg_my_add"
)
func main() {
fmt.Println("fmt println")
}
包成员访问权限
- Go语言中,通过变量或函数名称首字母大小写来控制包成员对外的访问权限,首字母大写表示公开的,外部可访问,首字母小写表示私有的,只允许包内部访问。
- main 包用于声明告知编译器将包编译为二进制可执行文件,main 包中的 main 函数是程序的入口,无返回值,无参数。
- 同包下的不同文件间的不同内容引用时,需要这几个源文件文件一起编译。
// 假设 main 包下存在 main.go test.go 两个源文件
// test.go
package main
import "fmt"
func test() {
fmt.Println("test func")
}
// main.go
package main
import (
"fmt"
)
func main() {
test()
fmt.Println("main func")
}
// 执行编译指令: go build .\main.go .\test.go
输出结果:
test func
main func
init函数
- init 函数无返回值,无参数, 用于包的初始化,是一种特殊的函数。
- init 函数在 import包时自动被调用。不能被显示调用。
- 多个包同时导入另一个包时,init 函数在应用程序运行期间只会自动调用一次。
package main
import (
"fmt"
)
func init() {
fmt.Println("init func")
}
// 自动调用 init()
func main() {
fmt.Println("main func")
// init() // 不支持显示调用
}
输出结果
init func
main func
- 若在同一个包中存在多个 init 函数,同一个源文件中定义的多个 init 函数会按照在代码中的出现顺序执行,多个源文件中的 init 函数执行顺序不确定。
defer关键字
- defer关键字将其后面跟随的语句进行延迟处理,在函数 return 之后、退出之前调用。
- 一般用于资源释放、异常捕获、记录日志等。
- 相同作用域内多个函数使用 defer 声明,执行时与声明顺序相反(栈特点:先进后出)。
package main
import (
"fmt"
)
func main() {
fmt.Println("1 exec")
defer fmt.Println("2 exec")
defer fmt.Println("3 exec")
defer fmt.Println("4 exec")
defer test()
fmt.Println("5 exec")
}
func test() {
fmt.Println("test func")
}
输出结果
1 exec
5 exec
test func
4 exec
3 exec
2 exec
- defer 声明时,其对应的参数会根据当前获取的数据作为参数,也就是说将当前传入时的变量参数作为入参,不会等到函数调用时掺入形参。
package main
import (
"fmt"
)
func main() {
var a int = 10
defer test1(a)
defer test2(a)
defer func() {
fmt.Println("func a = ", a)
}()
a++
fmt.Println("main a = ", a)
}
func test1(a int) {
fmt.Println("test1 a = ", a)
}
func test2(a int) {
fmt.Println("test2 a = ", a)
}
输出结果
main a = 11
func a = 11
test2 a = 10
test1 a = 10
- defer语句放在return后面时不会被执行。
package main
import (
"fmt"
)
func main() {
fmt.Println("return before")
return
defer fmt.Println("return after")
}
输出结果: return before
- 函数返回值匿名时,执行 return 语句后,defer 操作无法对返回值进行修改。
func test() int {
var ret int
defer func() {
ret++
fmt.Println("defer1 func: ", ret)
}()
defer func() {
ret++
fmt.Println("defer2 func: ", ret)
}()
return ret
}
func main() {
fmt.Println("test value:", test())
}
输出结果
defer2 func: 1
defer1 func: 2
test value: 0
- 函数返回值命名时,执行 return 语句时,defer 操作对返回值修改。
package main
import "fmt"
func test() (ret int) {
defer func() {
ret++
fmt.Println("defer1 func: ", ret)
}()
defer func() {
ret++
fmt.Println("defer2 func: ", ret)
}()
return ret
}
func main() {
fmt.Println("test value: ", test())
}
输出结果
defer2 func: 1
defer1 func: 2
test value: 2
panic 与 recover
- panic 和 recover是用于异常处理的两个关键概念,用于处理程序运行时出现的错误。
- panic 用于异常的触发,程序调用 panic 抛出错误时,程序会被中断,不会继续执行 panic 之后的程序逻辑,defer 声明的函数会被执行。
package main
import "fmt"
func main() {
n := 10
defer func() {
fmt.Println("defer n = ", n)
}()
fmt.Println("n = ", n)
panic("create error")
fmt.Println("panic")
}
输出结果
n = 10
defer n = 10
panic: create error
goroutine 1 [running]:
main.main()
D:/GoProject/test_go01/src/main/main.go:11 +0xba
exit status 2
- recover 用于异常的捕获和异常的处理,仅在 defer 语句的函数中有效,注意 recover 只能捕获到最后一个错误。
- 必须在发生Panic后的代码块中调用,无错误返回 nil。
package main
import "fmt"
func main() {
n := 10
defer func() { // 捕获并处理错误,去掉 defer 后 recover 函数无效
if err := recover(); err != nil {
fmt.Println("recv panic error")
} else {
fmt.Println("no error")
}
}()
fmt.Println("n = ", n)
panic("create error")
fmt.Println("panic")
}
输出结果
n = 10
recv panic error
包管理(Go modules)
- Go 版本 < 1.5,无版本管理,所有的依赖包都放在 GOPATH 下,比较混乱。
- Go 版本 = 1.5,使用vendor 机制,并在 Go1.6 中默认启用。
- Go 版本 = 1.9,Golang 官方集成了由社区组织合作开发的 Dep。
- Go 版本 = 1.11,此版本推出了 Go Modules 机制来管理包,是由 vgo 演变而来。
- Go 版本 = 1.13,将 Go Modules 设置为默认的 Go 管理工具。
- Go 版本 >= 1.14,Golang 官方正式推荐在生产环境使用 Go Modules作为管理工具。
- Go Modules 的管理命令为go mod,具体使用 go help mod查看帮助。
Usage:
go mod <command> [arguments]
The commands are:
download:下载 go.mod 文件中记录的依赖包到本地缓存。指令:go mod download
edit:编辑 go.mod 文件。指令:go mod edit
graph:查看现有的依赖结构。指令:go mod graph
init:初始化新模块。指令:go mod init 模块名称,创建 go.mod 文件
tidy:添加丢失的模块,并移除无用的模块。指令:go mod tidy
vendor:将所有依赖包存到当前目录下的 vendor 目录。指令:go mod vendor
verify:校验当前模块依赖包是否缓存或是否有修改。指令:go mod verify
why:查看为什么需要依赖某模块。指令:go mod why
- Golang 使用环境变量 GO111MODULE 来控制版本管理,此环境变量有三个值可设置。
GO111MODULE=off时,始终在GOPATH 和 vendor 目录下搜索依赖包
GO111MODULE=on时,使用 Go modules 机制,在 GOPATH/pkg/mod 目录下搜索依赖包。
GO111MODULE=auto时,当源代码不在 GOPATH/src 的子目录且包含 go.mod 文件,则使用 Go modules 机制,否则使用 GOPATH 和 vendor 机制。
- 关于Modules的官方定义为:
Modules是相关Go包的集合,是源代码交换和版本控制的单元。go命令直接支持使用Modules,包括记录和解析对其他模块的依赖性。Modules替换旧的基于GOPATH的方法,来指定使用哪些源文件。
Modules和传统的GOPATH不同,不需要包含例如src,bin这样的子目录,一个源代码目录甚至是空目录都可以作为Modules,只要其中包含有go.mod文件。
go doc 工具
- 此工具用于在命令行输出 Go 源码相关的文档说明。使用指令:go help doc查看具体用法。
// 查看 pkg_my_add 包
PS D:\GoProject\test_go01\src\main> go doc pkg_my_add
package pkg_my_add // import "pkg_my_add"
func Add[T int | float32](a, b T) T