0.1、索引

https://waterflow.link/articles/1666090530880

1、概念

1.1、源文件里的代码执行顺序

init 函数是用于初始化应用程序状态的函数。 它不接受任何参数并且不返回任何结果(一个 func() 函数)。 初始化包时,将初始化包中的所有常量和变量声明。 然后,执行初始化函数。 下面是一个初始化主包的例子:

  1. package main
  2. import "fmt"
  3. // 1
  4. var a = func() int {
  5. fmt.Println("var a")
  6. return 0
  7. }()
  8. // 2
  9. func init() {
  10. fmt.Println("init")
  11. }
  12. // 1
  13. var b = func() int {
  14. fmt.Println("var b")
  15. return 1
  16. }()
  17. // 3
  18. func main() {
  19. fmt.Println("main")
  20. }

上面代码的初始化顺序是:

  1. 初始化常量/变量(虽然b在init函数后面,但是会首先初始化)
  2. 初始化init函数
  3. 执行main函数

我们看下打印的结果:

  1. go run 2.go
  2. var a
  3. var b
  4. init
  5. main

1.2、不同包的init函数执行顺序

初始化包时会执行一个 init 函数。 在下面的例子中,我们定义了两个包,main 和 redis,其中 main 依赖于 redis。 首先, 2 .go 是主包:

  1. package main
  2. import (
  3. "fmt"
  4. "go-demo/100gomistakes/2/redis"
  5. )
  6. // 2
  7. func init() {
  8. fmt.Println("main init")
  9. }
  10. // 3
  11. func main() {
  12. err := redis.Store("ni", "hao")
  13. fmt.Println(err)
  14. }

我们可以看到main包中调用了redis包的方法。

我们再看下redis包中的内容:

  1. package redis
  2. import "fmt"
  3. // 1
  4. func init() {
  5. fmt.Println("redis init")
  6. }
  7. func Store(key, value string) error {
  8. return nil
  9. }

因为main依赖redis,所以先执行redis包的init函数,然后是main包的init,然后是main函数本身。上面的代码中标明了执行顺序。

1.3、同一个包不同文件init执行顺序

我们可以为每个包定义多个初始化函数。 当我们这样做时,包内的 init 函数的执行顺序是基于源文件的字母顺序。 例如,如果一个包包含一个 a.go 文件和一个 b.go 文件,并且都有一个 init 函数,则首先执行 a.go init 函数。

但是如果我们把文件a.go改为ca.go,则会先执行b.go的init函数。

所以我们不应该依赖包中初始化函数的顺序。 实际上,这可能很危险,因为可以重命名源文件,从而可能影响执行顺序。

下图说明了这个问题:

1.4、同一个文件中的init函数

当然,我们还可以在同一个源文件中定义多个初始化函数:

  1. package main
  2. import (
  3. "fmt"
  4. "go-demo/100gomistakes/2/redis"
  5. )
  6. func init() {
  7. fmt.Println("main init1")
  8. }
  9. func init() {
  10. fmt.Println("main init2")
  11. }
  12. func main() {
  13. err := redis.Store("ni", "hao")
  14. fmt.Println(err)
  15. }

执行顺序是按照源文件顺序执行的,我们看下打印的结果:

  1. go run 2.go
  2. redis2 init
  3. redis init
  4. main init1
  5. main init2
  6. <nil>

1.5、以副作用方式执行的init函数

我们开发的时候经常会使用gorm查询数据库,所以我们经常会看到在初始化db连接的时候会有下面的代码:

  1. package models
  2. import (
  3. _ "github.com/go-sql-driver/mysql"
  4. "github.com/jinzhu/gorm"
  5. )

这种情况就是说,我们没有强依赖mysql包(没有直接使用mysql的公共函数)。但是我们需要初始化mysql包里的一些数据,也就是执行mysql包里的init函数。这个时候我们就可以在这个包的前面加上_

需要注意的是init函数不能被当作普通函数调用,会编译报错

2、使用场景

首先,让我们看一个使用 init 函数可能被认为不合适的示例:持有数据库连接池。 在示例的 init 函数中,我们使用 sql.Open 打开一个数据库。 这个全局变量db会被其他函数调用:

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. _ "github.com/go-sql-driver/mysql"
  6. "log"
  7. )
  8. var db *sql.DB
  9. func init() {
  10. d, err := sql.Open("mysql",
  11. "root:liufutian@tcp(127.0.0.1:3306)/test")
  12. if err != nil {
  13. log.Panic(err)
  14. }
  15. err := db.Ping()
  16. if err != nil {
  17. log.Panic(err)
  18. }
  19. db = d
  20. }
  21. func main() {
  22. }

在本例中,我们打开数据库,检查是否可以 ping 通,然后将其分配给全局变量。

但是这种实现会带来一些问题:

  1. init 函数中的错误管理是有限的。 实际上,由于 init 函数不返回错误,因此发出错误信号的唯一方法之一就是panic,导致应用程序停止。 在我们的示例中,如果打开数据库失败,无论如何都会停止应用程序。 但是,不一定要由包本身来决定是否停止应用程序。 有可能调用者想实现重试或使用回退机制。 在这种情况下,在 init 函数中打开数据库会阻止客户端包实现其错误处理逻辑。
  2. 如果我们向这个文件添加测试,init 函数将在运行测试用例之前执行,但是我们想要的是创建db连接的时候需要测试。 因此,此示例中的 init 函数使编写单元测试变得复杂。
  3. 该示例需要将数据库连接池分配给全局变量。 全局变量有一些严重的缺点; 例如:
    • 任何函数都可以更改包中的全局变量
    • 单元测试可能会更复杂,因为依赖于全局变量的函数将不再被隔离

在大多数情况下,我们应该倾向于封装一个变量而不是让它保持全局。

由于这些原因,之前的初始化可能应该封装到一个函数中处理,如下所示:

  1. func createDB() (*sql.DB, error) {
  2. d, err := sql.Open("mysql",
  3. "root:liufutian@tcp(127.0.0.1:3306)/test")
  4. if err != nil {
  5. return nil, err
  6. }
  7. err = db.Ping()
  8. if err != nil {
  9. return nil, err
  10. }
  11. return d, nil
  12. }

这样写的话,我们解决了之前讨论的主要缺点:

  • 是否处理错误留给调用者
  • 可以创建一个集成测试来检查此功能是否有效
  • 连接池封装在函数中

但是这样就是不能使用init函数了么?在我们上面的引入mysql驱动的例子中,说明使用init还是有帮助的:

  1. func init() {
  2. sql.Register("mysql", &MySQLDriver{})
  3. }

上面的例子,通过注册提供的驱动名称使数据库驱动程序可用。

golang中的init初始化函数的更多相关文章

  1. golang中的init函数以及main函数

    首先我们看一个例子:init函数: init 函数可在package main中,可在其他package中,可在同一个package中出现多次. main函数 main 函数只能在package ma ...

  2. pytorch系列 -- 9 pytorch nn.init 中实现的初始化函数 uniform, normal, const, Xavier, He initialization

    本文内容:1. Xavier 初始化2. nn.init 中各种初始化函数3. He 初始化 torch.init https://pytorch.org/docs/stable/nn.html#to ...

  3. golang中包的初始化

    1.当一个go源程序被初始化时,首先去初始化所依赖的其他包,然后初始化该go源码文件的全局变量的初始化和执行初始化函数,其中该包所有的全局变量初始化在前,该包的初始化函数int在后.当所有包的初始化函 ...

  4. golang中,map作为函数参数是如何传递的

    当你声明一个map的时候: m := make(map[int]int) 编译器会调用 runtime.makemap: // makemap implements a Go map creation ...

  5. golang中字符串内置函数整理

    字符串内置函数 1. 判断字符串的长度 str := "korea国" fmt.Println("str len=", len(str)) 2. 字符串遍历,同 ...

  6. 仙人指路,引而不发,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中New和Make函数的使用背景和区别EP16

    Golang只有二十五个系统保留关键字,二十几个系统内置函数,加起来只有五十个左右需要记住的关键字,纵观编程宇宙,无人能出其右.其中还有一些保留关键字属于"锦上添花",什么叫锦上添 ...

  7. golang中结构体当做函数参数或函数返回值都会被拷贝

    1. 结构体做函数的参数或返回值时,都会被重新拷贝一份如果不想拷贝,可以传递结构体指针 package main import "fmt" type Person struct { ...

  8. MATLAB——神经网络init初始化函数和adapt函数

  9. GCC中初始化函数是怎样被处理的?

    本文译至: http://gcc.gnu.org/onlinedocs/gccint/Initialization.html 如我们所知,在GCC通过给代码追加__attribute__((const ...

随机推荐

  1. LuoguP1725 琪露诺 (动态规划)

    \(单调队列\) 或 \(堆\) 优化 #include <iostream> #include <cstdio> #include <cstring> #incl ...

  2. java-Servlet生命周期与jsp

    容器如何处理请求资源路径? 比如,在浏览器地址栏输入:ip地址路径/abc.html. Step1.默认访问的是一个servlet,会从web.xml中查找有没有匹配的servlet.         ...

  3. java学习第一天.day01

    Java的编译和运行机制 java文件编译成字节码文件后加载到java缓存中jvm Java的基本语法 1.Java语言严格区分大小写 2.一个Java源文件里可以定义多个Java类,但不能存在多个p ...

  4. java数组---特点,边界

    数组的四个基本特点 1.其长度是确定的.数组一旦被创建,它的大小就是不可以改变的. 2.其元素必须是相同类型,不允许出现混合类型. 3.数组中的元素可以是任何数据类型,包括基本类型和引用类型. 4.数 ...

  5. day29--Java泛型02

    Java泛型02 5.自定义泛型 5.1自定义泛型类 基本语法: class 类名<T,R...>{//-表示可以有多个泛型 成员 } 注意细节: 普通成员可以使用泛型(属性.方法) 使用 ...

  6. nginx的安装和配置

    目录 目录 一.购买下载SSL证书 二.修改Nginx配置信息 三.重启Nginx 一.购买下载SSL证书 SSL证书阿里云做活动期间可以免费申请,购买SSL证书时选择单域名-DV SSL-免费版即可 ...

  7. luogu P1488 肥猫的游戏

    肥猫的游戏 P1488 肥猫的游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 野猫与胖子,合起来简称肥猫,是一个班的同学,他们也都是数学高手,所以经常在一起讨论数学问 ...

  8. Q准则涡识别方法

    Q准则(Q Criterion)在涡识别中计算效率高,效果也不错,是一种常用的涡提取/识别方法. 了解Q准则需要从速度梯度张量入手,而速度梯度张量可以分解成两部分 \[\frac{\partial u ...

  9. Pwn出题指南

    0x00:背景 最近在为社团招新赛出pwn题,发现网上关于出题方面的文章资料特别少,所以打算记录下自己出题的过程,便于网友们参考学习.本次出题采用了ctf_xinetd与pwn_deploy_chro ...

  10. 《Java基础——继承》

    Java基础--继承     一.类的继承: 规则: 1.一个子类只能有一个父类,但一个父类可以有多个子类,每个子类都可以作为父类使用. 2.若一个类没有声明父类,则默认其父类为Object. 3.父 ...