翻译自
正文
init函数在包中定义,通常用来
- 比较复杂的初始化流程,不能通过表达式初始化的变量的初始化
- 检查、修复程序的状态
- 注册
- 执行一次性的计算
- 等等
除了下面讨论的一些差异之外,init函数中可以放任何常规函数可以放的东西
包初始化
要使用导入的包,需要先将包初始化,初始化及顺序问题由Golang的running system完成。
- 导入的包的初始化(递归定义)
- 为包中声明的变量计算和分配初始值
- 执行包中的
init
函数
即使包被导入多次,包的初始化也只能执行一次
顺序
Go里面的一个包可以包含多个文件。分布在众多包中的众多文件,变量和init函数执行的顺序应该是什么样子呢?之前的文章提到了变量的初始化顺序。完成此操作后,需要决定文件 a.go
或z.go
中的变量初始化谁更应该早执行。 这取决于传递给编译器的文件顺序。 如果 z.go 首先由构建系统传递,则z.go
先执行,然后在 a.go
的再执行。 这同样适用于init 函数的触发。 语言规范建议始终使用相同的顺序并按字典顺序从包中传递文件名:
为了确保可重现的初始化行为,鼓励构建系统把属于同一包的多个文件以词法文件名顺序传递给编译器。
不过依赖文件名初始化顺序的程序十分罕见,让我们来看这样子的例子
sandbox.go
package main
import "fmt"
var _ int64 = s()
func init() {
fmt.Println("init in sandbox.go")
}
func s() int64 {
fmt.Println("calling s() in sandbox.go")
return 1
}
func main() {
fmt.Println("main")
}
a.go
package main
import "fmt"
var _ int64 = a()
func init() {
fmt.Println("init in a.go")
}
func a() int64 {
fmt.Println("calling a() in a.go")
return 2
}
.go
package main
import "fmt"
var _ int64 = z()
func init() {
fmt.Println("init in z.go")
}
func z() int64 {
fmt.Println("calling z() in z.go")
return 3
}
运行后,程序会这样输出
calling a() in a.go
calling s() in sandbox.go
calling z() in z.go
init in a.go
init in sandbox.go
init in z.go
main
属性
init函数不需要参数也不返回任何值。和main不同,标识符init未声明,所以不能被引用
package main
import "fmt"
func init() {
fmt.Println("init")
}
func main() {
init()
}
在编译时,程序返回
undefined: init
同一个包或文件中可以有很多个init函数,在不同文件中定义的init函数如下按照字母顺序执行,同一个文件按声明顺序执行,举例
sandbox.go
package main
import "fmt"
func init() {
fmt.Println("init 1")
}
func init() {
fmt.Println("init 2")
}
func main() {
fmt.Println("main")
}
utils.go
package main
import "fmt"
func init() {
fmt.Println("init 3")
}
输出
init 1
init 2
init 3
main
init函数的最常见用途,就是用来给那些不能通过表达式初始化的变量初始化,如:
var precomputed = [20]float64{}
func init() {
var current float64 = 1
precomputed[0] = current
for i := 1; i < len(precomputed); i++ {
precomputed[i] = precomputed[i-1] * 1.2
}
}
表达式中可不能使用for循环,所以通过init函数来解决这个问题
为了副作用来导入包
Go对于未使用的引用非常严格。有些场景,你导入一个包,只为了执行其中的init函数(如mysql的driver)。
import _ "image/png"
网友评论