前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 语言中的 init 函数

Go 语言中的 init 函数

作者头像
公众号: 云原生生态圈
发布2021-06-10 14:37:56
7450
发布2021-06-10 14:37:56
举报
文章被收录于专栏:云原生生态圈

标识符main无所不在。每个Go程序的执行都是从main包中一个拥有相同名字的函数开始的。当这个main函数返回时,整个程序也退出了执行。init函数也扮演着特定的角色,本文会描述它们的特性并介绍它们的使用方法。

init函数是定义在包级别的,它被用于:

  • 初始化无法使用表达式初始化的变量
  • 检查和修复程序的状态
  • 注册
  • 执行一次性的运算
  • 以及其它

除了下面要介绍一些区别,你可以将任何在一般函数中有效的代码放在其中。

包的初始化

要使用一个引入的包,首先它需要被初始化。这是由 Golang 的运行系统来完成的,由以下几步(顺序很重要)组成:

  1. 初始化引入的包(递归释义)
  2. 计算并初始化赋值包级别的变量
  3. 执行包内的 init 方法

包的初始化过程只会被执行一次,即使它被多次引用

顺序

Go 语言的包可以包含许多文件。那么在这些包和文件中,变量的初始化和init函数的执行顺序是怎样的呢?首先,初始化依赖机制会起作用(详情可以查看“Go 中的初始化依赖”[1])。当依赖工作完成后,必须决定先初始化a.go文件中的变量还是z.go文件中的变量。这依赖于文件在编译器中出现的顺序。如果z.go先被提交给构建系统,那么它的变量就会先于a.go中的变量初始化。init方法的调用也遵守相同的顺序。语言规格定义中建议总是采用相同的顺序,并且将包中的文件按单词拼写顺序传入:

为了保证初始化行为可稳定复现,构建系统应该倾向于将同一个包中的多个文件按文件名的单词拼写顺序传递给编译器。

不过对于移植性较差的程序来说也可以使用特别的顺序。我们用下面的例子看看这些是如何一起工作的:

「sandbox.go」

代码语言:javascript
复制
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」

代码语言:javascript
复制
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
}

「z.go」

代码语言:javascript
复制
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
}

程序输出:

代码语言:javascript
复制
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是没有被申明的,所以无法被引用:

代码语言:javascript
复制
package main
import "fmt"
func init() {
    fmt.Println("init")
}
func main() {
    init()
}

编译时它会输出 “undefined: init” 错误。

正式地来讲,init标识符不会引入任何绑定关系。与此相同的还有,下划线表示的空白标识符。

同一个包或文件中可以定义许多的init函数:

「sandbox.go」

代码语言:javascript
复制
package main
import "fmt"
func init() {
    fmt.Println("init 1")
}
func init() {
    fmt.Println("init 2")
}
func main() {
    fmt.Println("main")
}

「utils.go」

代码语言:javascript
复制
package main
import "fmt"
func init() {
    fmt.Println("init 3")
}

输出如下:

代码语言:javascript
复制
init 1
init 2
init 3
main

init 函数在标准库中被频繁地使用,比如:在math[2],bzip2[3] 和 image[4] 这些包里。

init函数最常见的使用场景就是赋值无法用初始化表达式计算得出的情况:

代码语言:javascript
复制
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循环无法用作表达式[5],所以将其放入init函数中可以解决这个问题。

为副作用而引入包

Go 对于未使用的包引入非常严格。有时候程序员引入一个包可能只是为了执行其中的init函数进行初始化工作。空白标识符这时候就派上用场了:

代码语言:javascript
复制
import _ "image/png"

这在 image[6] 包的评论中都有被提到。

Go 操作 excel 利器之 excelize

参考资料

[1]

Go中的初始化依赖: https://medium.com/golangspec/initialization-dependencies-in-go-51ae7b53f24c

[2]

math: https://github.com/golang/go/blob/2878cf14f3bb4c097771e50a481fec43962d7401/src/math/pow10.go#L33

[3]

bzip2: https://github.com/golang/go/blob/2878cf14f3bb4c097771e50a481fec43962d7401/src/compress/bzip2/bzip2.go#L479

[4]

image: https://github.com/golang/go/blob/2d573eee8ae532a3720ef4efbff9c8f42b6e8217/src/image/gif/reader.go#L511

[5]

Expression: https://golang.org/ref/spec#Expression

[6]

image #L10: https://github.com/golang/go/blob/0104a31b8fbcbe52728a08867b26415d282c35d2/src/image/image.go#L10

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云原生生态圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 包的初始化
  • 顺序
  • 属性
  • 为副作用而引入包
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档