前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Golang 1.18 泛型:零值判断

Golang 1.18 泛型:零值判断

作者头像
恋喵大鲤鱼
发布于 2023-04-23 08:29:06
发布于 2023-04-23 08:29:06
1.1K00
代码可运行
举报
文章被收录于专栏:C/C++基础C/C++基础
运行总次数:0
代码可运行

1.背景

如果我想实现一个函数,其功能是清除一个切片中所有零值元素,该如何实现呢?

从 Golang 1.18 开始支持泛型,我们可以考虑使用泛型来实现支持任意类型的切片,那么需要判断泛型切片的元素是否为零值。

下面是我实现的一个清除切片零值元素的函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ClearZero creates a slice with all zero values removed.
func ClearZero[S ~[]E, E any](s S) S {
	r := make([]E, 0, len(s))
	for i := range s {
		if !IsZero(s[i]) {
			r = append(r, s[i])
		}
	}
	return r
}

这里的问题是如何判断泛型切片元素是否为零值,也就是实现上面代码中的函数 IsZero()

2.可能的实现

Go 原生支持类型零值,我们使用var v T申明一个变量 v,那么变量 v 便是类型 T 的零值。所以你可能会这么实现 IsZero()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func IsZero[T any](v T) bool {
	var zero T
	return v == zero // 此处有语法错误:invalid operation: cannot compare v == zero (incomparable types in type set)
}

从语法错误提示可以看出,我们没有对类型参数做可比较的限制,即没有将类型参数 T 限制为comparable。所以改为下面这样就可以了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func IsZero[T comparable](v T) bool {
	var zero T
	return v == zero
}

对应的,ClearZero 的元素类型 E 也要限定为comparable

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ClearZero creates a slice with all zero values removed.
func ClearZero[S ~[]E, E comparable](s S) S {
	r := make([]E, 0, len(s))
	for i := range s {
		if !IsZero(s[i]) {
			r = append(r, s[i])
		}
	}
	return r
}

上面的实现可以满足大部业务场景下的需要,因为日常使用的切片元素均是可比较大小的(comparable),比如 booleans, numbers, strings, pointers, channels 等。但是一旦切片元素类型不可比较时,便无法使用上面的ClearZero()。比如切片元素是个 map 时。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var ms []map[string]string
ClearZero(ms) // 此处有语法错误:map[string]string does not implement comparable

3.通用的实现

要想实现一个满足所有元素类型的 ClearZero(),那么将切片元素和类型参数的零值比较便不能满足要求,有没有其他更好的办法完成零值判断呢?

虽然 Go 支持了泛型,但是我们也不能忘记了反射。标准库包 reflect 有一个函数用于判断一个值是否是其对应类型的零值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// IsZero reports whether v is the zero value for its type.
// It panics if the argument is invalid.
func (v Value) IsZero() bool

有了 reflect Value.IsZero 我们便可以改写我们的 IsZero()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func IsZeroRef[T any](v T) bool {
	return reflect.ValueOf(v).IsZero()
}

// 或者
func IsZeroRef[T any](v T) bool {
	return reflect.ValueOf(&v).Elem().IsZero()
}

推荐使用后者,因为ValueOf接受一个interface{}参数,如果 v 恰好是一个接口,你就会丢失这个信息。也就是说,使用ValueOf(v)时,当 v 是一个 interface 时会有问题。

然后再改写一下ClearZero()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ClearZeroRef creates a slice with all zero values removed.
func ClearZeroRef[S ~[]E, E any](s S) S {
	r := make([]E, 0, len(s))
	for i := range s {
		if !IsZeroRef(s[i]) {
			r = append(r, s[i])
		}
	}
	return r
}

测试如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

import (
	"fmt"
	"reflect"
)

func main() {
	bs := []bool{true, false, true}
	fmt.Println(ClearZeroRef(bs))

	is := []int{1, 2, 0, 3}
	fmt.Println(ClearZeroRef(is))

	strs := []string{"foo", "bar", "", "baz"}
	fmt.Println(ClearZeroRef(strs))

	ms := []map[string]string{
		{"foo": "foo"},
		nil,
		{"bar": "bar"},
	}
	fmt.Println(ClearZeroRef(ms))
}

运行如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[true true]
[1 2 3]
[foo bar baz]
[map[foo:foo] map[bar:bar]]

4.go-huge-util

本文实现的两个函数对应的两个版本已放置开源仓库 dablelv/go-huge-util,欢迎大家使用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// IsZero reports whether v is the zero value for its type.
func IsZero[T comparable](v T) bool {
	var zero T
	return v == zero
}

// IsZeroRef reports whether v is the zero value for its type.
// IsZeroRef is implemented base on reflection. 
func IsZeroRef[T any](v T) bool {
	return reflect.ValueOf(v).IsZero()
}

// ClearZero creates a slice with all zero values removed.
func ClearZero[S ~[]E, E comparable](s S) S {
	r := make([]E, 0, len(s))
	for i := range s {
		if !IsZero(s[i]) {
			r = append(r, s[i])
		}
	}
	return r
}

// ClearZeroRef creates a slice with all zero values removed.
// ClearZeroRef is implemented base on reflection. 
func ClearZeroRef[S ~[]E, E any](s S) S {
	r := make([]E, 0, len(s))
	for i := range s {
		if !reflect.ValueOf(s[i]).IsZero() {
			r = append(r, s[i])
		}
	}
	return r
}

使用示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

import (
	"fmt"

	"github.com/dablelv/go-huge-util/cond"
	"github.com/dablelv/go-huge-util/slice"
)

type ILvlv interface {
	Name() string
}

type Lvlv struct{}

func (l Lvlv) Name() string {
	return "lvlv"
}

func main() {
	fmt.Println(cond.IsZero(false)) // true
	fmt.Println(cond.IsZero(true))  // false
	fmt.Println(cond.IsZero(0))     // true
	fmt.Println(cond.IsZero(1))     // false
	fmt.Println(cond.IsZero(""))    // true
	fmt.Println(cond.IsZero("foo")) // false

	fmt.Println(cond.IsZeroRef(map[string]string(nil)))          // true
	fmt.Println(cond.IsZeroRef(map[string]string{}))             // false
	fmt.Println(cond.IsZeroRef(map[string]string{"foo": "foo"})) // false
	
	ifcSlice := []ILvlv{Lvlv{}, nil, Lvlv{}}
	fmt.Println(cond.IsZeroRef(ifcSlice[0])) // false
	fmt.Println(cond.IsZeroRef(ifcSlice[1])) // true

	bs := []bool{true, false, true}
	fmt.Println(slice.ClearZero(bs))

	is := []int{1, 2, 0, 0, 3}
	fmt.Println(slice.ClearZero(is))

	strs := []string{"", "foo", "bar", "baz"}
	fmt.Println(slice.ClearZero(strs))

	ms := []map[string]string{
		{"foo": "foo"},
		nil,
		{"bar": "bar"},
	}
	fmt.Println(slice.ClearZeroRef(ms))
}

运行输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
true
false
true
false
true
false
true
false
false
false
true
[true true]
[1 2 3]
[foo bar baz]
[map[foo:foo] map[bar:bar]]

注意,在删除切片零值元素时,如果切片元素是可比较的(comparable),建议使用ClearZero,因为其性能略好于ClearZeroRef

参考文献

dablelv/go-huge-util - GitHub How to check if the value of a generic type is the zero value? - stackoverflow.com

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-04-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Go 切片转集合(Slice to Set)
因为 Golang 是一门追求简单、现代、易于使用的语言,所以不会引入不必要的特性。
恋喵大鲤鱼
2022/12/13
1K0
Golang 将切片连接成字符串
Join 将字符串切片的所有元素连接成一个字符串,各个元素间使用给定的字符串分隔。
恋喵大鲤鱼
2023/10/12
3530
Golang 将切片连接成字符串
Go 判断元素是否在切片中
如何判断元素是否在切片中,Golang 并没有提供直接的库函数来判断,最容易想到的实现便是通过遍历来判断。
恋喵大鲤鱼
2021/12/06
10.4K0
Go 判断元素是否在切片中
Golang 任意类型切片的增删改查
slice 名为切片,是 Go 中的可变长数组,是对底层数组的封装和引用。切片指向一个底层数组,并且包含长度和容量信息。未初始化切片的值为 nil。作用于切片的内建函数主要有四个,分别是 make、len、cap 和 append。make 用于创建切片,len 用于获取切片的长度,cap 用于获取切片的容量,append 用于向切片追加元素。
恋喵大鲤鱼
2022/05/09
9170
Golang 任意类型切片的增删改查
Golang 泛型实现类型转换
Golang 标准库提供了很多类型转换的函数,如 strconv 包可完成 string 与基本数据类型之间的转换。
恋喵大鲤鱼
2022/12/30
3.6K0
Go map 转 slice
编码中,我们可能需要将 map 的 key 或者 value 转换为 slice 进行操作。
恋喵大鲤鱼
2021/07/14
2.4K0
Go map 转 slice
Go 中几种常见的编程模式
模式可以理解成最佳实践,或者是约定俗成的规范或套路,熟悉一些常见模式可以方便理解项目代码。本文是参考左耳朵耗子的专栏文章做的笔记,另外也缅怀一下耗子叔。
菜皮日记
2023/12/18
1640
Go 中几种常见的编程模式
Golang 任意类型切片互转
上面的两个例子,除了元素类型转换的实现不同,其他的代码都是重复的。如果为多种不同类型切片互转都实现各自的转换函数,无疑是低效繁琐的。
恋喵大鲤鱼
2023/10/12
7660
GoLang反射
反射是 Go 语言比较重要的特性。虽然在大多数的应用和服务中并不常见,但是很多框架都依赖 Go 语言的反射机制实现简化代码的逻辑。因为 Go 语言的语法元素很少、设计简单,所以它没有特别强的表达能力,但是 Go 语言的 reflect 包能够弥补它在语法上的一些劣势。
大忽悠爱学习
2022/08/23
4940
GoLang反射
深入理解 go 反射
反射是可以让我们在程序运行时(runtime)访问、检测和修改对象本身状态或行为的一种机制。
leobhao
2024/04/01
1400
深入理解 go 反射
Golang反射-上篇
反射是指在运行时动态的访问和修改任意类型对象的结构和成员,在go语言中提供reflect包提供反射的功能,每一个变量都有两个属性:类型Type和值Value
仙人技术
2021/11/12
8670
Golang反射-上篇
golang教程
这里有两个关键点。 - 其一是defer关键字。defer语句的含义是不管程序是否出现异常,均 在函数退出时自动执行相关代码。 - 其二是Go语言的函数允许返回多个值。
陨石坠灭
2018/10/19
1.7K0
golang教程
golang中的反射
本文转载自https://github.com/KeKe-Li/For-learning-Go-Tutorial/edit/master/src/chapter07/01.0.md
ccf19881030
2020/12/16
1.2K0
理解Golang的泛型
为降低interface{}带来的糟糕阅读体验,新增了any关键字,它实际上是一种语法糖,定义如下:
chandlerpan
2022/07/12
1.5K0
深入理解Golang的reflect原理
反射是指在运行期对程序本身进行访问和修改的能力。程序编译后,变量被转换为内存地址,而变量名无法被编译器写入可执行部分。在运行程序时,程序无法获取自身的信息。
KunkkaWu
2023/05/05
7930
深入理解Golang的reflect原理
Golang 递归获取目录下所有文件
标准库 io/ioutil 包提供了一个函数 ReadDir() 可以获取指定目录下的所有内容,按文件名排序,返回 []fs.FileInfo 切片来描述目录中的所有内容。
恋喵大鲤鱼
2022/12/02
3.3K0
Go 每日一库之 reflect
反射是一种机制,在编译时不知道具体类型的情况下,可以透视结构的组成、更新值。使用反射,可以让我们编写出能统一处理所有类型的代码。甚至是编写这部分代码时还不存在的类型。一个具体的例子就是fmt.Println()方法,可以打印出我们自定义的结构类型。
用户7731323
2021/06/25
6200
Go反射
Go语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。
用户9022575
2021/10/01
1.1K0
Golang 反射
在Go语言中,大多数时候值/类型/函数非常直接,要的话,定义一个。你想要个Struct
李海彬
2019/03/07
9650
Golang 反射
Go通关08:断言、反射的理解与使用!
您诸位好啊,我是无尘,学习Go语言肯定经常看到断言、反射这两个词,曾因为使用场景不太熟悉,让我很是费解,今天就好好唠唠!
微客鸟窝
2021/08/18
1.1K0
相关推荐
Go 切片转集合(Slice to Set)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验