在 Java 中,我们将每个 .java 文件称之为类。引入某个类到别的类中,称之为类对象引用。
在 Go 中并没有类的概念,每个 .go 文件被称之为模块,引入某个模块到别的模块中称之为模块引用。
但是与 Java 不同的是:Go 中不存在引入某个模块到另一个模块中,只有包的引用。
我们可以看最简单的 fmt 的使用:
fmt 是包名而不是模块名。
引入了 fmt 这个包之后,该包下所有公开的变量和方法都会被引用到。所谓的公开的,即变量或者方法名开头是大写字母形式,基本语法中有介绍,不再赘述。
这一篇主要介绍模块内各个对象的初始化方式以及隐藏特性,如何引入包下的各个模块。
包的概念
如同所有的语言,pkg 没有什么高大上,就是 Go 文件的管理目录。只是每个语言对包管理和引入的方式有所区别,所以值得我们拿出来单独说明。
每个 .go 源文件的第一行必然要声明该文件属于哪个包,比如:
package main
一个独立的 Go 程序必须要有一个 package main
的声明,main 包是该程序可执行的入口,所有的启动都将从 main 函数开始。
包的引用
下面我们来创建一些包以及 Go 文件,观察如何引用:
有如上目录结构,
UserInfo.go
package user
type User struct {
Id int64
Name string
Sex int32
}
func GetUserInfo(uid int64) User {
return User{1,"xiaoming", 1}
}
我们接下来在 UserDao.go 中引用 UserInfo.go:
package dao
import (
"fmt"
"mod-demo/user"
)
func GetUser(uid int64) user.User {
fmt.Print()
return user.GetUserInfo(uid)
}
可以看到导入了 'mod-demo/user' 包名,然后使用 user
就可以点出来该包下的公开变量/方法。
那我们接着在 UserInfo.go 中添加一个私有属性:
var default_sex int32
可以看到这时候是无法引入:
接着我们继续在 user 包下新增 UserClientCache.go 模块,看看是否还是可以通过 user 包名来引用。
UserClientCache.go
package user
type UserClient struct {
Id int64
ClientId int64
ClientType string
}
func GetUserClient(id int64) UserClient {
return UserClient{1, 2, "android"}
}
接着引用它:
可以看到是没问题的。
包别名
如果你引入不同的包模块,它们的子目录都有一样的名称,这时候你可以使用包别名来区分它们。
如果你的包目录名称非常的长,你也可以使用别名来代替。
上面的项目中我已经把包名改的如此之长:
下面来看一下包别名的用法:
package dao
import (
shortUser "mod-demo/user_test_long_long_name"
)
func GetUser(uid int64) shortUser.User {
return shortUser.GetUserInfo(uid)
}
func GetUserClient(uid int64) shortUser.UserClient {
return shortUser.GetUserClient(uid)
}
上面示例中我们使用别名也是一样能调用。
点操作
. 导入可以让包内的方法注册到当前包的上下文中,直接调用方法名即可,不需要再加包前缀。
package dao
import (
. "mod-demo/user_test_long_long_name"
)
func GetUser(uid int64) User {
return GetUserInfo(uid)
}
func GetUserClientInfo(uid int64) UserClient {
return GetUserClient(uid)
}
下划线操作
_ 是包引用操作,只会执行包下各模块中的 init 方法,并不会真正的导入包,所以不可以调用包内的其他方法。
后文我们说到模块的时候会讲 init 方法。它会在该模块被编译的时候执行。所以你可以在 init 方法中去做一些初始化的事情。
当我们在执行 go build file.go
的时候,该文件引用的模块中如果有定义 init 方法,那么会在构建的时候执行该 init 方法。
先上结论:
- 在同一个 package 中,可以多个文件中定义 init 方法
- 在同一个 Go 文件中,可以重复定义 init 方法
- 在同一个 package 中,不同文件中的 init 方法的执行按照文件名先后执行各个文件中的 init 方法
- 在同一个文件中的多个 init 方法,按照在代码中编写的顺序依次执行不同的 init 方法
首先目录结构如下:
mod-demo
user
UserClientCache.go
UserInfo.go
main.go
UserClientCache.go 如下:
package user
import "fmt"
type UserClient struct {
Id int64
ClientId int64
ClientType string
}
func GetUserClient(id int64) UserClient {
return UserClient{1, 2, "android"}
}
func init() {
fmt.Println(UserClient{1, 2, "android"})
}
func init() {
fmt.Println(UserClient{2, 2, "ios"})
}
UserInfo.go 如下:
package user
import "fmt"
var default_sex int32
type User struct {
Id int64
Name string
Sex int32
}
func GetUserInfo(uid int64) User {
return User{1,"xiaoming", 1}
}
func init() {
fmt.Println(User{1,"xiaoming", 1})
}
Main.go 如下:
package main
import (
"fmt"
_ "mod-demo/user"
)
func main() {
fmt.Print("main start")
}
执行 main 函数可以看到:
{1 2 android}
{2 2 ios}
{1 xiaoming 1}
main start
是按照文件排列顺序先后输出的,并且 main 函数是最后执行。
上面说到了 init 函数和 main 函数的初始化顺序,一个完整的 Go 进程启动,各个模块以及变量的初始化顺序如下:
go run *.go
├── 执行 Main 包
├── 初始化所有引用的包
| ├── 初始化所有引用的包 (recursive definition)
| ├── 初始化全局变量
| └── 以词法文件名的顺序调用 init 函数
└── 初始化 Main 包
├── 初始化全局变量
└── 以词法文件名的顺序调用 init 函数
程序的初始化和执行都起始于 main 包。
如果 main 包还导入了其它的包,那么就会在编译时将它们依次导入。
有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt 包,但它只会被导入一次,因为没有必要导入多次)。
当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行 init 函数(如果有的话),依次类推。
等所有被导入的包都加载完毕了,就会开始对 main 包中的包级常量和变量进行初始化,然后执行 main 包中的 init 函数(如果存在的话),最后执行 main 函数。
Go 常用命令介绍
直接在终端中输入 go help
即可显示所有的 Go 命令以及相应命令功能简介,主要有下面这些:
- build:编译包和依赖
- clean:移除对象文件
- doc:显示包或者符号的文档
- env:打印 go 的环境信息
- bug:启动错误报告
- fix:运行 go tool fix
- fmt:运行 gofmt 进行格式化
- generate:从 processing source 生成 go 文件
- get:下载并安装包和依赖
- install:编译并安装包和依赖
- list:列出包
- run:编译并运行 go 程序
- test:运行测试
- tool:运行 go 提供的工具
- version:显示 go 的版本
- vet:运行 go tool vet
命令的使用方式为: go command [args]
, 除此之外,可以使用go help
来显示指定命令的更多帮助信息。
build 和 run 命令
就像其他静态类型语言一样,要执行 Go 程序,需要先编译,然后在执行产生的可执行文件。go build
命令就是用来编译 Go 程序生成可执行文件的。但并不是所有的 Go 程序都可以编译生成可执行文件的, 要生成可执行文件,Go 程序需要满足两个条件:
- 该 Go 程序需要属于 main 包
- 在 main 包中必须还得包含 main 函数
也就是说 Go 程序的入口就是 main.main
, 即 main 包下的 main 函数,如下:
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
编译 hello.go,然后运行可执行程序:
$ go build hello.go # 将会生成可执行文件 hello
$ ./hello # 运行可执行文件
Hello World!
上面就是 go build 的基本用法,另外如果使用 go build 编译的不是一个可执行程序,而是一个包,那么将不会生成可执行文件。
而 go run
命令可以将上面两步并为一步执行(不会产生中间文件)。
$ go run hello.go
Hello World!
上面两个命令都是在开发中非常常用的。
此外 go clean 命令,可以用于将清除产生的可执行程序:
$ go clean # 不加参数,可以删除当前目录下的所有可执行文件
$ go clean sourcefile.go # 会删除对应的可执行文件
install 命令
用来编译和安装 Go 程序,我们可以将它与 build 命令对比:
install | build | |
---|---|---|
生成的可执行文件路径 | 工作目录下的 bin 目录下 | 当前目录下 |
可执行文件的名字 | 与源码所在目录同名 | 默认与源程序同名,可以使用-o 选项指定 |
依赖 | 将依赖的包放到工作目录下的 pkg 文件夹下 | - |