【Go 快速入门】包及依赖管理 | Go 第三方包发布 | 接口 | 反射

news/2024/5/19 5:19:41 标签: golang, 反射, 包及依赖管理, 接口, 第三方库

文章目录

    • 包和依赖管理
      • 依赖管理
        • go mod
        • go get
      • go.mod 文件
      • go.sum 文件
      • Go Modules 发布包
    • 接口
    • 反射
      • reflect.TypeOf
      • reflect.ValueOf
      • 结构体反射

项目代码地址:04-PackageInterfaceReflection

包和依赖管理

Go 使用包来支持代码模块化和代码复用,一个包由多个 .go 文件组成。

基本格式:package packagename,其中 packagename 表示包名

  • 一个文件夹直接包含的文件只能属于一个包
  • 包名为 main 的包是程序入口包,仅该包编译后会得到一个可执行文件
  • 首字母大/小写控制标识符是否对外可见/不可见,大写则对包外可见
  • 引入的包都会被编译到可执行文件中

包的引入

  • 禁止循环导入包
  • 导入的包必须都有使用
  • 三种常见的引入包模式,._别名

每个包在初始化时都会先执行依赖包中声明的 init 函数,再执行当前包中声明的 init 函数。

import (
	_ "00-LocalPackage"                       // 匿名
	Calc "04-PackageInterfaceReflection/calc" // 别名
	. "fmt"                                   // 不带前缀,不建议使用,容易重名
)

func function01() {
	Printf("%s\n", "不带前缀")
	Println(Calc.Add(1, 2))
}

date 包初始化ing~
不带前缀
3

  1. 匿名导入包会先执行其 init 函数
  2. 使用 . 导入包,则不需要使用该包名作为前缀调用,PrintfPrintln
  3. 使用别名 Calc 导入包,调用该包下的函数需要使用别名

上述示例中,00-LocalPackage 包是本地没被发布到其他任何代码仓库的本地包,04-PackageInterfaceReflection/calc 则是当前项目下的包,结构如下:

- 04-PackageInterfaceReflection
	|- calc
		|- add.go
	|- go.mod
	|- main.go
- 00-LocalPackage
	|- go.mod
	|- date.go

当前项目内的其他包,使用很简单,如上述直接 import 项目名/包名 导入即可。

本地包导入如下,可以在 go.mod 文件中使用 replace 语句将依赖临时替换为本地包的相对路径:

04-PackageInterfaceReflection/go.mod

require 00-LocalPackage v0.0.0
replace 00-LocalPackage => ../00-LocalPackage

依赖管理

Go 1.16 版本默认开启 Go Modules 管理依赖

GOPROXY

设置 Go 模块代理,使 Go 在后续拉取模块版本时脱离传统 vcs 方式,直接通过镜像站点快速拉取。

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

GOPRIVATE

设置了 GOPROXY 后,go 命令会从配置的代理地址拉取和校验依赖包。当项目中引入非公开的包(私有仓库或公司内部 git 仓库),就无法正常从代理拉取,需要配置 GOPRIVATE。

GOPRIVATE 用来告诉 go 命令哪些仓库是私有的,不必通过代理服务器拉取和校验。

go env -w GOPRIVATE=gitee.com

这样就可以正常拉取 gitee.com 为前缀的依赖包了


go mod

常用的 go mod 命令:

指令介绍
go mod download下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
go mod edit编辑go.mod文件
go mod graph打印模块依赖图
go mod init初始化当前文件夹, 创建go.mod文件
go mod tidy增加缺少的module,删除无用的module
go mod vendor将依赖复制到vendor下
go mod verify校验依赖
go mod why解释为什么需要依赖

我们在代码中删除依赖代码后,相关的依赖库并不会在 go.mod 文件中自动移除。这种情况下我们可以使用 go mod tidy 命令更新 go.mod 中的依赖关系。

go get

在项目中执行 go get 命令可以下载依赖包,并且还可以指定下载的版本。

  • 运行 go get -u 将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号)
  • 运行 go get -u=patch 将会升级到最新的修订版本
  • 运行 go get package@version 将会升级到指定的版本号 version

go.mod 文件

go.mod 文件记录了当前项目中所有依赖包的相关信息

声明依赖的基本格式:require module/path v1.2.3

  • require: 声明依赖关键字

  • module/path: 依赖包的引入路径

  • v1.2.3: 依赖包的版本号

    • latest: 最新版本

    • v1.2.3: 详细版本号

      • 主版本号:发布了不兼容的版本迭代时递增
      • 次版本号:发布了功能性更新时递增
      • 修订号:发布了 bug 修复类更新时递增
    • commit hash: 指定某次 commit hash

go.sum 文件

在 Go Modules 下载依赖后生成,记录依赖包及其 hash 值。Go 采用分布式方式管理包,为了防止依赖包被非法篡改,Go Modules 引入了 go.sum 机制对依赖包进行校验。

基本格式:

<module> <version> <hash>
<module> <version>/go.mod <hash>

Go Modules 发布包

  1. gitee.com 创建仓库,并下载到本地

    • git clone git@gitee.com:Cauchy_AQ/hello.git
  2. hello 项目目录下初始化,设置项目路径

    • go mod init gitee.com/Cauchy_AQ/hello
  3. 创建 hello.go 文件,提供方法 SayHello()

    •   package hello
        
        import "fmt"
        
        func SayHello() {
        	fmt.Println("你好,我是Cauchy!")
        }
      
  4. 项目代码 push 到远端 master 分支

    • git add .
    • git commit -m "SayHello() v0.1.0"
    • git push origin master
  5. 为代码包打上标签

    • git tag -a v0.1.0 -m "release version v0.1.0"
  6. 项目代码 push 到远端标签分支

    • git push origin v0.1.0
  7. 迭代版本 v2.0.0

    •   func SayHello(name string) {
        	fmt.Printf("你好%s,我是Cauchy!", name)
        }
      
  8. go.mod 修改当前包的引入路径,添加后缀 v2

    • module gitee.com/Cauchy_AQ/hello/v2
  9. 发布新的主版本

    • git add .
    • git commit -m "SayHello(string) v2.0.0"
    • git push
    • git tag -a v2.0.0 -m "release version v2.0.0"
    • git push origin v2.0.0
  10. 项目使用自己发布的包

    • 设置 GOPRIVATE

      • go env -v GOPRIVATE=gitee.com
    • 项目导入包

      • go get gitee.com/Cauchy_AQ/hello/v2@v2.0.0
      • go mod tidy

执行完上述操作后,就成功发布包在 gitee 平台,并且作为第三方库,可以随时获取使用。

import (
	hello "gitee.com/Cauchy_AQ/hello"
	helloV2 "gitee.com/Cauchy_AQ/hello/v2"
)

func function02() {
	hello.SayHello() // 你好,我是Cauchy!
	helloV2.SayHello("AQ") // 你好AQ,我是Cauchy!
}

需要注意:

从 v2 版本开始,主要版本必须出现在模块路径末尾(…/v2)。


接口

Go 1.18 版本开始,接口分为 一般接口基础接口,在此讨论基本接口,后续谈及泛型在讨论一般接口

一个接口类型的定义中只包含方法,称为基本接口

基本格式:

type 接口类型名 interface {
	方法名(参数列表) 返回值列表
}

实现接口

如下所示:

  • 类型 Dog 实现了接口 Animal 的 Move、Say 方法,即 Dog 实现了接口 Animal
  • 接口 FlyAnimal 包含了接口 Animal 所有的方法,即 FlyAnimal 实现了接口 Animal

在这里插入图片描述

// Interface
type Animal interface {
	Move()
	Say()
}

// 
type FlyAnimal interface {
	Animal
	Fly()
}

type Dog struct{}

func (d Dog) Move() {
	Println("dog move")
}

func (d Dog) Say() {
	Println("dog say")
}

func function03() {
	var a Animal // 接口类型变量
	var d Dog = Dog{}
	a = d    // 能存储所有实现了该接口的类型变量
	a.Move() // dog move
	a.Say()  // dog say
}

Go 有对指针求值的语法糖:

Dog 是值接收者,赋值给接口类型变量 a 时,无论是值还是指针都可以直接赋值。Cat 是指针接收者,赋值给 a 时,只能赋值地址。

  • 值接收者实现的接口,可用值类型和指针类型
  • 指针接收者实现的接口,只能用指针类型
var d *Dog = &Dog{}
a = d
var c Cat = Cat{}
a = &c
type Dog struct{}

func (d Dog) Move() {
	Println("dog move")
}

func (d Dog) Say() {
	Println("dog say")
}

type Cat struct{}

func (c *Cat) Move() {
	Println("cat move")
}

func (c *Cat) Say() {
	Println("cat say")
}

只要类型实现了接口的所有方法,那么该类型就实现了接口

  • 多种类型实现同一接口
type HomeWork interface {
	DoA()
	DoB()
}

type Week1 struct{}

func (w Week1) DoA() {}

type Week2 struct {
	Week1
}

func (w Week2) DoB() {}
type A interface{
	PlayA()
}
type B interface{
	PlayB()
}

type AB interface {
	A
	B
}

接口

接口指没有定义任何方法的接口类型,因此任何类型都可以视为实现了空接口。所以空接口的类型变量可以存储任意类型的值。

func function04() {
	a := make(map[int]interface{})
	a[1] = [...]int{1, 2, 3}
	a[2] = "Golang"
	a[3] = 123

	for k, v := range a {
		Println(k, v)
	}
	/*
		2 Golang
		3 123
		1 [1 2 3]
	*/
}

Go 中只要是可比较类型都可以作为 key ,除了 slice ,map,function 这几种类型,其他类型都可以作为 map 的 key。因此 interface{}接口类型也不可以作为 key。

Go 中的 any 类型实际就是空接口类型:type any = interface{}

func function05() {
	var a interface{}
	a = []int{1, 2, 3}
	Println(a) // [1 2 3]
	
	var b any
	b = []int{0}
	Println(b) // [0]

	func(a interface{}) {
		switch a.(type) {
		case int:
			Println("int")
		case string:
			Println("string")
		default:
			Println("None")
		}
	}("123") // string
}

接口

接口值由类型(type)和值(value)组成,这两部分根据存入值不同而发生变化,也称之为接口的动态类型和动态值。

// 接口
func function06() {
	var c Animal
	Println(c == nil) // true

	c = new(Cat)
	Println(c == nil) // false
	Printf("%T\n", c) // *main.Cat
}

类型断言

接口值可能被赋为任意类型的值,通过类型断言从接口值获取其存储的具体数据类型。

基本语法:x.(Type),返回两个参数:第一个是转化为 Type 类型的变量;第二个是一个 bool 值,true 表示断言成功。

// 类型断言
func function07() {
	var x interface{}
	x = []int{1, 2, 3}

	switch v := x.(type) {
	case int:
		Println("int", v)
	case string:
		Println("string", v)
	case []int:
		Println("[]int", v) // []int [1 2 3]
	default:
		Println("failed")
	}
}

tips

下述代码可以在程序编译阶段验证某一结构是否满足特定的接口类型

type IRouter interface{...}
type RouterGroup struct {...}
var _ IRouter = (*RouterGroup)(nil) // 确保 RouterGroup 实现了接口 IRouter

反射

上文通过类型断言,获取空接口类型的动态值和类型。那么也可以通过反射,在程序运行时动态的获取一个变量的类型信息和值信息。

Go 语言反射相关功能由内置的 reflect 包提供,任何接口值在反射中都可以理解为由 reflect.Typereflect.Value 两部分组成。分别由 reflect.TypeOfreflect.ValueOf 函数来获取任意值的 TypeValue 信息。

reflect.TypeOf

函数签名:func TypeOf(i interface{}) Type

通过反射得到的类型信息分为 TypeKind 两种,

  • Type 指声明的类型,名称由 reflect.TypeName 方法得到

    • 数组、切片、Map、指针等类型变量的 Type 名称都是空字符串
  • Kind 指语言底层的类型,名称由 reflect.TypeKind 方法得到

    • 当需要区分指针、结构体等大品种类型,就用到 Kind
func reflectType(x interface{}) {
	v := reflect.TypeOf(x)
	Printf("type:%v kind:%v\n", v.Name(), v.Kind())
}

type MyInt int

func function08() {
	var a *float32
	var b MyInt
	var c int64

	reflectType(a) // type: kind:ptr
	reflectType(b) // type:MyInt kind:int
	reflectType(c) // type:int64 kind:int64

	type ABC struct {
		name string
	}
	var d = ABC{"123"}
	reflectType(d) // type:ABC kind:struct

	e := &struct {
		ID int
	}{1010}
	reflectType(e) // type: kind:ptr
}

reflect.ValueOf

函数签名:func ValueOf(i any) Value

reflect.ValueOf 返回 reflect.Value 类型,它是结构体类型,包含了原始值信息,可以与原始值之间互相转换。

func reflectValue(x interface{}) {
	v := reflect.ValueOf(x) // reflect.Value
	k := v.Kind()
	switch k {
	case reflect.Int:
		Println("Int", v.Int())
	case reflect.Int32:
		Println("Int32", int32(v.Int()))
	case reflect.Float64:
		Println("Float64", v.Float())
	default:
		Println("None")
	}
}

func function09() {
	var a int
	var c int32 = 1
	b := 0.1
	reflectValue(a) // Int 0
	reflectValue(c) // Int32 1
	reflectValue(b) // Float64 0.1
}

结构体反射

任意值通过 reflect.Typeof() 获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的NumField()Field() 方法获得结构体成员的详细信息。

方法说明
Field(i int) StructField根据索引,返回索引对应的结构体字段的信息
NumField() int返回结构体成员字段数量
FieldByName(name string) (StructField, bool)根据给定字符串返回字符串赌赢的结构体字段信息
FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构

遍历 Person 结构体,根据 tag 获取对应的名称,并获取对应字段的值:

  • reflect.Typereflect.Value 类型,如果是指针类型(reflect.Ptr),可以通过 Elem 方法获取指针对应的值
  • reflect.TypeNumFieldField 获得结构体成员的详细信息(StructField
  • reflect.ValueFieldByName 通过字段名字获取对应的值
  • reflect.ValueFieldByIndex 通过 []int 切片指定字段的层级获取具体某个字段的值,比如 []int{0, 1} 表示结构体第一个字段内部的第二个字段
type Person struct {
	Name string `info:"name"`
	Age  int8   `info:"age"`
}

func function10() {
	cauchy := &Person{
		"cauchy",
		20,
	}
	t := reflect.TypeOf(cauchy)
	v := reflect.ValueOf(cauchy)

	if t.Kind() == reflect.Ptr && v.Kind() == reflect.Ptr {
		Println("reflect.Ptr") // reflect.Ptr
	}

	for i := 0; i < t.Elem().NumField(); i++ {
		f := t.Elem().Field(i)
		Println(f.Index, f.Name, f.Type)
		Println(f.Tag.Get("info"), v.Elem().FieldByName(f.Name))
	} /*
		[0] Name string
		name cauchy
		[1] Age int8
		age 20
	*/

	value0 := v.Elem().FieldByIndex([]int{0})
	value1 := v.Elem().FieldByIndex([]int{1})
	Println(value0, value1) // cauchy 20
}

方法说明
FieldByNameFunc(match func(string) bool) (StructField, bool)根据传入的匹配函数匹配需要的字段
NumMethod() int返回该类型的方法集中方法的数目
Method(int) Method返回该类型方法集中的第 i 个方法
MethodByName(string) (Method, bool)根据方法名返回该类型方法集中的方法

获取结构体方法,并进行调用:

  • reflect.TypeNumMethod 可以获取该类型可导出的方法(首字母大写)

    • 指针类型(reflect.Ptr)可以获取该类型的指针、值接收者的所有方法
    • 值类型仅可以获取该类型的值接收者的所有方法
  • reflect.TypeMethod 通过下标 index 获取该类型的对应的方法 Method

  • reflect.ValueMethodByName 通过方法名称获取对应可执行方法,实际返回是一个 reflect.Value 类型

    • 返回的类型可以通过 IsValidIsNil 判断是否有效,是否为空

    • 类型存在,则调用 Call 可以调用该类型实际的对应的方法

      • Call 接收参数是 []reflect.Value 类型
func (p *Person) SetName(name string) {
	p.Name = name
}

func (p Person) GetName() string {
	return p.Name
}

func function11() {
	p := &Person{
		"cauchy",
		20,
	}

	t := reflect.TypeOf(p)
	v := reflect.ValueOf(p)

	Println(t.Kind(), t.Elem().Kind()) // ptr struct

	// 获取的方法必须是可导出的,首字母大写
	// 指针类型获取(t.NumMethod()),所有接收者方法
	// 值类型获取(t.Elem().NumMethod()),值接收者方法
	ptrN, N := t.NumMethod(), t.Elem().NumMethod()
	Println(ptrN, N) // 2, 2

	for i := 0; i < ptrN; i++ {
		m := t.Method(i)
		Println(m.Name, m.Type, m.Func)
	} /*
		GetName func(*main.Person) string 0x1ddf80
		SetName func(*main.Person, string) 0x1ddb80
	*/

	// 通过名称获取函数
	f := v.MethodByName("GetName")
	// 如果函数存在
	if f.IsValid() && !f.IsNil() {
		ret := f.Call(nil) // 调用,不用参数 nil
		Println(ret)       // [cauchy]
	}

	// 参数必须是 []reflect.Value 类型
	v.MethodByName("SetName").Call([]reflect.Value{reflect.ValueOf("AQ")})
	ret := v.MethodByName("GetName").Call(nil)
	Println(ret) // [AQ]
}

上述代码需要注意的一点,要想修改一个反射对象,那么它必须是可设置的。((p *SetName)

读者可自行运行上述代码,做相应修改测试是否是预期结果。


http://www.niftyadmin.cn/n/5353192.html

相关文章

华为策略路由+NQA配置

---NQA--- [RouterA] nqa test-instance admin NQA [RouterA-nqa-admin-vlan10] test-type icmp [RouterA-nqa-admin-vlan10] destination-address ipv4 对方地址 [RouterA-nqa-admin-vlan10] frequency 10 [RouterA-nqa-admin-vlan10] probe-count 2 [RouterA-nqa-admin-vlan…

DAY34:贪心算法part、1005\134\135

Leetcode: 1005 K次取反后最大化的数组和 基本思路 这道题的思路比较简单&#xff0c;如果有负数&#xff0c;就先把最大的负数转化成正数&#xff0c;如果全部转换完之后还有k剩余&#xff0c;就将最小的正数反复正负变化。但是需要注意一点代码的写法。 代码注意点 定义绝…

Node.js-express

1.了解Ajax 1.1 什么是ajax Ajax的全称是Asynchronous Javascript And XML&#xff08;异步Js和XML&#xff09;. 通俗的理解&#xff1a;在网页中利用XMLHttpRequest对象和服务器进行数据交互的方式&#xff0c;就是Ajax 1.2 为什么要学习Ajax 之前所学的技术&#xff0c…

ClickHouse(23)ClickHouse集成Mysql表引擎详细解析

MySQL表引擎 MySQL引擎可以对存在远程MySQL服务器上的数据执行SELECT查询。 调用格式&#xff1a; MySQL(host:port, database, table, user, password[, replace_query, on_duplicate_clause]);调用参数 host:port — MySQL 服务器地址。database — 数据库的名称。table …

如何“安装CyberDuck和使用”win11系统?

1、下载 下载 (cyberduck.io) 2、安装 3、使用

wsl-ubuntu 安装 nginx

wsl-ubuntu 安装 nginx 1. 安装 nginx2. 确认 nginx 启动状态3. 重启 nginx4. 停止 nginx 1. 安装 nginx sudo apt install nginx2. 确认 nginx 启动状态 systemctl status nginx3. 重启 nginx systemctl restart nginx4. 停止 nginx systemctl stop nginx完成&#xff01;…

PyFlink使用教程,Flink,Python,Java

环境准备 环境要求 Java 11 Python 3.7, 3.8, 3.9 or 3.10文档&#xff1a;https://nightlies.apache.org/flink/flink-docs-release-1.17/zh/docs/dev/python/installation/ 打开 Anaconda3 Prompt > java -version java version "11.0.22" 2024-01-16 LTS J…

封装通用mixins,在vue中实现a-table组件的可伸缩列(详细且使用便捷)

1、实现效果 2、使用场景 vue2 antd-vue 1.x版本由于antd-vue 1.x版本的组件库没有提供可伸缩列的功能&#xff0c;才需要我们手动开发在antd-vue 3.x版本以上的表格已经支持这个功能&#xff0c;不需要我们再去手动开发 3、话不多说&#xff0c;上代码 首先安装vue-dragga…