文章目录
一、init 函数
1. init 函数的作用
go语言中的 init 函数用于包(package)的 初始化 ,该函数是go语言的一个重要特性。
主要作用是:
- 初始化 不能采用初始化表达式 初始化的变量。
- 程序运行前的注册。
- 实现sync.Once功能。
- 其他
2. init 函数的特征
- init 函数是用于程序执行前(程序执行从main函数开始,init 位于main之前执行)做 包的初始化 的函数,比如初始化包里的变量等
- init 函数没有输入参数、返回值
- 每个包可以拥有多个init函数
- 包的每个源文件也可以拥有多个 init 函数
- 关于一个源文件内部、同一个包、不同包中的多个 init 函数的执行顺序规律在接下来详述
- init 函数不能被其他函数调用,而是在main函数执行之前,自动被程序调用
3. 多个 init 函数的执行顺序
- 对同一个go文件的 init() 调用顺序是 从上到下 的。
- 对同一个 package 中不同文件是按 文件名 字符串比较 “从小到大” 顺序调用各文件中的 init() 函数。
- 对于不同的package,如果不相互依赖的话,按照main包中 “先import的后调用” 的顺序调用其包中的init(),如果package存在依赖,则先调用最早被依赖的package中的init(),最后调用main函数。也就是说,包之间存在依赖关系的话,会先调用没有依赖关系的包的init(),依次到依赖关系最多的包。
- 如果init函数中使用了 println() 或者 print() 你会发现在执行过程中这两个不会按照你想象中的顺序执行。这两个函数官方只推荐在测试环境中使用,对于正式环境不要使用。
4. Go 程序的初始化
golang程序初始化先于main函数执行,由 runtime
进行初始化,初始化顺序如下:
- 初始化导入的包(包的初始化顺序并不是按导入顺序 “从上到下” 执行的,runtime需要解析包依赖关系,最先初始化没有依赖的包 ,与变量初始化依赖关系类似)
- 初始化包作用域的变量(该作用域的变量的初始化也并非按照 “从上到下、从左到右” 的顺序,runtime解析变量依赖关系,最先初始化没有依赖的变量)
- 执行包的 init 函数
二、main 函数
Go语言程序的默认 入口 函数(主函数):func main(),main函数返回后,程序也就结束了。
函数体用{}一对括号包裹:
func main(){
//函数体
}
三、init 函数和 main 函数的异同
相同点:
两个函数在定义时 不能有任何的参数和返回值 ,且 Go 程序 自动调用(一般函数都要先声明,再调用。而 init 函数和 main 函数只声明就行,Go 程序会自己进入)。
不同点:
init可以应用于任意包中,且可以重复定义多个。
main函数只能用于main包中,且只能定义一个。
四、实例
实例一
package main
import (
"fmt"
)
func main() {
fmt.Println("do in main")
}
// the other init function in this go source file
func init() {
fmt.Println("do in init")
}
func testf() {
fmt.Println("do in testf")
//if uncomment the next statment, then go build give error message : .\gprog.go:19: undefined: init
//init()
}
输出结果:
do in init
do in main
实例一说明:
- init 在函数 main 函数之前执行。
- init 函数和 main 函数都是自动被程序调用(只定义就行,程序自动进入执行,不用显式调用。而且不能在其他函数中调用,显式调用会报该函数未定义)。
实例二
编辑两个文件:
文件1:文件名 inma.go,内容如下
package main
import (
"fmt"
)
// the other init function in this go source file
func init() {
fmt.Println("do in init")
}
func main() {
fmt.Println("do in main")
}
func testf() {
fmt.Println("do in testf")
//if uncomment the next statment, then go build give error message : .\gprog.go:19: undefined: init
//init()
}
文件2:文件名 inin.go,内容如下
package main
import (
"fmt"
)
// the first init function in this go source file
func init() {
fmt.Println("do in init1")
}
// the second init function in this go source file
func init() {
fmt.Println("do in init2")
}
编译上面两个文件:go build inma.go inin.go
编译之后执行 inma.exe,输出结果:
do in init
do in init1
do in init2
do in main
过程截图:(这两个文件都在 hello 包内)
实例二说明:
-
inma.go 中的 init 函数先执行,然后执行了 inin.go 中的两个 init 函数,然后才执行 main 函数。
这说明一个包内的所有go源文件中的所有 init 函数执行完之后,才会执行 main 函数。
实例三
package main
import (
"fmt"
)
var T int64 = a()
func init() {
fmt.Println("init in main.go ")
}
func a() int64 {
fmt.Println("calling a()")
return 2
}
func main() {
fmt.Println("calling main")
}
输出结果:
calling a()
init in main.go
calling main
实例三说明:
- 初始化顺序:变量初始化 -> init() -> main()
实例四
创建三个包:
pack 包里有一个源文件 pack.go :
package pack
import (
"fmt"
"test_util"
)
var Pack int = 6
func init() {
a := test_util.Util
fmt.Println("init pack ", a)
}
test_util 包里有一个源文件 test_util.go :
package test_util
import "fmt"
var Util int = 5
func init() {
fmt.Println("init test_util")
}
hello 包里有一个源文件 main.go :
package main
import (
"fmt"
"pack"
"test_util"
)
func main() {
fmt.Println(pack.Pack)
fmt.Println(test_util.Util)
}
编译这三个包,并执行 hello.exe (即main.go):
输出结果:
init test_util //执行test_util包的init()函数
init pack 5 //执行pack包的init()函数
6 //hello包的main函数:pack.Pack
5 //hello包的main函数:test_util.Util
实例四说明:
-
我们先分析一下包的依赖关系:pack包依赖于 test_util 包; test_util 包不依赖于任何包。
因此运行时,先初始化 test_util 包,再初始化 pack 包:即先执行 test_util 中的 init() 函数,再执行 pack 包中的 init() 函数。
也就是说,程序执行时,会初始化你所导入的包,如果这些包之间存在依赖关系的话,会最先初始化没有依赖关系的包(即调用init()),依次类推,最后初始化依赖关系最多的包。
-
init 在函数 main 函数之前执行。执行完所有 init 函数,才会执行main 函数。
实例五
在同一个包中写三个源文件:
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
}
z.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 函数执行顺序,是源文件名称的字典序。
实例六
package main
import "fmt"
var initArg [20]int
func init() {
initArg[0] = 10
for i := 1; i < len(initArg); i++ {
initArg[i] = initArg[i-1] * 2
}
}
func main() {
fmt.Println(initArg)
fmt.Println("main")
}
输出结果:
[10 20 40 80 160 320 640 1280 2560 5120 10240 20480 40960 81920 163840 327680 655360 1310720 2621440 5242880]
main
实例六说明:
- init 函数的主要用途:初始化不能使用初始化表达式初始化的变量
实例七
import _ "net/http/pprof"
说明:
Go语言对没有使用的导入包会编译报错,但是有时我们只想调用该包的 init 函数,不使用包导出的变量或者方法,这时就采用上面的导入方案。
执行上述导入后,init函数会启动一个异步协程采集该进程实例的资源占用情况,并以http服务接口方式提供给用户查询。