11-Go语言基础之包

Go包的作用

  • 区分同名的函数、变量等标识符
  • 管理工程化项目
  • 控制函数、变量等标识符的访问作用域
  • 控制Go变量标识符的对外可见(public)/不可见(private)

Go包学习目标

  • 包的定义、使用
  • init初始化函数
  • go module依赖管理工具

在开发复杂Go项目里,需要用包(package)去管理Go源码的复用。

以及导出包的变量、如何引入其他包的变量。

Go包是什么

  • 学习到包管理,就表示以后的开发,不会再是单个go文件这么简单,而是要在不同的*.go文件里调用其他函数,开发复杂的Go工程化项目。

  • Go语言的源码复用以包package为单位,包是go语言工程化的最小粒度。

  • go的每一个文件都必须属于一个包,都必须定义package xxx

  • 一个包是由1个、多个*.go源码文件组成
  • 如下是内置的包,如os,fmt等,便于我们直接import导入,复用其源码。

image-20230111160049254

  • 包的名字不要和内置包名重复

  • 特殊的包名`package main

  • ```go 当你需要编译一个可执行文件,需要2个条件

package main

import "fmt"

// 当你需要编译可执行文件,作为程序入口,就得满足2个条件 func main() { fmt.Println("www.yuchaoit.cn 于超老师带你学golang") }

// 编译 go build -o hello_golang m1.go

//执行 ./hello_golang www.yuchaoit.cn 于超老师带你学golang


![image-20230111163534080](pic/image-20230111163534080.png)



## 定义包

这会咱们就创建一个文件夹(包),且写好go源码文件,该包下所有的`*.go`都要在第一行写上

package pkgname //表示该*.go文件属于该包

package 关键字 pkgname 包名,一般是和文件夹名一致,不得有"-"符号,最好见名知意




## 标识符可见性

- 同一个包内声明的变量都在同一个命名空间下,不同的包下声明的标识符都属于各自的命名空间。

- 想调用外部包的标识符,就得添加该包的前缀如`fmt.Println("www.yuchaoit.cn")`,表示调用fmt包下的`Println公开函数`。

- 想让一个包内的标识符如果想让一个包中的标识符(如变量、常量、类型、函数等)能被外部的包使用,那么标识符必须是对外可见的(public)。

  - 在Go语言中是通过标识符的首字母大/小写来控制标识符的对外可见(public)/不可见(private)的。
  - 在一个包内部只有首字母大写的标识符才是对外可见的。
  - 尽可能减少没必要的变量暴露

- 注意点

  - 函数内部变量,属于局部作用域,只能在函数内被调用
  - 公开包内标识符的规则,适用于结构体

  ```go
  type Student struct {
      Name  string // 可在包外访问的方法
      class string // 仅限包内访问的字段
  }

自定义go包与可见性

image-20230111171957026

结构体字段的可见性

package main

import (
    "encoding/json"
    "fmt"
)

// 结构体json序列化
type stuInfo struct {
    Nname string `json:"name"`
    Age   int    `json:"age"` //结构体字段如果小写,无法被json包访问
}

func main() {

    //实例化结构体
    s1 := stuInfo{"小黑", 18}
    fmt.Printf("值:%v、类型:%T\n", s1, s1)
    //序列化 > json字符串
    data, err := json.Marshal(s1) //s1变量直接传递给json包了
    if err != nil {
        fmt.Printf("Json marshal error:%v\n", err)
        return
    }
    fmt.Printf("%s\n", data)
}

import包导入

在包内引入另一个包使用import关键字,并且通常写在代码的开头,package的下方。

import pkgname  "path/to/pkg"

pkgname 是给导入的包设置名字,一般不写

"path/to/pkg"  必须双引号,是导入包的路径

Go语言禁止重复导入包

导入语法,两种

import "fmt"
import "encoding/json"

import (
    "encoding/json"
    "fmt"
)

也支持给导入的包设置个新包名,然后用新包名去调用,不推荐使用!看懂即可。

import (
    myjson "encoding/json"
    "fmt"
)

新包名调用
data, err := myjson.Marshal(s1) //s1变量直接传递给json包了,不同于import导入的用法

也支持导入包之后,设置特殊包名 _,叫做匿名导包。

该功能是为了加载该包的init函数,而获取一定的初始化资源。

import (
    myjson "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

将fmt启用别名".",这样就可以直接使用其内容,而不用再添加fmt.Println()

import . "fmt"          //不推荐使用

init初始化函数

上面说的匿名导包,其实是为了加载该包中的初始化函数。

在每一个Go源码里,你都可以自定义一个特殊的函数

package main

import "fmt"

func f1() {
    fmt.Println("普通函数加载了。。")
}
func init() {
    fmt.Println("init函数一般完成初始化工作,如数据库驱动连接等")
}

func main() {
    f1()
    fmt.Println("我是主程序")
}

该函数很特殊,无任何参数、无返回值、也不能调用。

init函数加载顺序

程序启动后,init函数按照声明它们的顺序自动执行。

当程序里导入多个包时,按顺序进行每一个包的初始化函数执行,且只会执行一次。

看如下代码即可彻底理解。

image-20230111191430563

package main

import (
    "fmt"
    _ "goStudy/a"
    _ "goStudy/b"
    _ "goStudy/c"
)

var mainVar = "www.yuchaoit.cn"

func init() {
    fmt.Println("全局变量mainVar值:", mainVar)
    fmt.Println("main 包init 已初始化")
    hello()
}

func hello() {
    fmt.Println("普通函数hello执行了。。")
}

func main() {
    fmt.Println("main函数已执行。。")

}

从上面这个案例,可以完全看出Go语言中包的定义、初始化的顺序。

以及如何导入外部包,只不过咱们目前还都支持导入本地的Go包,后续开发还会导入github公开发布的包,用的人越多,也就越能发现问题,优化开源项目。

Go module

  • Go语言早期的包管理工具都是基于GOPATH,下载的如github的第三方包也都放在GOPATH目录下。

  • 该方式的致命问题是只能管理xx依赖包的1个版本。

  • 而我们实际开发的项目可能会同时依赖一个包的多个版本。

  • 因此在Go1.14版本之后,Go module正式用于生产环境,Go1.16版本默认开启go module功能。

➜  ~  go env|grep -i modu
GO111MODULE=""

该变量是早期go使用的环境变量控制go module的开关,在Go 1.16之后无须关心了,默认开启。

➜  ~ go version
go version go1.19.4 darwin/amd64


/*
download    download modules to local cache (下载依赖的module到本地cache))
edit        edit go.mod from tools or scripts (编辑go.mod文件)
graph       print module requirement graph (打印模块依赖图))
init        initialize new module in current directory (再当前文件夹下初始化一个新的module, 创建go.mod文件))
tidy        add missing and remove unused modules (增加丢失的module,去掉未用的module)
vendor      make vendored copy of dependencies (将依赖复制到vendor下)
verify      verify dependencies have expected content (校验依赖)
why         explain why packages or modules are needed (解释为什么需要依赖)*/
命令 作用
go mod init 项目名 初始化项目依赖,生成go.mod文件
go mod download 根据go.mod文件下载依赖,指第三方包
go mod tidy 比对项目文件中引入的依赖与go.mod进行比对,统一源码和go.mod的依赖关系一致性
go mod graph 输出依赖关系图
go mod edit 编辑go.mod文件
go mod vendor 将项目的所有依赖导出至vendor目录
go mod verify 检验一个依赖包是否被篡改过
go mod why 解释为什么需要某个依赖

Go proxy

当你下载第三方依赖包时,可以修改该变量,便于加速下载第三方包,或者配置公司私有的仓库地址。

常见公开的go proxy设置

go env -w GOPROXY=https://goproxy.cn,direct

➜  goStudy go env|grep -i proxy
GONOPROXY=""
GOPROXY="https://goproxy.cn,direct"

可以通过英文逗号分隔多个地址,Go会逐个的去尝试代理地址。
direct作用是例如https://goproxy.cn找不到xx包时,Go自动去回源地址访问数据。

Go private

当你配置了go proxy时,go会自动从代理地址去拉取、校验依赖包。

而Go项目了引入了公司内部的包,如一些大型企业会有私有仓库,如gitlab仓库,就无法下载依赖包。

还需要设置go private环境变量,来设定哪些url是私有仓库,不走go proxy了。

go private也支持设置多个地址逗号分隔,用法如

$ go env -w GOPRIVATE="git.yuchaoit.cn"

go module实践

1.初始化新项目,选择一个新目录即可
➜  learn_go_module pwd
/Users/yuchao/goStudy/learn_go_module


2. 初始化项目
➜  learn_go_module go mod init mywebsite
go: creating new go.mod: module mywebsite

生成的go.mod内容
module mywebsite  //定义项目的导入路径

go 1.19 // 当前项目用的什么go编译器

该文件记录你的go项目用的所有的第三方依赖包,包名,版本号。


3.新建一个main.go入口文件,引入三方包试试,第三方包被go module管理

gin第三方包测试

方法1,go get

package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Println("点滴进步,水滴石穿。")
    // 方法1:先去下载 go get -u github.com/gin-gonic/gin
    // 导入该包
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

方法2,用go.mod下载,了解即可

1.直接修改go.mod,写入你想要的依赖信息
module mywebsite

go 1.19

require "https://github.com/gin-gonic/gin" latest  //也可以指定版本


2.go mod download命令执行,根据go.mod的依赖关系自动取下载

最终正确的代码

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    fmt.Println("点滴进步,水滴石穿。")
    // 方法1:先去下载 go get -u github.com/gin-gonic/gin@v1.7.7
    // 导入该包
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

记录了第三方包的go.mod

module mywebsite

go 1.19

require (
    github.com/gin-contrib/sse v0.1.0 // indirect
    github.com/gin-gonic/gin v1.7.7 // indirect
    github.com/go-playground/locales v0.14.1 // indirect
    github.com/go-playground/universal-translator v0.18.0 // indirect
    github.com/go-playground/validator/v10 v10.11.1 // indirect
    github.com/golang/protobuf v1.5.2 // indirect
    github.com/json-iterator/go v1.1.12 // indirect
    github.com/leodido/go-urn v1.2.1 // indirect
    github.com/mattn/go-isatty v0.0.17 // indirect
    github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
    github.com/modern-go/reflect2 v1.0.2 // indirect
    github.com/ugorji/go/codec v1.2.8 // indirect
    golang.org/x/crypto v0.5.0 // indirect
    golang.org/x/sys v0.4.0 // indirect
    golang.org/x/text v0.6.0 // indirect
    google.golang.org/protobuf v1.28.1 // indirect
    gopkg.in/yaml.v2 v2.4.0 // indirect
)

行尾的indirect表示该依赖包为间接依赖,说明在当前程序中的所有 import 语句中没有发现引入这个包。

试试运行gin

➜  learn_go_module go build

➜  learn_go_module 
➜  learn_go_module ll
total 29552
-rw-r--r--  1 yuchao  staff   880B Jan 11 20:26 go.mod
-rw-r--r--  1 yuchao  staff   9.0K Jan 11 20:26 go.sum
-rw-r--r--  1 yuchao  staff   419B Jan 11 20:26 main.go
-rwxr-xr-x  1 yuchao  staff    14M Jan 11 20:31 mywebsite

➜  learn_go_module ./mywebsite 
点滴进步,水滴石穿。
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080


访问试试
➜  goStudy curl http://0.0.0.0:8080/ping
{"message":"pong"}

删除第三方包

1. 删除go.mod里记录的信息
2. 物理删除下载到本地的包
➜  gin-gonic pwd
/Users/yuchao/goCode/pkg/mod/github.com/gin-gonic

本地go包管理

代码测试

➜  learn_go_module tree
.
├── go.mod
├── go.sum
├── halo
│   └── say.go
├── main.go
└── mywebsite

1 directory, 5 files
➜  learn_go_module cat halo/say.go 
package halo

import "fmt"

func Say() {
        fmt.Println("我说超哥,你说6,超哥!")
}
➜  learn_go_module

main.go

// 最新代码
package main

import (
    "fmt"                      //内置包
    "github.com/gin-gonic/gin" //导入github第三方的包,名字为啥叫这么?因为该仓库的go.mod 声明的名字就是 module github.com/gin-gonic/gin
    "mywebsite/halo"           //导入当前项目名称空间下的halo包
    "net/http"                 //内置包
)

func main() {
    fmt.Println("点滴进步,水滴石穿。")
    halo.Say() //调用本地包

    // 方法1:先去下载 go get -u github.com/gin-gonic/gin@v1.7.7
    // 导入该包
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

再次编译



➜  learn_go_module go build
➜  learn_go_module ./mywebsite 
点滴进步,水滴石穿。
我说超哥,你说6,超哥!
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

go.sum

该文件详细记录了引入的第三方依赖以及hash值

github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

语法
<module> <version>/go.mod <hash>
这是因为go不同于pip,npm那种的中央仓库管理依赖包,为了防止依赖包被篡改,因此引入go.sum对依赖包哈希值进行校验。

Go语言反射

新人不用看,以后再学。

变量内部机制

Go语言的变量有两部分信息

  • 类型:提前声明好的元信息
  • 值:程序在运行后动态可变化的数据。

反射是什么

  • 程序在运行时,可以通过反射动态获取变量的信息,如类型,如值,以及修改的能力,这就是反射的功能。
  • 程序在编译时,变量被转换为内存地址,变量名是不会被编译器写入可执行程序的,因此程序运行时无法获取自身的信息。
  • 支持反射功能的编程语言,可以让程序在编译期间将变量的反射信息(如结构体的字段名,类型等)整合到可执行文件里,并且提供了访问入口,这样程序员就可以在程序运行时,动态获取变量信息,以及修改。
  • 思考一个问题,之前我们学的空接口,可以存储任意其他类型变量,这个空接口到底保存了个啥?
  • 反射就是在运行时动态的获取一个变量的类型信息和值信息。

在Go语言的反射机制中,任何接口值都由是一个具体类型具体类型的值两部分组成的(我们在上一篇接口的博客中有介绍相关概念)。

在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Typereflect.Value两部分组成

并且reflect包提供了reflect.TypeOfreflect.ValueOf两个函数来获取任意对象的Value和Type。

TypeOf()函数

package main

import (
    "fmt"
    "reflect"
)

func reflectType(i interface{}) {
    // 在Go语言中,使用reflect.TypeOf()函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。
    //有同学会问,超哥 不是fmt包的%T格式化打印,就可以获取变量类型吗?
    //你有没有想过,%T这玩意,它内部代码实现,就是通过reflect.TypeOf获取的。。。理解了吧
    res := reflect.TypeOf(i) //获取变量类型
    fmt.Printf("res类型:%v\n", res)
}

type Animal struct {
    Name string
    Age  int
}

func main() {

    name := "老六"
    reflectType(name)

    mao := Animal{"猫猫", 2}
    reflectType(mao)
}

Type name、Type kind

Go语言支持用type关键字去构造自定义类型

type myInt int64

如上int64其实底层的类型,但是怎么查出来呢?

当我们要区分自定义类型、区别指针、结构体等类型时,就分别会用
Type类型
Kind种类

反射包里如下用法

实践

package main

import (
    "fmt"
    "reflect"
)

type myInt int64

func reflectType(i interface{}) {
    // 在Go语言中,使用reflect.TypeOf()函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。
    //有同学会问,超哥 不是fmt包的%T格式化打印,就可以获取变量类型吗?
    //你有没有想过,%T这玩意,它内部代码实现,就是通过reflect.TypeOf获取的。。。理解了吧
    res := reflect.TypeOf(i) //获取变量类型
    fmt.Printf("反射获取type类型:%v  \t 底层kind种类:%v\n", res.Name(), res.Kind())
}

func main() {

    //使用自定义类型
    var score myInt

    score = 66
    fmt.Printf("普通格式化输出类型:%T、值:%v\n", score, score)

    var p1 *int
    fmt.Printf("普通格式化输出类型:%T、值:%v\n", p1, p1)

    //2.使用反射查询出自定义类型,底层的类型,调用Kind方法
    reflectType(score)

    //3.查看更多类型
    var p *string //指针
    var m myInt   //自定义类型
    var r rune    //类型别名
    reflectType(p)
    reflectType(m)
    reflectType(r)

    //4.结构体
    type Student struct {
        Name string
    }

    jack := Student{"jack"}
    reflectType(jack)
}

/*
➜  learn_go_module go run main.go
普通格式化输出类型:main.myInt、值:66
普通格式化输出类型:*int、值:<nil>
反射获取type类型:myInt          底层kind种类:int64
反射获取type类型:       底层kind种类:ptr
反射获取type类型:myInt          底层kind种类:int64
反射获取type类型:int32          底层kind种类:int32
反射获取type类型:Student        底层kind种类:struct

Go语言里数组,切片,map,指针的变量,通过反射.Name()获取的是空。

*/

fmt包的格式化输出原理

Go语言标准库里有大量的使用反射,如fmt包的Printf都会利用反射解析参数。

我们直接使用反射包练习。

reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。

reflect.Value与原始值之间可以互相转换。image-20230112144926228

参考标准库简单理解

反射获取变量

package main

import (
    "fmt"
    "reflect"
)

func reflectType(i interface{}) {
    res := reflect.ValueOf(i)
    //获取该变量的底层类型
    v := res.Kind()
    //多条件判断
    switch v {
    case reflect.Int64:
        //通过res.Int()可以反射获取,该变量的原始值,然后通过对应类型,强制转换
        fmt.Printf("类型:int64、值:%d\n", int64(res.Int()))
    case reflect.Float64:
        fmt.Printf("类型:float64、值:%f\n", float64(res.Float()))
    case reflect.Float32:
        fmt.Printf("类型:float32、值:%f\n", float32(res.Float()))
    case reflect.String:
        fmt.Printf("类型:string、值:%s\n", string(res.String()))

    default:
        fmt.Println("暂未支持该类型!!")
    }
}

func main() {
    var a float32 = 3.1415
    reflectType(a)

    var b int64 = 666
    reflectType(b)

    var c string = "老王八"
    reflectType(c)

}
Copyright © www.yuchaoit.cn 2025 all right reserved,powered by Gitbook作者:于超 2023-01-12 14:52:20

results matching ""

    No results matching ""