Go语言中的init函数

基本概念:

在Go语言中,init 函数是一个特殊的函数,它在程序执行开始之前自动执行。每个包可以包含一个或多个 init 函数,这些函数会在包被导入时自动执行。init 函数的主要用途包括:

  1. 初始化包级别的变量 :可以在 init 函数中对包级别的变量进行初始化。
  2. 执行一些初始化逻辑:例如,打开数据库连接、读取配置文件等。
  3. 注册服务或插件 :在某些情况下,init 函数可以用来注册服务或插件。

特点

  • 自动执行init 函数在包被导入时自动执行,不需要显式调用。
  • 无参数和返回值init 函数不能有任何参数,也不能有返回值。
  • 顺序执行 :在一个包中,所有的 init 函数会按照它们在文件中的出现顺序依次执行。如果一个包导入了其他包,那么被导入的包的 init 函数会先执行。

init 函数的执行顺序

1.文件结构简单,没有其他依赖的场景:

在Go语言中,一个文件中可以定义多个 init 函数。这些 init 函数的执行顺序遵循以下规则:

  1. 按代码顺序执行 :在一个文件中,多个 init 函数会按照它们在文件中出现的顺序依次执行。
  2. 文件间的顺序 :如果一个包中有多个文件,Go编译器会按照文件名的字母顺序加载这些文件,并依次执行每个文件中的 init 函数。

示例

假设有一个包 example,其中包含两个文件 a.gob.go,它们的代码如下:

a.go

go 复制代码
package example

import "fmt"

func init() {
    fmt.Println("init 1 in a.go")
}

func init() {
    fmt.Println("init 2 in a.go")
}

b.go

go 复制代码
package example

import "fmt"

func init() {
    fmt.Println("init 1 in b.go")
}

func init() {
    fmt.Println("init 2 in b.go")
}

main.go

go 复制代码
package main

import (
    "example"
    "fmt"
)

func main() {
    fmt.Println("main function")
}

执行顺序

  1. 文件加载顺序 :Go编译器会按照文件名的字母顺序加载文件,因此 a.go 会先于 b.go 被加载。
  2. 文件内 init 函数的执行顺序 :在每个文件中,init 函数会按照它们在文件中出现的顺序依次执行。

因此,程序的输出顺序将是:

css 复制代码
init 1 in a.go
init 2 in a.go
init 1 in b.go
init 2 in b.go
main function

这种设计确保了 init 函数的执行顺序是可预测的,开发者可以根据需要合理安排 init 函数的位置和顺序。

2.文件结构相对复杂,存在导入其他包的场景

init 函数的执行顺序不仅与当前包内的文件和代码顺序有关,还与包的导入关系有关。如果一个文件导入了其他包,并且这些被导入的包中也有 init 函数,那么执行顺序会更加复杂。

执行顺序规则

  1. 包的初始化顺序 :Go会按照包的依赖关系,从最底层的包开始初始化。如果包 A 导入了包 B,那么包 Binit 函数会先于包 Ainit 函数执行。
  2. 文件的加载顺序 :在同一个包内,文件按照文件名的字母顺序加载。每个文件中的 init 函数按照它们在文件中出现的顺序依次执行。
  3. 文件内 init 函数的执行顺序 :在同一个文件中,多个 init 函数按照它们在文件中出现的顺序依次执行。
  4. 导入包的 init 函数优先执行 :如果一个文件导入了其他包,那么被导入包的 init 函数会先于当前文件的 init 函数执行。

示例

假设有以下包和文件结构:

example

  • a.go:导入了包 sub1
  • b.go:导入了包 sub2

sub1

  • sub1.go:定义了一个 init 函数。

sub2

  • sub2.go:定义了一个 init 函数。

main.go

  • 导入了包 example

sub1/sub1.go

go 复制代码
package sub1

import "fmt"

func init() {
    fmt.Println("init in sub1")
}

sub2/sub2.go

go 复制代码
package sub2

import "fmt"

func init() {
    fmt.Println("init in sub2")
}

example/a.go

go 复制代码
package example

import (
    "example/sub1"
    "fmt"
)

func init() {
    fmt.Println("init 1 in a.go")
}

func init() {
    fmt.Println("init 2 in a.go")
}

example/b.go

go 复制代码
package example

import (
    "example/sub2"
    "fmt"
)

func init() {
    fmt.Println("init 1 in b.go")
}

func init() {
    fmt.Println("init 2 in b.go")
}

main.go

go 复制代码
package main

import (
    "example"
    "fmt"
)

func main() {
    fmt.Println("main function")
}

执行顺序分析

  1. 包的依赖关系
    • example 包依赖于 sub1sub2
    • sub1sub2 是最底层的包,它们的 init 函数会先执行。
  1. 文件的加载顺序
    • example 包中,文件 a.gob.go 按照文件名的字母顺序加载,因此 a.go 会先于 b.go 加载。
  1. init 函数的执行顺序
    • 首先执行 sub1 包的 init 函数。
    • 然后执行 sub2 包的 init 函数。
    • 接着执行 a.go 中的 init 函数(按顺序)。
    • 最后执行 b.go 中的 init 函数(按顺序)。

输出结果

程序的输出顺序将是:

csharp 复制代码
init in sub1
init in sub2
init 1 in a.go
init 2 in a.go
init 1 in b.go
init 2 in b.go
main function

从执行结果来看,同一个文件中的 init 函数在程序的生命周期中只会执行一次,无论这个文件被导入多少次。

Go 文件的初始化顺序

在Go语言中,文件的初始化顺序遵循一定的规则,以确保程序在启动时正确地初始化各个包和变量。

1. 包级别的初始化顺序

Go程序的初始化从main包开始,然后按照依赖关系递归地初始化其他包。具体来说,包的初始化顺序如下:

  1. 包级别的变量初始化
    • 在同一个包内,所有包级别的变量(即在函数外部声明的变量)按照它们在文件中出现的顺序进行初始化。
    • 如果变量的初始化表达式依赖于其他变量,那么这些变量会先被初始化。
  1. init函数
    • 每个包可以包含零个或多个init函数。init函数在包级别的变量初始化之后自动执行。
    • 同一个包内的init函数按照它们在文件中出现的顺序执行。
    • 不同文件中的init函数按照文件名的字母顺序执行。

2. 包之间的初始化顺序

Go程序的初始化是按照包的依赖关系进行的。具体来说:

  1. 依赖关系
    • 如果包A依赖于包B,那么包B会在包A之前被初始化。
    • 这种依赖关系是通过导入语句(import)来确定的。
  1. 递归初始化
    • Go编译器会根据导入关系构建一个依赖图,并按照拓扑排序的顺序来初始化各个包。
    • 这意味着所有被依赖的包会在依赖它们的包之前被初始化。

3. main包的初始化

main包是Go程序的入口点,它的初始化顺序如下:

  1. 包级别的变量初始化
    • main包中的所有包级别变量按照它们在文件中出现的顺序进行初始化。
  1. init函数
    • main包中的init函数在包级别变量初始化之后执行。
  1. main函数
    • 最后,main函数在所有init函数执行完毕后被调用,程序开始执行。

4. 示例

假设有以下文件结构:

css 复制代码
main.go
pkgA/a.go
pkgB/b.go

main.go内容如下:

go 复制代码
package main

import (
    "example/pkgA"
    "example/pkgB"
)

var mainVar = 10

func init() {
    fmt.Println("main init")
}

func main() {
    fmt.Println("main function")
}

pkgA/a.go内容如下:

go 复制代码
package pkgA

import "fmt"

var aVar = 20

func init() {
    fmt.Println("pkgA init")
}

pkgB/b.go内容如下:

go 复制代码
package pkgB

import "fmt"

var bVar = 30

func init() {
    fmt.Println("pkgB init")
}

假设main.go中导入了pkgApkgB,那么初始化顺序如下:

  1. 初始化pkgA包中的aVar
  2. 执行pkgA包中的init函数。
  3. 初始化pkgB包中的bVar
  4. 执行pkgB包中的init函数。
  5. 初始化main包中的mainVar
  6. 执行main包中的init函数。
  7. 调用main函数。

输出结果可能是:

csharp 复制代码
pkgA init
pkgB init
main init
main function
相关推荐
白泽来了5 小时前
2个小时1.5w字| React & Golang 全栈微服务实战
笔记·go·react
我的golang之路果然有问题10 小时前
案例速成GO+redis 个人笔记
经验分享·redis·笔记·后端·学习·golang·go
大鹏dapeng12 小时前
使用gonectr操作gone项目,包括:创建项目、安装依赖、生成代码、编译和运行
后端·go·github
pedestrian_h12 小时前
gin框架学习笔记
笔记·学习·go·web·gin
朱颜辞镜花辞树‎14 小时前
关于GoWeb(1)
go·web
纪元A梦14 小时前
华为OD机试真题——绘图机器(2025A卷:100分)Java/python/JavaScript/C++/C/GO最佳实现
java·javascript·c++·python·华为od·go·华为od机试题
古月的三个锦囊16 小时前
Nginx openresty web服务 与 Go 原生web服务性能对比
nginx·go·openresty
chxii17 小时前
18.2.go语言redis中使用lua脚本
redis·go·lua
用户01422600298418 小时前
Go语言 Map 详解
go
孔令飞18 小时前
Go 1.24 中的弱指针包 weak 使用介绍
人工智能·云原生·go