你好,我今天想跟你聊聊 Go语言里面一个很有趣、很重要的小机制 —— init
函数。 init
虽然平时用得不多,但每次用到,它都是救场王。 我尽量用很自然的方式告诉你,到底init
是干什么的,它的规则又是什么。
什么是 init
函数?
简单说,init
函数就是Go语言帮我在程序启动时自动调用的函数。 我不需要去手动调用它,也不用担心忘了调用。只要我写了,它就一定会在合适的时候自己跑起来。
它的主要目的是:
-
在程序真正运行之前,做一些必要的初始化工作。
-
比如,设置一些全局变量,连接数据库,或者提前加载一些资源。
总结一句话:
init
是专门为初始化准备的小工人,默默在程序启动时帮我打好地基。
init
函数的基本特点
我这里总结了一些init
函数的“性格”,你得了解清楚:
-
没有参数,也没有返回值。 写的时候不能传参,也不能有返回值。
-
每个包可以有多个
init
函数。 可以在一个文件里写多个init
,也可以分散在不同文件里,各自独立。 -
执行顺序是确定的,不会乱。
-
先初始化依赖的包,再初始化当前包。
-
一个包内,按照文件的编译顺序(一般是文件名字母顺序)执行
init
。 -
同一个文件内,多个
init
按照写的顺序执行。
-
-
main包里的
init
最后执行。 所有引入的包init
跑完之后,才轮到main
里的init
。
举个简单例子
我写段小代码,演示一下。
a.go package main import "fmt" func init() { fmt.Println("init from a.go") } func main() { fmt.Println("main function") }
运行一下,输出是:
init from a.go main function
你看,init
比main
先执行了。
再举一个多包的例子
比如我有两个包,pkg1
和 main
:
pkg1/pkg1.go package pkg1 import "fmt" func init() { fmt.Println("pkg1 init") } func Pkg1Func() { fmt.Println("pkg1 function") } main.go package main import ( "fmt" "yourproject/pkg1" ) func init() { fmt.Println("main init") } func main() { fmt.Println("main function") pkg1.Pkg1Func() }
运行结果:
pkg1 init main init main function pkg1 function
注意到没有?
-
pkg1
的init
先跑, -
然后才是
main
自己的init
, -
最后才是
main()
函数。
因为Go的规则是:先初始化被依赖的包,然后才到自己。
一个包里有多个init
怎么办?
这其实也很好理解。 如果我在同一个文件里写了两个init
,Go会按照我写的顺序来执行。 如果是不同文件,就按照文件名字母排序,一个个来。
例如:
a.go package main import "fmt" func init() { fmt.Println("init in a.go") } b.go package main import "fmt" func init() { fmt.Println("init in b.go") }
运行结果是:
init in a.go init in b.go main function
因为a.go
字母比b.go
小,先编译a.go,自然也先执行a.go的init
。
为什么需要 init
?我自己main里初始化不行吗?
这个问题我一开始也想过。 实际上,init
就是为了解耦初始化和执行逻辑。
比如说:
-
某个工具库,里面有个变量需要在程序一开始就设置好。
-
但又不希望使用者去记得显式调用某个
Setup()
函数。 -
这时候用
init
,包引入了,初始化也就自动做好了,使用体验更好。
再比如数据库连接:
var DB *sql.DB func init() { var err error DB, err = sql.Open("mysql", "user:password@/dbname") if err != nil { log.Fatal(err) } }
只要引入了这个包,数据库就连接好了,其他人用DB
就行了,不需要每次都记得去初始化。
注意事项 ⚡
这里我提醒一下几点小坑,别踩了:
-
init
尽量轻量! 不要做特别重的操作,比如网络IO、循环特别多的逻辑,容易拖慢启动速度。 -
不要写成依赖顺序复杂的
init
链。 比如A的init
里要等B,B又等A,那就完了,死循环或者出错了。 -
保持可读性。 如果初始化逻辑很复杂,不如拆出去单独写成
Setup()
方法,让init
里只是简单调用。
总结
我自己理解init
,其实就像程序的幕后化妆师。 观众(用户)只看到程序光鲜亮丽地跑起来了, 而init
在台下悄悄布置好灯光、化好妆、摆好舞台。
它不抢戏,但没有它,戏根本开不了场。
小结版
特性 | 说明 |
---|---|
自动调用 | 程序启动时自动执行 |
无参数无返回值 | 语法上必须这样 |
顺序执行 | 按导入顺序、文件顺序 |
轻量快速 | 建议避免耗时操作 |
适合做初始化工作 | 变量初始化、连接配置 |
👉 立即点击链接,开启你的全栈开发之路:Golang全栈开发完整课程