目录
  • 包 package
  • main.main 函数:Go 应用的入口函数
    • package main
    • 注意
    • 其他包也可以拥有 main 函数或方法
  • 重点
    • 引子
  • init 函数:Go 包的初始化函数
    • 和 main 函数不一样
    • init 函数的执行顺序
  • Go 包的初始化次序
    • init 函数的特点
      • init 函数的用途
        • 重置包级变量值
        • 实现对包级变量的复杂初始化
      • 在 init 函数中实现“注册模式”
        • 实际原因
      • 通过注册模式实现获取各种格式图片的宽、高
        • Go 源码

      包 package

      • Go 包是 Go 语言的基本组成单元,一个 Go 程序就是一组包的集合,所有 Go 代码都位于包中
      • Go 源码可以导入其他 Go 包,并使用其中的导出语法元素,包括类型、变量、函数、方法等,而且 main 函数是整个 Go 应用的入口函数
      • Go 语言提供了很多内置包,如 fmt、os、io 等
      • 任何源代码文件必须属于某个包,同时源码文件的第一行有效代码必须是 package 包名语句,通过该语句声明源码文件所在的包

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

      • Go 语言中有一个特殊的函数:main 包中的 main 函数,也就是 main.main,它是所有 Go 可执行程序的用户层执行逻辑的入口函数
      • Go 程序在用户层面的执行逻辑,会在这个函数内按照它的调用顺序展开

      package main

      • 整个 Go 可执行程序中仅允许存在一个名为 main 的包
      • package main想要引用别的包的代码,必须同样以包的方式进行引用
      package main
      
      func main() {    
          // 用户层执行逻辑
          ... ...
      }

      Go 语言要求:可执行程序的 main 包必须定义 main 函数,否则 Go 编译器会报错

      注意

      main 包是不可以像标准库 fmt 包那样被导入(Import)的

      其他包也可以拥有 main 函数或方法

      按照 Go 的可见性规则(小写字母卡头的标识符为非导出标识符),非 main包中自定义的 main 函数仅限于包内使用

      package pkg1
      
      import "fmt"
      
      func Main () {
          main()
      }
      
      func main(){
          fmt.Println("main func for pkg1")
      }

      重点

      • 一个文件夹下的所有源码文件只能属于同一个包,不要求同名,但还是建议包名和所在目录同名,这样结构更清晰,包名中不能包含特殊符号
      • 给结构定义的方法必须放在同一个包内,可以是不同文件
      • 包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件。
      • 一个文件夹下的所有源码文件只能属于同一个包,属于同一个包的源码文件不能放在多个文件夹下

      引子

      不过对于 main 包的main 函数来说,还需要明确一点,就是它虽然是用户层逻辑的入口函数,但它却不一定是用户层第一个被执行的函数。这是为什么呢?这跟 Go 语言的另一个函数 init 有关

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

      和 main.main 函数一样,init 函数也是一个无参数无返回值的函数

      func init() {
      	// 包初始化逻辑
      	... ...
      }
      • Go 程序会在这个包初始化的时候,自动调用它的 init 函数,所以 init 函数的执行会发生在 main 函数之前
      • 在 Go 程序中不能手工显式地调用 init,否则会收到编译错误

      和 main 函数不一样

      • init 函数在一个包中可以有多个,每个 Go 源文件都可以定义多个 init 函数

      init 函数的执行顺序

      • 在初始化 Go 包时,Go 会按照一定的顺序,逐一、顺序地调用这个包的 init 函数
      • 一般来说,先传递给 Go 编译器的源文件中的 init 函数,会先被执行;而同一个源文件中的多个 init 函数,会按声明顺序依次执行

      Go 包的初始化次序

      • 从程序逻辑结构角度来看,Go 包是程序逻辑封装的基本单元
      • 每个包都可以理解为是一个“自治”的、封装良好的、对外部暴露有限接口的基本单元
      • 一个 Go 程序就是由一组包组成的,程序的初始化就是这些包的初始化
      • 每个 Go 包还会有自己的依赖包、常量、变量、init 函数(其中 main 包有 main 函数)等

      Go 的入口函数和包初始化的使用

      三步走

      • 依赖包按“深度优先”的次序进行初始化
      • 每个包内按以“常量 -> 变量 -> init 函数”的顺序进行初始化
      • 包内的多个 init 函数按出现次序进行自动调用

      init 函数的特点

      • 如上图所示,执行顺位排在包内其他语法元素(常量、变量)的后面
      • 每个 init 函数在整个 Go 程序生命周期内仅会被执行一次
      • init 函数是顺序执行的,只有当一个 init 函数执行完毕后,才会去执行下一个 init 函数

      init 函数的用途

      重置包级变量值

      init 函数就好比 Go 包真正投入使用之前唯一的“质检员”,负责对包内部以及暴露到外部的包级数据(主要是包级变量)的初始状态进行检查

      实现对包级变量的复杂初始化

      有些包级变量需要一个比较复杂的初始化过程,有些时候,使用它的类型零值或通过简单初始化表达式不能满足业务逻辑要求,而 init 函数则非常适合完成此项工作,标准库 http 包中就有这样一个典型示例

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

      http 包在init 函数中,就根据环境变量 GODEBUG 的值,对这些包级开关变量进行了复杂的初始化,从而保证了这些开关变量在 http 包完成初始化后,可以处于合理状态

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

      来看一段使用 lib/pq 包访问 PostgreSQL 数据库的代码

      声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。