介绍
Go 1.21.0 新增 3 个内置函数,min
和 max
函数,返回 N
个入参中最小/最大的参数,参数类型为 Ordered
(有序类型,即支持比较运算符的类型)。
clear
函数,删除 map
中的所有元素,将切片中的所有元素改为切片类型的零值。
本文我们详细介绍 min
、max
和 clear
的使用方式。
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
分别计算整型、浮点型、字符串,代码如下:
示例一
func main() { m := min() fmt.Println(m) // invalid operation: not enough arguments for min() (expected 1, found 0) }
阅读上面这段代码,我们没有传入参数,执行代码,返回错误信息。
示例二
func main() { s := []int{1, 2, 3} m := min(s...) fmt.Println(m) // invalid operation: invalid use of ... with built-in min }
阅读上面这段代码,我们传入切片类型参数,执行代码,返回错误信息。
示例三
func main() { var x int m := min(x) fmt.Println(m) // 0 }
阅读上面这段代码,我们定义一个整型参数 x
,并将其作为 min
函数的入参,返回值赋值给参数 m
,打印 m
输出的值为 0
,即参数 x
的值。
根据打印输出结果,我们可以得出结论,当我们给 min
函数仅传入一个参数时,返回结果和入参的值相等,即 m == x
。
示例四
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
中值最小的参数的值。
示例五
func main() { c := min(1, 2.0, 3) fmt.Printf("%T\t%v\n", c, c) // float64 1 }
阅读上面这段代码,我们给 min
函数传入三个参数,分别是整型参数 1
、浮点型参数 2.0
和整型参数 3
,返回值赋值给参数 c
,打印 c
的类型和值,输出结果为 float64 1
,即三个参数中值最小的参数的值。
但是,值最小的参数的类型发生了变化,由整型转换为浮点型,这是因为 min
函数中的参数,如果有浮点型参数,则所有参数都会转换为浮点型参数作比较,因为运行该示例代码的电脑是 64
位操作系统,所以参数 2.0
的类型为 float64
。
如果我们指定浮点数类型,则参数都会转换为指定的浮点数类型参数作比较。
示例六
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
函数的入参为字符串类型的参数,则按照字典序返回最小的字符串,如果有空字符串,则返回空字符串。
示例七
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
// 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
的执行则是无效操作。
示例一
func main() { s := []int{1, 2, 3} fmt.Printf("len=%d\t s=%+v\n", len(s), s) // len=3 s=[1 2 3] clear(s) fmt.Printf("len=%d\t s=%+v\n", len(s), s) // len=3 s=[0 0 0] }
阅读上面这段代码,我们可以发现使用 clear
执行的切片,其长度不变,所有元素变为切片类型的零值。
示例二
func main() { m := map[string]int{"go": 100, "php": 80} fmt.Printf("len=%d\tm=%+v\n", len(m), m) // len=2 m=map[go:100 php:80] clear(m) fmt.Printf("len=%d\tm=%+v\n", len(m), m) // len=0 m=map[] }
阅读上面这段代码,我们可以发现使用 clear
执行的 map
,其长度变为 0
,所有元素被删除。
示例三
func main() { d := []Data{ { User: map[int]string{1: "frank", 2: "lucy"}, Salary: map[string]int{"frank": 1000, "lucy": 2000}, }, } fmt.Printf("d=%+v\n", d) // d=[{User:map[1:frank 2:lucy] Salary:map[frank:1000 lucy:2000]}] clear(d) fmt.Printf("d=%+v\n", d) // d=[{User:map[] Salary:map[]}] d1 := []Data1{ { User: "frank", Salary: 1000, }, } fmt.Printf("d1=%+v\n", d1) // d1=[{User:frank Salary:1000}] clear(d1) fmt.Printf("d1=%+v\n", 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 个内置函数详解」中已经介绍,建议还没有阅读的读者朋友们一起阅读。