【Go】Go语言中的 Init 函数和 main 函数


一、init 函数

1. init 函数的作用

go语言中的 init 函数用于包(package)的 初始化 ,该函数是go语言的一个重要特性。

主要作用是:

  • 初始化 不能采用初始化表达式 初始化的变量。
  • 程序运行前的注册。
  • 实现sync.Once功能。
  • 其他

2. init 函数的特征

  1. init 函数是用于程序执行前(程序执行从main函数开始,init 位于main之前执行)做 包的初始化 的函数,比如初始化包里的变量等
  2. init 函数没有输入参数、返回值
  3. 每个包可以拥有多个init函数
  4. 包的每个源文件也可以拥有多个 init 函数
  5. 关于一个源文件内部、同一个包、不同包中的多个 init 函数的执行顺序规律在接下来详述
  6. init 函数不能被其他函数调用,而是在main函数执行之前,自动被程序调用

3. 多个 init 函数的执行顺序

  1. 对同一个go文件的 init() 调用顺序是 从上到下 的。
  2. 对同一个 package 中不同文件是按 文件名 字符串比较 “从小到大” 顺序调用各文件中的 init() 函数。
  3. 对于不同的package,如果不相互依赖的话,按照main包中 “先import的后调用” 的顺序调用其包中的init(),如果package存在依赖,则先调用最早被依赖的package中的init(),最后调用main函数。也就是说,包之间存在依赖关系的话,会先调用没有依赖关系的包的init(),依次到依赖关系最多的包。
  4. 如果init函数中使用了 println() 或者 print() 你会发现在执行过程中这两个不会按照你想象中的顺序执行。这两个函数官方只推荐在测试环境中使用,对于正式环境不要使用。

4. Go 程序的初始化

golang程序初始化先于main函数执行,由 runtime 进行初始化,初始化顺序如下:

  1. 初始化导入的包(包的初始化顺序并不是按导入顺序 “从上到下” 执行的,runtime需要解析包依赖关系,最先初始化没有依赖的包 ,与变量初始化依赖关系类似)
  2. 初始化包作用域的变量(该作用域的变量的初始化也并非按照 “从上到下、从左到右” 的顺序,runtime解析变量依赖关系,最先初始化没有依赖的变量)
  3. 执行包的 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

实例一说明:

  1. init 在函数 main 函数之前执行。
  2. 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 包内)

实例二说明:

  1. 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

实例三说明:

  1. 初始化顺序:变量初始化 -> 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

实例四说明:

  1. 我们先分析一下包的依赖关系:pack包依赖于 test_util 包; test_util 包不依赖于任何包。

    因此运行时,先初始化 test_util 包,再初始化 pack 包:即先执行 test_util 中的 init() 函数,再执行 pack 包中的 init() 函数。

    也就是说,程序执行时,会初始化你所导入的包,如果这些包之间存在依赖关系的话,会最先初始化没有依赖关系的包(即调用init()),依次类推,最后初始化依赖关系最多的包。

  2. 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

实例五说明:

  1. 同一个包中的不同源文件的 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

实例六说明:

  1. init 函数的主要用途:初始化不能使用初始化表达式初始化的变量

实例七

import _ "net/http/pprof"

说明:

Go语言对没有使用的导入包会编译报错,但是有时我们只想调用该包的 init 函数,不使用包导出的变量或者方法,这时就采用上面的导入方案。

执行上述导入后,init函数会启动一个异步协程采集该进程实例的资源占用情况,并以http服务接口方式提供给用户查询。


参考链接

  1. Init函数和main函数
  2. go语言的init函数
  3. 五分钟理解golang的init函数

おすすめ

転載: blog.csdn.net/weixin_44211968/article/details/121647596