重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
https://waterflow.link/articles/
创新互联建站长期为成百上千客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为大姚企业提供专业的成都网站制作、成都做网站,大姚网站改版等技术服务。拥有10余年丰富建站经验和众多成功案例,为您定制开发。
init 函数是用于初始化应用程序状态的函数。 它不接受任何参数并且不返回任何结果(一个 func() 函数)。 初始化包时,将初始化包中的所有常量和变量声明。 然后,执行初始化函数。 下面是一个初始化主包的例子:
package main
import "fmt"
// 1
var a = func() int {
fmt.Println("var a")
return 0
}()
// 2
func init() {
fmt.Println("init")
}
// 1
var b = func() int {
fmt.Println("var b")
return 1
}()
// 3
func main() {
fmt.Println("main")
}
上面代码的初始化顺序是:
我们看下打印的结果:
go run 2.go
var a
var b
init
main
初始化包时会执行一个 init 函数。 在下面的例子中,我们定义了两个包,main 和 redis,其中 main 依赖于 redis。 首先, 2 .go 是主包:
package main
import (
"fmt"
"go-demo/100gomistakes/2/redis"
)
// 2
func init() {
fmt.Println("main init")
}
// 3
func main() {
err := redis.Store("ni", "hao")
fmt.Println(err)
}
我们可以看到main包中调用了redis包的方法。
我们再看下redis包中的内容:
package redis
import "fmt"
// 1
func init() {
fmt.Println("redis init")
}
func Store(key, value string) error {
return nil
}
因为main依赖redis,所以先执行redis包的init函数,然后是main包的init,然后是main函数本身。上面的代码中标明了执行顺序。
我们可以为每个包定义多个初始化函数。 当我们这样做时,包内的 init 函数的执行顺序是基于源文件的字母顺序。 例如,如果一个包包含一个 a.go 文件和一个 b.go 文件,并且都有一个 init 函数,则首先执行 a.go init 函数。
但是如果我们把文件a.go改为ca.go,则会先执行b.go的init函数。
所以我们不应该依赖包中初始化函数的顺序。 实际上,这可能很危险,因为可以重命名源文件,从而可能影响执行顺序。
下图说明了这个问题:
当然,我们还可以在同一个源文件中定义多个初始化函数:
package main
import (
"fmt"
"go-demo/100gomistakes/2/redis"
)
func init() {
fmt.Println("main init1")
}
func init() {
fmt.Println("main init2")
}
func main() {
err := redis.Store("ni", "hao")
fmt.Println(err)
}
执行顺序是按照源文件顺序执行的,我们看下打印的结果:
go run 2.go
redis2 init
redis init
main init1
main init2
我们开发的时候经常会使用gorm查询数据库,所以我们经常会看到在初始化db连接的时候会有下面的代码:
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
这种情况就是说,我们没有强依赖mysql包(没有直接使用mysql的公共函数)。但是我们需要初始化mysql包里的一些数据,也就是执行mysql包里的init函数。这个时候我们就可以在这个包的前面加上_
。
需要注意的是init函数不能被当作普通函数调用,会编译报错。
首先,让我们看一个使用 init 函数可能被认为不合适的示例:持有数据库连接池。 在示例的 init 函数中,我们使用 sql.Open 打开一个数据库。 这个全局变量db会被其他函数调用:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"log"
)
var db *sql.DB
func init() {
d, err := sql.Open("mysql",
"root:liufutian@tcp(127.0.0.1:3306)/test")
if err != nil {
log.Panic(err)
}
err := db.Ping()
if err != nil {
log.Panic(err)
}
db = d
}
func main() {
}
在本例中,我们打开数据库,检查是否可以 ping 通,然后将其分配给全局变量。
但是这种实现会带来一些问题:
在大多数情况下,我们应该倾向于封装一个变量而不是让它保持全局。
由于这些原因,之前的初始化可能应该封装到一个函数中处理,如下所示:
func createDB() (*sql.DB, error) {
d, err := sql.Open("mysql",
"root:liufutian@tcp(127.0.0.1:3306)/test")
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
return nil, err
}
return d, nil
}
这样写的话,我们解决了之前讨论的主要缺点:
但是这样就是不能使用init函数了么?在我们上面的引入mysql驱动的例子中,说明使用init还是有帮助的:
func init() {
sql.Register("mysql", &MySQLDriver{})
}
上面的例子,通过注册提供的驱动名称使数据库驱动程序可用。