01
介绍
Go 1.21.0 新增 3 个内置函数,min
和 max
函数,返回 N
个入参中最小/最大的参数,参数类型为 Ordered
(有序类型,即支持比较运算符的类型)。
clear
函数,删除 map
中的所有元素,将切片中的所有元素改为切片类型的零值。
本文我们详细介绍 min
、max
和 clear
的使用方式。
02
Min and max
源码/usr/local/go/src/builtin/builtin.go
// The max built-in function returns the largest value of a fixed number of
// arguments of [cmp.Ordered] types. There must be at least one argument.
// If T is a floating-point type and any of the arguments are NaNs,
// max will return NaN.
func max[T cmp.Ordered](x T, y ...T) T
// The min built-in function returns the smallest value of a fixed number of
// arguments of [cmp.Ordered] types. There must be at least one argument.
// If T is a floating-point type and any of the arguments are NaNs,
// min will return NaN.
func min[T cmp.Ordered](x T, y ...T) T
阅读源码,我们可以发现其是泛型函数,入参是 [cmp.Ordered]
,cmp
包也是 Go 1.21.0 新增的 package
,它提供 3 个函数,分别是 Less
、Compare
和 isNaN
,感兴趣的读者朋友们可以阅读源码,本文将不展开介绍 cmp
包提供的函数。
如果读者朋友们还不熟悉泛型,建议先阅读之前的一篇介绍泛型的文章「Go 1.18 新增三大功能之一“泛型”怎么使用?」。
[cmp.Ordered]
类型源码:
// Ordered is a constraint that permits any ordered type: any type
// that supports the operators < <= >= >.
// If future releases of Go add new ordered types,
// this constraint will be modified to include them.
//
// Note that floating-point types may contain NaN ("not-a-number") values.
// An operator such as == or < will always report false when
// comparing a NaN value with any other value, NaN or not.
// See the [Compare] function for a consistent way to compare NaN values.
type Ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}
内置函数 min
和 max
分别计算任意数量(至少有一个参数)的可比较类型参数的最小值和最大值。
所谓可比较类型参数,即可以使用运算符比较的参数,比如整型、浮点型、字符串。
如果泛型 T
是浮点数类型,并且任意参数是 NaN
("not-a-number"),则函数的返回结果是 NaN
。
所谓不可比较类型参数,即不可以使用运算符比较的参数,比如 slice
、map
、function
,它们不可以作为 min
和 max
的参数。
注意:虽然
slice
、map
、function
三种类型不可比较,但是,有个特例,即它们都可以和nil
比较。
接下来,我们使用 min
和 max
分别计算整型、浮点型、字符串,代码如下:
示例一
代码语言:javascript复制func main() {
m := min()
fmt.Println(m) // invalid operation: not enough arguments for min() (expected 1, found 0)
}
阅读上面这段代码,我们没有传入参数,执行代码,返回错误信息。
示例二
代码语言:javascript复制func main() {
s := []int{1, 2, 3}
m := min(s...)
fmt.Println(m) // invalid operation: invalid use of ... with built-in min
}
阅读上面这段代码,我们传入切片类型参数,执行代码,返回错误信息。
示例三
代码语言:javascript复制func main() {
var x int
m := min(x)
fmt.Println(m) // 0
}
阅读上面这段代码,我们定义一个整型参数 x
,并将其作为 min
函数的入参,返回值赋值给参数 m
,打印 m
输出的值为 0
,即参数 x
的值。
根据打印输出结果,我们可以得出结论,当我们给 min
函数仅传入一个参数时,返回结果和入参的值相等,即 m == x
。
示例四
代码语言:javascript复制func main() {
var x, y int = 1, 2
m := min(x, y)
fmt.Println(m) // 1
}
阅读上面这段代码,我们定义整型参数 x
和 y
,分别赋值 1
和 2
,并将 x
和 y
作为 min
函数的入参,返回值赋值给参数 m
,打印 m
输出的值为 1
,即参数 x
和 y
中值最小的参数的值。
示例五
代码语言:javascript复制func main() {
c := min(1, 2.0, 3)
fmt.Printf("%Tt%vn", c, c) // float64 1
}
阅读上面这段代码,我们给 min
函数传入三个参数,分别是整型参数 1
、浮点型参数 2.0
和整型参数 3
,返回值赋值给参数 c
,打印 c
的类型和值,输出结果为 float64 1
,即三个参数中值最小的参数的值。
但是,值最小的参数的类型发生了变化,由整型转换为浮点型,这是因为 min
函数中的参数,如果有浮点型参数,则所有参数都会转换为浮点型参数作比较,因为运行该示例代码的电脑是 64
位操作系统,所以参数 2.0
的类型为 float64
。
如果我们指定浮点数类型,则参数都会转换为指定的浮点数类型参数作比较。
示例六
代码语言:javascript复制func main() {
t := min("foo", "bar")
fmt.Println(t) // bar
t1 := min("", "foo", "bar")
fmt.Println(t) // ""
}
阅读上面这段代码,我们给 min
函数传入两个参数,分别是字符串 foo
和字符串 bar
,返回值赋值为参数 t
,打印 t
输出的值为 bar
。
我们给 min
函数传入三个参数,分别是空字符串、字符串 foo
和字符串 bar
,返回值赋值给参数 t1
,打印 t1
输出的值为空字符串。
根据 t
和 t1
的值,我们可以得出结论,即如果 min
函数的入参为字符串类型的参数,则按照字典序返回最小的字符串,如果有空字符串,则返回空字符串。
示例七
代码语言:javascript复制func main() {
m := min(3.14, math.NaN(), 1.0)
fmt.Println(m) // NaN
}
阅读上面这段代码,参数为浮点数类型,包含 NaN
,返回结果则是 NaN
。
函数 max
和函数 min
的使用方式相同,返回结果相反。
在项目开发中,我们可以使用 min
和 max
直接比较一组数据,得出最小/最大的结果值,而不再需要循环遍历。
特别提示:
- 整型参数,
min
和max
的参数可以交换和组合。 - 字符串类型参数,
min
和max
的参数逐个字节比较,得出最小/最大的字符串,参数可以交换和组合。 - 浮点型参数
-0.0
和0.0
作为参数,-0.0
小于0.0
;负无穷大,小于任意其它数值;正无穷大,大于任意其它数值。 min
和max
的任意参数是 NaN[1],则返回结果是NaN
("not-a-number") 值。
03
Clear
代码语言:javascript复制// The clear built-in function clears maps and slices.
// For maps, clear deletes all entries, resulting in an empty map.
// For slices, clear sets all elements up to the length of the slice
// to the zero value of the respective element type. If the argument
// type is a type parameter, the type parameter's type set must
// contain only map or slice types, and clear performs the operation
// implied by the type argument.
func clear[T ~[]Type | ~map[Type]Type1](t T)
阅读源码,我们可以发现 clear
也是泛型函数,入参为 map
、slice
、type parameter
(类型参数),如果是 map
,则删除 map
中的所有元素,返回一个空 map
;如果是 slice
,则将 slice
中的所有元素改为切片类型的零值。
需要注意的是,如果函数 clear
的入参是 type parameter
(类型参数),则类型参数的集合必须仅包含 map
或 slice
,函数 clear
则按照类型参数集合中的字段类型,执行相应的操作。
注意:如果
map
,slice
为nil
,函数clear
的执行则是无效操作。
示例一
代码语言:javascript复制func main() {
s := []int{1, 2, 3}
fmt.Printf("len=%dt s=% vn", len(s), s) // len=3 s=[1 2 3]
clear(s)
fmt.Printf("len=%dt s=% vn", len(s), s) // len=3 s=[0 0 0]
}
阅读上面这段代码,我们可以发现使用 clear
执行的切片,其长度不变,所有元素变为切片类型的零值。
示例二
代码语言:javascript复制func main() {
m := map[string]int{"go": 100, "php": 80}
fmt.Printf("len=%dtm=% vn", len(m), m) // len=2 m=map[go:100 php:80]
clear(m)
fmt.Printf("len=%dtm=% vn", len(m), m) // len=0 m=map[]
}
阅读上面这段代码,我们可以发现使用 clear
执行的 map
,其长度变为 0
,所有元素被删除。
示例三
代码语言:javascript复制func main() {
d := []Data{
{
User: map[int]string{1: "frank", 2: "lucy"},
Salary: map[string]int{"frank": 1000, "lucy": 2000},
},
}
fmt.Printf("d=% vn", d) // d=[{User:map[1:frank 2:lucy] Salary:map[frank:1000 lucy:2000]}]
clear(d)
fmt.Printf("d=% vn", d) // d=[{User:map[] Salary:map[]}]
d1 := []Data1{
{
User: "frank",
Salary: 1000,
},
}
fmt.Printf("d1=% vn", d1) // d1=[{User:frank Salary:1000}]
clear(d1)
fmt.Printf("d1=% vn", d1) // d1=[{User: Salary:0}]
}
type Data struct {
User map[int]string
Salary map[string]int
}
type Data1 struct {
User string
Salary int
}
阅读上面这段代码,我们可以发现使用 clear
执行类型参数集合参数 d
的类型是 slice
, slice
类型的参数类型是 struct
,struct
的字段类型为 map
,返回结果是按照 struct
的字段类型做相应处理,该示例是删除 map
中的所有元素;参数 d1
同理。
关于类型参数的示例,阅读起来比较烧脑,建议读者朋友们运行代码加深理解。
在项目开发中,我们可以使用函数 clear
删除 map
中的元素,替代通过循环遍历调用 delete
删除 map
中的元素;使用函数 clear
将 slice
中的元素的值修改为切片类型的零值,替代通过循环遍历修改切片中的元素的值为切片类型的零值。
04
总结
本文我们介绍 Go 1.21.0 新增的 3 个内置函数,通过示例代码介绍函数的使用方式和注意事项。
读者朋友们可以尝试在项目开发中使用新增的 3 个内置函数,逐步加深对这 3 个新增内置函数的理解。
关于其它内置函数,我们在之前的文章「Go 语言 15 个内置函数详解」中已经介绍,建议还没有阅读的读者朋友们一起阅读。
推荐阅读
- Go 语言怎么使用类型转换和类型断言?
- Go 配置管理库 Viper 怎么读取结构体嵌套的配置信息?
- Go 语言实现创建型设计模式 - 工厂模式
- Go 语言各个版本支持 Go Modules 的演进史
- Go 1.18 新增三大功能之一“泛型”怎么使用?
参考资料
[1]
NaN: https://en.wikipedia.org/wiki/NaN