浅谈golang package中init方法的多处定义及运行顺序问题
在不了解这个问题之前,在网上搜索一下竟然搜出了两个完全相反的结果,所以打算自己测试下这个问题。
首先给出结论:
在同一个package中,可以多个文件中定义init方法
在同一个go文件中,可以重复定义init方法
在同一个package中,不同文件中的init方法的执行按照文件名先后执行各个文件中的init方法
在同一个文件中的多个init方法,按照在代码中编写的顺序依次执行不同的init方法
下面看下测试的代码:
在当前目录下新建main.go及testinit目录,在testinit目录下共有三个文件:123.go、ini1.go、ini2.go,各个源码文件分别如下:
123.go
package testinit import "fmt" func init(){ fmt.Println("123init") }
ini1.go
package testinit import "fmt" func init(){ fmt.Println("init1") } func init(){ fmt.Println("init1-2") }
ini2.go
package testinit import "fmt" func init(){ fmt.Println("init2") }
main.go
package main import ( _ "./testinit" "fmt" ) func main(){ fmt.Println("main") }
如上main.go中导入testinit package,然后go build main.go,执行显示如下:
从运行的结构就能很清晰的看到,123、ini1、ini2三个文件按照文件名执行,对于ini1.go中的两个ini方法按照init方法编写的先后顺序执行,最后才执行的main方法!
补充:Golang中defer、return、返回值和main、init函数的陷阱
Go语言中延迟函数defer充当着 cry...catch 的重任,使用起来也非常简便,然而在实际应用中,很多gopher并没有真正搞明白defer、return和返回值之间的执行顺序。他们的特点:
多个defer的执行顺序为“后进先出”;
defer、return、返回值三者的执行逻辑应该是:return最先执行,return负责将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出。
如何解释两种结果的不同:
上面两段代码的返回结果之所以不同,其实从上面第2条结论很好理解。
a()int 函数的返回值没有被提前声名,其值来自于其他变量的赋值,而defer中修改的也是其他变量,而非返回值本身,因此函数退出时返回值并没有被改变。
b()(i int) 函数的返回值被提前声名,也就意味着defer中是可以调用到真实返回值的,因此defer在return赋值返回值 i 之后,再一次地修改了 i 的值,最终函数退出后的返回值才会是defer修改过的值。
package main import ( "fmt" ) func main() { fmt.Println("c return:", *(c())) // 打印结果为 c return: 2 } func c() *int { var i int defer func() { i++ fmt.Println("c defer2:", i) // 打印结果为 c defer: 2 }() defer func() { i++ fmt.Println("c defer1:", i) // 打印结果为 c defer: 1 }() return &i }
虽然 c()*int 的返回值没有被提前声明,但是由于 c()*int 的返回值是指针变量,那么在return将变量 i 的地址赋给返回值后,defer再次修改了 i 在内存中的实际值,因此函数退出时返回值虽然依旧是原来的指针地址,但是其指向的内存实际值已经被成功修改了。
Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。
Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。
当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。
等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。
下图详细地解释了整个执行过程:
以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。