【Go语言学习】包、Init函数与执行顺序

一、包的概念

  1. Go语言的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称。这就像在一个已经存在的包中生成一个子目录一样,在编写代码时,导入包需要做的仅仅只是提供这个要被嵌套的包的相对路径。
  2. Go语言都是以包为组织的,类似于其他语言中的库和模块。
    代码中的层次结构,圈出的即为包名,其下可放多个源文件(注:此处IDE为VSCODE)
    在这里插入图片描述
  3. 用 import 语法后跟包名来导入包,会优先搜寻GOROOT/src 目录中寻找包目录,如果没有找到,则会去 GOPATH/src 目录中继续寻找。
package main//包名,一般和源文件目录名保持一致

import ( 	//包的导入关键字
	"fmt"	//go语言内部包,在goroot/src下存在
	"go测试程序/test01" //自定义包,在gopath/src存在
)

GOROOT下的fmt包
在这里插入图片描述
GOPATH下的test01包
在这里插入图片描述

  1. Go 语言中如果一个变量或函数的名称以大写字母开头就是可导出的,其他所有的名称不以大写字母开头的变量或函数都是这个包私有的。
  2. 被递归 import 的包的初始化顺序与 import 顺序相反,例如:导入顺序 main –> A –> B –> C,则初始化顺序为 C –> B –> A –> main
  3. 一个包被其它多个包 import,但只能被初始化一次
  4. main 包总是被最后一个初始化,因为它总是依赖别的包
  5. 如果一个程序是 main 包的一部分,那么在 go install 则会生成一个二进制文件,在执行时则会调用 main 函数。如果一个程序除了 main 包外还是其他包的一部分,那么在使用 go install 命令时会创建包存档文件。(run main函数=install+执行main)
  6. 当某个包只需要初始化不需要使用时,包名前可加"_"。

二、Init函数

  1. 像 main 函数一样,init 函数在包被初始化时被 Go 调用。它不需要任何参数也不返回任何值。init 函数由 由 Go 隐式调用,因此你无法从任何地方引用它,但可以使用func init() 这样来调用它。
  2. **init 函数的主要作用是将在全局代码中无法初始化的全局变量初始化。例如,数组的初始化。

三、代码执行顺序

Go语言代码执行顺序为

  1. 初始化所有被导入的包
  2. 初始化被导入的包所有全局变量
  3. 被导入的包init函数调用
  4. Main函数执行

其中init函数调用顺序为

  1. 同一个go文件的init()调用顺序是从上到下的。
  2. 同一个package中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的init()函数。
  3. 不同的package,按照main包中"先import的先调用"的顺序调用其包中的init()

一个例子
main\hello.go

package main //包名,一般和源文件目录名保持一致

import ( //包的导入关键字
	"fmt"           //go语言内部包,在goroot/src下存在
	"go测试程序/test01" //自定义包,当前路径为gopath/src
	"go测试程序/test02"
)

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

var testArg = test02.TestArg

func main() {
	fmt.Println("hello world!")
	fmt.Println("getValue", testArg)
	fmt.Println(test01.WriteHelloWorld())
	fmt.Println(test02.WriteHelloWorld())
}

test01\aaatest1.go

package test01

import (
	"fmt"
)

var testArg01 = getArg()

func init() {
	fmt.Println("init=======>aaatest1")
}

func getArg() string {
	fmt.Println("globle init=======>aaatest1")
	return "globle init=======>aaatest1"
}


test01\test01.go

package test01

import (
	"fmt"
)

var testArg = "getTest01Arg"

func init() {
	fmt.Println("init=======>test01 [1]")
}
func init() {
	fmt.Println("init=======>test01 [2]")
}

//WriteHelloWorld test.
func WriteHelloWorld() string {
	return "hello world from test01"
}

test02\test02.go

package test02

import (
	"fmt"
)

var testArg = GetTestArg() //无法被外部包访问
//TestArg globlearg.
var TestArg = GetTestArg()

func init() {
	fmt.Println("init=======>test02")
}

//GetTestArg test.
func GetTestArg() string {
	fmt.Println("Globle Init from test02")
	return "TestArgFromTest02"
}

//WriteHelloWorld test.
func WriteHelloWorld() string {
	return "hello world from test02"
}


在这里插入图片描述
运行结果解释:
1.从main开始执行,首先在GOROOT/src下找到fmt包,导入
2.在GOPATH/src/下找到test01包,导入
3.因aaatest1文件名字典序在test01之前,因此先对aaatest1中的全局变量进行初始化操作,输出“globle init=======>aaatest1”
4.执行aaatest1.go中的init函数,输出“init=======>aaatest1”
5.顺序执行test01的init函数,输出
“init=======>test01 [1]”
“init=======>test01 [2]”
6.在GOPATH/src/下找到test02包,导入
7.初始化test02.go的两个全局变量,输出两次“Globle Init from test02”
8.执行test02.go中的init函数,输出“init=======>test02”
9.init main包,输出“init=======>main”
10.顺序执行main函数,得到结果

四、VSCode编译的坑

1.如果遇到“exportedxxx should have comment or be unexportedgo-lint”这样的提示性信息(不影响程序运行),则需要向变量或函数加注释,注释规则为“变量名(函数名)+注释+句号”,即

//WriteHelloWorld test.
func WriteHelloWorld() string {
	return "hello world from test01"
}

2.如果发现在import加入包名后保存,VSCode自动删除了所加入包的代码,则需要查看该包的路径是否正确,若不正确VScode会自动删除包的引用。

<think>我们正在讨论Go语言中init函数执行机制以及是否必须定义init函数的问题。根据引用[1]的内容,我们知道当多个源文件都有init函数时,执行顺序是从main的import开始,依次执行导入init函数。如果被导入的又导入其他,则形成链式调用,类似于函数调用栈,最后被导入的执行init函数,然后依次向上返回。关于第一个问题,init函数执行机制:1.一个可以有多个init函数(可以在多个源文件中定义),它们按照源文件名的字典序(lexicographicalorder)被调用。2.init函数全局变量初始化之后被调用。3.当一个被导入时,首先初始化级变量,然后按照字典序调用该的所有init函数。4.导入时,如果该还导入了其他,则先递归执行其他的初始化(括变量初始化和init函数)。5.同一个内的多个init函数执行顺序按源文件名字典序。6.所有被导入的只会被初始化一次,即init函数只会执行一次。关于第二个问题,是否必须定义init函数?-答案是不必须。Go语言中,init函数是可选的。一个可以没有init函数。如果内没有定义init函数,那么在导入该时就不会执行init函数,但仍然会进行级变量的初始化。为了更清晰地说明,我们可以用代码示例来展示init函数执行顺序:假设有如下结构:```main.gopackageA/a1.goa2.gopackageB/b.go```在main.go中导入packageA,而packageA又导入packageB。执行顺序:1.初始化packageB的变量,然后按字典序执行packageB的init函数(如果有多个源文件)。2.初始化packageA的变量,然后按字典序执行packageA的init函数(注意:a1.go和a2.go按照文件名排序)。3.最后执行maininit函数(如果有),然后执行main函数。注意:同一个中多个init函数按照源文件名字典序(字母顺序执行,因此通常建议不要依赖init函数执行顺序,而是让它们相互独立。如果确实有顺序要求,应将它们放在同一个源文件中(按照代码顺序执行)或者通过其他方式(如变量初始化)来保证顺序。另外,需要强调:init函数不能被显式调用,它是由Go运行时在初始化时自动调用的。下面我们通过一个简单的代码示例来说明:在packageB的b.go中:```gopackagepackageBimport"fmt"varB="B"funcinit(){fmt.Println("initinb.go")}```在packageA的a1.go中:```gopackagepackageAimport("fmt""packageB")varA1="A1"funcinit(){fmt.Println("initina1.go")fmt.Println("importedB:",packageB.B)}```在packageA的a2.go中(注意文件名a2在a1之后,字典序a1在a2前):```gopackagepackageAimport"fmt"varA2="A2"funcinit(){fmt.Println("initina2.go")}```在main.go中:```gopackagemainimport("fmt""packageA")funcinit(){fmt.Println("initinmain")}funcmain(){fmt.Println("mainfunction")}```执行顺序将是:1.初始化packageB的变量(B="B"),然后执行packageB的init函数(打印"initinb.go")。2.初始化packageA的变量(A1="A1",A2="A2"),然后按字典序执行packageA的init函数:先a1.go中的init(打印"initina1.go"和"importedB:B"),后a2.go中的init(打印"initina2.go")。3.执行maininit函数(打印"initinmain")。4.执行main函数(打印"mainfunction")。输出结果:```initinb.goinitina1.goimportedB:Binitina2.goinitinmainmainfunction```以上例子清晰地展示了init函数执行顺序。综上所述,init函数执行机制遵循从最底层的依赖开始,逐层向上,同一内按源文件名字典序执行并不必须定义init函数,完全可以根据需要选择是否使用。如果您有其他疑问,可以继续提问。§§相关问题§§1.如何避免init函数之间的顺序依赖?2.在init函数中能否安全地使用其他?3.Goinit函数全局变量初始化之间的顺序是怎样的?4.init函数能否被多次调用?5.如果多个之间形成循环导入,init函数会如何执行?</think> ### Go语言中init函数执行机制 在Go语言中,`init`函数是一种特殊的初始化函数,其执行机制遵循以下规则: 1. **执行顺序**: - 从`main`的`import`语句开始,按照**深度优先原则**递归执行: ``` main → A → B → ... → 最深层 ↖ 逐层返回向上执行 ``` - 每个内部按源文件**字典序**(文件名顺序执行`init`函数[^1] 2. **执行时机**: - 在级变量初始化后自动执行 - 在`main()`函数前自动执行 - 每个的`init`只执行一次(即使多次导入) 3. **示例流程**: ```go // main.go import "A" → import "B" → A.init() → 调用A内部字典序的init B.init() → 调用B内部字典序的init ... ← 返回执行路径 main() ``` ### 是否必须定义init函数? **不必须**: 1. init函数是可选特性 2. 没有init也能正常导入使用 3. 存在场景: - 需要初始化数据库连接池 - 注册自定义驱动/插件 - 预加载配置文件等一次性操作 4. **最佳实践**: ```go // 推荐将相关init放在同一文件避免顺序依赖 package config var Settings map[string]string func init() { // 可选初始化 Settings = loadConfig("app.yaml") } ``` ### 关键注意事项 1. **避免行为**: - 不要依赖不同的`init`执行顺序 - 不要在`init`中处理阻塞操作 - 避免在`init`中修改全局状态(除非必要) 2. **普通函数区别**: | 特性 | init函数 | 普通函数 | |------------|------------------|--------------| | 调用方式 | 自动调用 | 需显式调用 | | 数量 | 每个文件可有多个 | 不受限 | | 可见性 | 内隐式可见 | 需大写导出 |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值