入口函数与包的初始化:GO程序的执行顺序

main.main 函数:Go 应用的入口函数

  • main 包中的 main 函数,也就是 main.main,它是所有Go 可执行程序的用户层执行逻辑的入口函数

  • 可执行程序的 main 包必须定义 main 函数,否则 Go 编译器会报错。

  • main 函数返回就意味着整个 Go 程序的终结,而且你也不用管这个时候是否还有其他子 Goroutine 正在执行

  • 除了 main 包外,其他包也可以拥有自己的名为 main 的函数或方法。但按照 Go 的可见性规则(小写字母卡头的标识符为非导出标识符),非 main包中自定义的 main 函数仅限于包内使用

不过对于 main 包的main 函数来说,你还需要明确一点,就是它虽然是用户层逻辑的入口函数,但它却不一定是用户层第一个被执行的函数。

init 函数:Go 包的初始化函数

如果 main 包依赖的包中定义了 init 函数,或者是 main 包自身定义了 init 函数,那么 Go 程序在这个包初始化的时候,就会自动调用它的 init 函数,因此这些 init 函数的执行就都会发生在 main 函数之前

Go 程序中我们不能手工显式地调用 init,否则就会收到编译错误,

package main 

import "fmt" 

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

在构建并运行上面这些示例代码之后,Go 编译器会报下面这个错误:

$go run call_init.go 
./call_init.go:10:2: undefined: init

Go 包可以拥有不止一个 init 函数,每个组成 Go 包的 Go 源文件中,也可以定义

多个 init 函数。

所以说,在初始化 Go 包时,Go 会按照一定的次序,逐一、顺序地调用这个包的 init 函

数。一般来说,先传递给 Go 编译器的源文件中的 init 函数,会先被执行;而同一个源文

件中的多个 init 函数,会按声明顺序依次执行。

Go 包的初始化次序

  • 依赖包按“深度优先”的次序进行初始化;

  • 每个包内按以“常量 -> 变量 -> init 函数”的顺序进行初始化;

  • 包内的多个 init 函数按出现次序进行自动调用

init 函数的用途

  1. 重置包级变量值

2.对包级变量的复杂初始化。

var (
http2VerboseLogs bool // 初始化时默认值为false 
http2logFrameWrites bool // 初始化时默认值为false
 http2logFrameReads bool // 初始化时默认值为false 
http2inTests bool // 初始化时默认值为false 
)
func init() { 
    e := os.Getenv("GODEBUG") 
    if strings.Contains(e, "http2debug=1") { 
    http2VerboseLogs = true // 在init中对http2VerboseLogs的值进行重置
    }
    
    if strings.Contains(e, "http2debug=2") { 
    http2VerboseLogs = true // 在init中对http2VerboseLogs的值进行重置
    http2logFrameWrites = true // 在init中对http2logFrameWrites的值进行重置
    http2logFrameReads = true // 在init中对http2logFrameReads的值进行重置
    } 
}

我们可以看到,标准库 http 包定义了一系列布尔类型的特性开关变量,它们默认处于关闭

状态(即值为 false),但我们可以通过 GODEBUG 环境变量的值,开启相关特性开关。

可是这样一来,简单地将这些变量初始化为类型零值,就不能满足要求了,所以 http 包在

init 函数中,就根据环境变量 GODEBUG 的值,对这些包级开关变量进行了复杂的初始

化,从而保证了这些开关变量在 http 包完成初始化后,可以处于合理状态。

3.在 init 函数中实现“注册模式”。

import ( 
"database/sql" 
_ "github.com/lib/pq" 
)
func main() { 
    db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=ver 
    if err != nil { 
    log.Fatal(err) 
    }
    age := 21 
    rows, err := db.Query("SELECT name FROM users WHERE age = $1", age) 
    ... 
}

你可以看到示例代码是以空导入的方式导入 lib/pq 包的,main 函数中没有使用 pq 包的任何变量、函数或方法,这样就实现了对 PostgreSQL数据库的访问。而这一切的奥秘,全在 pq 包的 init 函数中:

func init() { 
sql.Register("postgres", &Driver{}) 
}

这个奥秘就在,我们其实是利用了用空导入的方式导入 lib/pq 包时产生的一个“副作

用”,也就是 lib/pq 包作为 main 包的依赖包,它的 init 函数会在 pq 包初始化的时候得

以执行。

从上面代码中,我们可以看到在 pq 包的 init 函数中,pq 包将自己实现的 sql 驱动注册

了 sql 包中。这样只要应用层代码在 Open 数据库的时候,传入驱动的名字(这里

是“postgres”),那么通过 sql.Open 函数,返回的数据库实例句柄对数据库进行的操

作,实际上调用的都是 pq 包中相应的驱动实现。

实际上,这种通过在 init 函数中注册自己的实现的模式,就有效降低了 Go 包对外的直接

暴露,尤其是包级变量的暴露,从而避免了外部通过包级变量对包状态的改动。

另外,从标准库 database/sql 包的角度来看,这种“注册模式”实质是一种工厂设计模式

的实现,sql.Open 函数就是这个模式中的工厂方法,它根据外部传入的驱动名称“生产”出不同类别的数据库实例句柄。

这种“注册模式”在标准库的其他包中也有广泛应用,比如说,使用标准库 image 包获取

各种格式图片的宽和高:

上面这个示例程序支持 png、jpeg、gif 三种格式的图片,而达成这一目标

的原因,正是 image/png、image/jpeg 和 image/gif 包都在各自的 init 函数中,将自

己“注册”到 image 的支持格式列表中了,你可以看看下面这个代码:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值