golang init 函数执行顺序本文通过编写demo的方式,解释说明了golang中init函数的执行顺序以及常量、
序言
本文通过编写demo,解释说明golang init函数的执行顺序以及特点。
1. init的作用以及执行特点
Golang 中的 init
函数是每个 Go 文件默认存在的,而且可以有多个,顾名思意,init
函数的作用就是为程序初始化提供一个入口。在不同的包和 Go 文件中,init
函数的执行顺序是有一定规则的。最近在工作中遇到了一些与初始化相关的问题,于是这里编写了一个 demo 来验证 init
函数的执行顺序。想快速了解的同学,可以直接查看最后的图解,它清晰地展示了 init
函数的执行流程和优先级。
1.1 同一个go文件中可以有零个或多个init函数,其中他们的执行顺序与init代码的前后顺序有关
1.1.1 测试案例1——测试同一个go文件多个init的执行顺序
package main
import (
"fmt"
)
func init() {
fmt.Println(" init func 1 starting")
}
func init() {
fmt.Println(" init func 2 starting")
}
func init() {
fmt.Println(" init func 3 starting")
}
func main() {
fmt.Println(" main func starting")
}
从执行结果可以得出init方法的执行顺序是按照代码位置顺序来执行的
这时候我们把第三个init的代码顺序移动到2前面去
package main
import (
"fmt"
)
func init() {
fmt.Println(" init func 1 starting")
}
func init() {
fmt.Println(" init func 3 starting")
}
func init() {
fmt.Println(" init func 2 starting")
}
func main() {
fmt.Println(" main func starting")
}
可以看见执行结果
由此可以证明1.1的结论
1.2 同一个包里,init的执行是以包为单位的,只要某个包的函数在其他包被使用,那么这个包内所有go文件的所有init函数都执行,顺序就是go文件排列顺序,并且与该使用的函数在哪个go文件无关。
1.2.1 测试案例2
目录结构
测试代码
// main包的main.go文件
package main
import (
"fmt"
"go_test/testInit/testinitOne"
)
func main() {
testinitOne.NewTestInit()
fmt.Println(" main func starting")
}
// testinitOne包的1.go文件
package testinitOne
import "fmt"
func init() {
fmt.Println(" 这是 testInitOne 包的1.go文件的init执行")
}
// testinitOne包的1a.go文件,2.go和a.go以及b.go输出同理
package testinitOne
import "fmt"
func init() {
fmt.Println(" 这是 testInitOne 包的1a.go文件的init执行")
}
执行main函数后输出结果,可以看见执行结果与文件顺序相同,并且没有因为NewTestInit()函数在b.go就执行b.go文件的init,所以说,init的执行是以包为单位的,而不是看被外部使用的方法在哪个go文件就先执行哪个go文件的init。
1.3 有关包级常量和变量执行顺序:常量的初始化->包级的变量的初始化->init
1.3.1 测试案例3
目录结构
代码
// main.go 代码
package main
import (
"fmt"
"go_test/testInit/testConstAndVar"
)
func main() {
testConstAndVar.UserPackageTestConstAndVar()
fmt.Println("main starting")
}
// testInit代码
package testConstAndVar
import "fmt"
const PackageName = "const testConstAndVar init finish"
var VarName = func() string {
fmt.Println(PackageName) // 成功打印出来了说明常量以及初始化完了,之后才初始化包级变量
fmt.Println("now is init var : testConstAndVar.vatName")
return "varName"
}()
func UserPackageTestConstAndVar() {
fmt.Println("UserPackageTestConstAndVar() starting")
}
执行结果
2. 大全套案例,贯通初始化流程
目录结构
代码:
/**********testInitA包*****************/
// 1.go
package testInitA
import "fmt"
func init() {
fmt.Println("testInitA 1.go 第一个init函数 init")
}
func init() {
fmt.Println("testInitA 1.go 第二个init函数 init")
}
//const A1 = A2 + 1
//const A2 = A1 + 1
const testInitString1GO = "testInitA包的1.go文件的常量初始化完毕"
var testInitA1GOVar = func() string {
fmt.Println(testInitString1GO)
fmt.Println("testInitA包的1.go文件的包级变量testInitA1GOVar初始化")
return "testInitA包的1.go文件的变量"
}()
// 1A.go
package testInitA
import "fmt"
func init() {
fmt.Println("testInitA 1A.go init")
}
// 1B.go
package testInitA
import "fmt"
func init() {
fmt.Println("testInitA 1B.go init")
}
// 1C.go
package testInitA
import "fmt"
func init() {
fmt.Println("testInitA 1C.go init")
}
const testInitString1CGO = "testInitA包的1C.go文件的常量初始化完毕"
var testInitA1CGOVar = func() string {
fmt.Println(testInitString1CGO)
fmt.Println("testInitA包的1C.go文件的包级变量testInitA1CGOVar初始化")
return "testInitA包的1C.go文件的变量"
}()
func NewPackageTestInitA() {
}
/*******testInitB包*******/
// 1.go
package testInitB
import "fmt"
func init() {
fmt.Println("testInitB 1.go init")
}
//1A.go
package testInitB
import "fmt"
const testInitString1AGO = "testInitB包的1A.go文件的常量初始化完毕"
var testInitB1AGOVar = func() string {
fmt.Println(testInitString1AGO)
fmt.Println("testInitB包的1A.go文件的包级变量testInitB1AGOVar初始化")
return "testInitB包的1A.go文件的变量"
}()
func init() {
fmt.Println("testInitB 1B.go init")
}
func NewPackageTestInitB() {
}
/********testInit包******/
// main.go
package main
import (
"fmt"
"go_test/testInit/testInitA"
"go_test/testInit/testInitB"
)
const testMainConstInit = "testInit包的main.go文件的常量初始化完毕"
var testMainConstInitVar = func() string {
fmt.Println(testMainConstInit)
fmt.Println("testInit包的main.go文件的包级变量testMainConstInitVar初始化")
return "testInit包的的main.go文件的变量"
}()
func init() {
fmt.Println("main.go init1")
}
func init() {
fmt.Println("main.go init2")
}
func main() {
testInitA.NewPackageTestInitA()
testInitB.NewPackageTestInitB()
fmt.Println("main.go starting")
}
执行结果
可以看见这里的执行顺序是按照const->var->init来执行的,这里需要注意到是如下两行const是一起初始化的,而不是按照如下打印的顺序,因为没想到什么更好的测试办法,所以对于const的初始化结果是采取直接打印来判断的,这里读者需要清除,同一个包下的所有const需要都初始化完毕,才会进行到var初始化,而不是按照如下图的打印顺序,除此之外图中其他的打印顺序即是初始化的顺序。
这里对于const初始化不难想到一个要点,const初始化如果之间有相互依赖,类似于包的依赖,也是先初始化不依赖其他const的const,同理const也有可能初出现像包一样的循环依赖的问题,如下图就会出现循环依赖的问题,编译不会通过,不光const、package,var也是一样的,所有发生循环依赖的地方编译都不会通过。
const A1 = A2 + 1
const A2 = A1 + 1
3.总结:话不多说,一图胜千言
转载自:https://juejin.cn/post/7425822400179240997