- /
- Skills/
- 后端编程/
- Go 语言的初始化函数 init()/
Go 语言的初始化函数 init()
目录
初始化函数,是编程语言、编程框架为程序运行过程中的函数调用提供数据初始设置的手段。比如在大家熟悉的 java、python 中,我们可以为类实例成对象时,在构造函数或者初始化函数中对对象的初始值进行设置。在非面向对象的编程中,我们通过按照某种约定或者人为的,在一个重任务启动之前,调用一个数据设置方法用来对环境数据进行初始化。
golang 不是面向对象的语言,没有构造函数,但同样提供了可以用来实现数据初始化的机制,这是语言级别的原生支持。
下面,有悟带大家来看看在 golang 中可以有哪里初始化的方式。
惯例,创建一个示例工程:
~/Projects/go/examples
➜ code goexamples.code-workspace
~/Projects/go/examples
➜ mkdir goinit && cd goinit
~/Projects/go/examples/goinit
➜ go mod init initdemo
go: creating new go.mod: module initdemo
~/Projects/go/examples/goinit
➜ touch helloinit.go
// helloinit.go
package main
import (
"fmt"
)
func main() {
fmt.Println("youwu.today say hi...😀")
}
在函数体外的变量初始化 #
我们在函数体定义一个变量,并为其赋初始值,这个变量的作用范围(scope)为包级别(变量在初始化后,可以在包中使用)。
// helloinit.go
package main
import (
"fmt"
)
var initA string
var initB = "我是 initB"
func main() {
fmt.Println("youwu.today say hi...😀")
fmt.Println("* 变量 initA: ", initA)
fmt.Println("* 变量 initB: ", initB)
}
~/Projects/go/examples/goinit
➜ go run helloinit.go
youwu.today say hi...😀
* 变量 initA:
* 变量 initB: 我是 initB
在有悟文章的
《golang 中的零值问题与判断为空》
中的谈到默认值的问题,所以 initA
是一个 长度为0的字符串 ,有兴趣的同学可以看下。
在 init 函数中初始化 #
golang 为我们提供了 init()
函数,这个函数是隐式调用的,即会在包引入就执行。
// helloinit.go
package main
import (
"fmt"
)
var initA string
var initB = "我是 initB"
var initC string
func init() {
initC = "设置为 hello"
}
func main() {
fmt.Println("youwu.today say hi...😀")
fmt.Println("* 变量 initA: ", initA)
fmt.Println("* 变量 initB: ", initB)
fmt.Println("* 变量 initC: ", initC)
}
~/Projects/go/examples/goinit
➜ go run helloinit.go
youwu.today say hi...😀
* 变量 initA:
* 变量 initB: 我是 initB
* 变量 initC: 设置为 hello
在 main()
函数中并没有直接调用init()
,go在编译的时候,已经确保 init
会在 main
执行之前就被调用,这种方式就是隐式。
在包中的init函数 #
上节演示变量在被声明时、init
函数中进行初始化的手段,那么问题来了。如果你的代码比较长,又使用包的方式组织,为了更清楚的表达,你想在包中每个 go 代码中都来一个 init
函数,不知道是否可行?因为我们知道,在一个 go 的包(package)中,全局变量与函数一样,不管在哪个 go 代码文件中声明,名称是全包内唯一,不能重复的。
例如,像以下的样子,该怎么办?init
函数应该怎么样组织,又可以有哪些作为?
┌──────────────────────────────────────────┐
│Package initdemo │
│ │
│ ┌──────────────────────────────────┐ │
│ │ initdemo1.go init()? │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ initdemo2.go init()? │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────────┘
init
是 go 中的特殊函数,接受特别的对待。它是 go 中唯一可以在一个包内的不同代码文件中多次定义的函数名称。
即使可以这么做,事实上可能不建议,因为这会让初始化代码看起来很混乱,对维护不友好。你应该根据实际需要来设计代码的组织方式。不过下面我们还是来看看被引用包中的
init
函数。
我们来改进下例子,创建一个名为 demo
的 package
,并新建两个 go 文件,initdemoA.go 和 initdemo2.go(它们都包含了 init 函数,内容如下),并在 helloinit.go 中引用,看看它们的初始化函数执行顺序:
~/Projects/go/examples/goinit
➜ mkdir demo
~/Projects/go/examples/goinit
➜ touch demo/initdemoA.go demo/initdemo2.go
// initdemoA.go
package demo
import "fmt"
func init() {
fmt.Println("initdemo1.go 的 init 函数给你打招呼了")
VarInDemo_Initdemo1 = "VarInDemo_Initdemo1"
}
var VarInDemo_Initdemo1 string
// initdemo2.go
package demo
import "fmt"
func init() {
fmt.Println("initdemo2.go 的 init 函数给你打招呼了")
VarInDemo_Initdemo2 = "VarInDemo_Initdemo2"
}
var VarInDemo_Initdemo2 string
// helloinit.go
package main
import (
"fmt"
"initdemo/demo"
)
var initA string
var initB = "我是 initB"
var initC string
func init() {
fmt.Println("main 初始化")
initC = "设置为 hello"
}
func main() {
fmt.Println("youwu.today say hi...😀")
fmt.Println("* 变量 initA: ", initA)
fmt.Println("* 变量 initB: ", initB)
fmt.Println("* 变量 initC: ", initC)
fmt.Println(demo.VarInDemo_Initdemo1)
}
~/Projects/go/examples/goinit
➜ go run helloinit.go
initdemo2.go 的 init 函数给你打招呼了
initdemo1.go 的 init 函数给你打招呼了
main 初始化
youwu.today say hi...😀
* 变量 initA:
* 变量 initB: 我是 initB
* 变量 initC: 设置为 hello
VarInDemo_Initdemo1
在 helloinit.go 中引入 initdemo/demo
,在 main
函数执行之前,先是执行被引入包的 init
函数,再执行当前 main
包的 init
函数。
上面还体现了一个细节,就是包 demo
中的多个 init
函数的执行顺序,是按所在 go 代码文件名称的排序来确定执行顺序的。
┌────────────────────────────────┐ ┌───────────────────────────────┐
│package main │ │package demo │
│ │ │ ┌───────────────────────┐ │
│ ┌──────────────────────────┐ │ │ │ │ │
│ │ import "initdemo/demo" │◀─┼────│ │init() in initdemoA.go │ │
│ └──────────────────────────┘ │ │ │ [2] │ │
│ │ │ └───────────────────────┘ │
│ ┌──────────────────────────┐ │ │ ┌───────────────────────┐ │
│ │ init() in helloinit.go │ │ │ │ │ │
│ │ [3] │ │ │ │init() in initdemo2.go │ │
│ │ main() in helloinit.go │ │ │ │ [1] │ │
│ └──────────────────────────┘ │ │ └───────────────────────┘ │
└────────────────────────────────┘ └───────────────────────────────┘
再次强调,如果包中有多个需要初始化的地方,把
init()
函数定义在每一个 go 代码文件中可能不是你想要的代码组织方式,尽可能将多个init
函数的逻辑放到一个文件单独管理起来可能是更好的选择,比如你还可以将该文件命名为 init.go,看到文件名会能猜到它的用途。