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

目录
  • 包 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 函数)等

三步走

  • 依赖包按“深度优先”的次序进行初始化
  • 每个包内按以“常量 -> 变量 -> 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 数据库的代码

package main

import (
	"database/sql"
	"log"

	_ "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 包的 conn.go 源码文件中的 init 函数

func init() {
	sql.Register("postgres", &Driver{})
}
  • 利用了空导入的特性,将 lib/pq 包作为 main 包的依赖包,在包初始化时,会先执行 lib/pq 包里面的 init 函数
  • pq 包的 init 函数将自己实现的 sql 驱动注册到了 sql 包中
  • 这样在实际应用代码中,Open 数据库时,传入驱动名字(这里是 postgres),就能得到数据库实例,然后对数据库进行操作,实际上是因为调用了 pq 包中相应的驱动实现的

好处:这种通过在 init 函数中注册自己的实现的模式,就有效降低了 Go 包对外的直接暴露,尤其是包级变量的暴露,从而避免了外部通过包级变量对包状态的改动

工厂设计模式

从标准库 database/sql 包的角度来看,这种“注册模式”实质是一种工厂设计模式的实现,sql.Open 函数就是这个模式中的工厂方法,它根据外部传入的驱动名称“生产”出不同类别的数据库实例句柄

通过注册模式实现获取各种格式图片的宽、高

Go 源码

package main

import (
	"fmt"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"os"
)

func main() {
	// 支持 png、jpeg、gif
	width, height, err := imageSize(os.Args[1])
	if err != nil {
		fmt.Println("get image size error:", err)
		return
	}
	fmt.Printf("image size: [%d,%d]\n", width, height)
}

func imageSize(imageFile string) (int, int, error) {
    // 打开图片文件
	f, _ := os.Open(imageFile)
	defer f.Close()

    // 对文件进行解码,得到图片实例
	img, _, err := image.Decode(f)
	if err != nil {
		return 0, 0, err
	}

    // 返回图片区域
	b := img.Bounds()
	return b.Max.X, b.Max.Y, nil
}
  • 上面的源码支持 png、jpeg、gif 三种格式的图片
  • 但并不需要手动支持图片格式
  • 是因为 image/png、image/jpeg 和 image/gif 包都在各自的 init 函数中,将自己“注册”到 image 的支持格式列表中了
// $GOROOT/src/image/png/reader.go
func init() {
	image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}

// $GOROOT/src/image/jpeg/reader.go
func init() {
	image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
}

// $GOROOT/src/image/gif/reader.go
func init() {
	image.RegisterFormat("gif", "GIF8?a", Decode, DecodeConfig)
}

到此这篇关于Go 的入口函数和包初始化的使用的文章就介绍到这了,更多相关Go 入口函数和包初始化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go基础教程系列之import导入包(远程包)和变量初始化详解

    import导入包 搜索路径 import用于导入包: import ( "fmt" "net/http" "mypkg" ) 编译器会根据上面指定的相对路径去搜索包然后导入,这个相对路径是从GOROOT或GOPATH(workspace)下的src下开始搜索的. 假如go的安装目录为/usr/local/go,也就是说GOROOT=/usr/local/go,而GOPATH环境变量GOPATH=~/mycode:~/mylib,那么要搜索net

  • go语言的初始化顺序,包,变量,init详解

    依次见例子代码: p1.go package p1 import "fmt" //1.1 var x float32 = 1.2 //1.2 func init() { //1.3 fmt.Printf("p1 package, x:%f\n", x) //1.4 } func Donothing() { fmt.Println("do nothing.\n") } a.go: package main import "fmt"

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

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

  • Kotlin教程之函数和包的定义和流程控制

    Kotlin教程之函数和包的定义和流程控制 包名的定义 在xxx.kt文件的顶部进行定义,使用package关键字,定义后,引用包内函数或类,使用包前缀: package cn.jasonmarzw //包定义 fun doSomething(){} // 定义的函数 class User(){} //定义的类 在其他文件中使用时: 可以直接使用 cn.jasonmarzw.doSomething()和 cn.jasonmarzw.User进行调用.可以像Java一样,使用import引入所需要

  • 细说webpack源码之compile流程-入口函数run

    Webpack是目前基于React和Redux开发的应用的主要打包工具.我想使用Angular 2或其他框架开发的应用也有很多在使用Webpack. 本节流程如图: 现在正式进入打包流程,起步方法为run: Compiler.prototype.run = (callback) => { const startTime = Date.now(); const onCompiled = (err, compilation) => { /**/ }; this.applyPluginsAsync(

  • Go语言中的流程控制结构和函数详解

    这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和无条件跳转. if if也许是各种编程语言中最常见的了,它的语法概括起来就是:如果满足条件就做某事,否则做另一件事. Go里面if条件判断语句中不需要括号,如下代码所示: 复制代码 代码如下: if x > 10 {     fmt.Println("x is greater than 10&

  • python实现贪吃蛇小游戏

    关于编写游戏,是博主非常向往的东西(博主喜爱游戏),编写游戏得一步一步的走!今天我简单的编写一下非常经典的游戏贪吃蛇!!!! 效果图: 首先引入pygame模块 pip install pygame 关于编写贪吃蛇有如下几个步骤!依次思考 1.设置背景大小,即游戏框大小,---像素(px) 2.设置颜色,蛇的颜色,背景颜色,豆子的颜色 #pygame游戏库,sys操控python运行的环境 import pygame,sys,random #这个模块包含所有pygame所使用的常亮 from p

  • 关于数据处理包dplyr的函数用法总结

    dplyr专注处理dataframe对象, 并提供更稳健的与其它数据库对象间的接口. 一.5个关键的数据处理函数: select() 返回列的子集 filter() 返回行的子集 arrange() 根据一个或多个变量对行排序. mutate() 使用已有数据创建新的列 summarise() 对各个群组汇总计算并返回一维结果. Tips: 1.select() Dplyr包有下列辅助函数,用于在select()中选择变量: starts_with("X"): 以 "X&qu

  • JavaScript学习笔记(三):JavaScript也有入口Main函数

    在C和Java中,都有一个程序的入口函数或方法,即main函数或main方法.而在JavaScript中,程序是从JS源文件的头部开始运行的.但是某种意义上,我们仍然可以虚构出一个main函数来作为程序的起点,这样一来不仅可以跟其他语言统一了,而且说不定你会对JS有更深的理解. 1. 实际的入口 当把一个JavaScript文件交给JS引擎执行时,JS引擎就是从上到下逐条执行每条语句的,直到执行完所有代码. 2. 作用域链.全局作用域和全局对象 我们知道,JS中的每个函数在执行时都会产生一个新的

  • C++改变编程入口为main函数

    1, 你用vc建了一个控制台程序,它的入口函数应该是main, 而你使用了WinMain. 2.  你用vc打开了一个.c/.cpp 文件,然后直接编译这个文件,这个文件中使用了WinMian而不是main作为入口函数.vc这时的默认设置是针对控制台程序的.  解决方法 1.进入project->setting->c/c++, 在category中选择preprocessor,在processor definitions中删除_WINDOWS, 添加_CONSOLE 2.进入project-&

  • Go语言的变量、函数、Socks5代理服务器示例详解

    Go语言中变量的声明和JavaScript很像,使用var关键字,变量的声明.定义有好几种形式 1. 变量和常量 // 声明并初始化一个变量 var m int = 10 // 声明初始化多个变量 var i, j, k = 1, 2, 3 // 多个变量的声明(注意小括号的使用) var( no int name string ) // 声明时不指明类型,通过初始化值来推导 var b = true // bool型 // := 隐含声明变量并赋值 str := "mimvp.com"

  • 深入了解Go语言的基本语法与常用函数

    目录 一.基本语法 标识符命名规范 变量的定义与使用 定义常量 二.常用函数 main 函数与 init 函数 fmt 包及其函数 一.基本语法 标识符命名规范 Go 是区分大小写的,标识符的命名包含了 Go 中变量.常量.函数.结构体.接口以及方法的命令,Go 限定任何需要对外暴露的标识符必须以大写字母开头,不需要对外暴露的标识符则需要以小写字母开头. 标识符以大写开头,就表示可以被外部包的代码引用,称之为导出.如果以小写字母开头,那么对外就是不可见的,但是在整个包的内部是可见且可用的. 标识

随机推荐