Go 1.21.0 新增 3 个内置函数详解

2023-09-08 16:20:11 浏览数 (1)

01

介绍

Go 1.21.0 新增 3 个内置函数,minmax 函数,返回 N 个入参中最小/最大的参数,参数类型为 Ordered(有序类型,即支持比较运算符的类型)。

clear 函数,删除 map 中的所有元素,将切片中的所有元素改为切片类型的零值。

本文我们详细介绍 minmaxclear 的使用方式。

02

Min and max

源码/usr/local/go/src/builtin/builtin.go

代码语言:javascript复制
// 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 个函数,分别是 LessCompareisNaN,感兴趣的读者朋友们可以阅读源码,本文将不展开介绍 cmp 包提供的函数。

如果读者朋友们还不熟悉泛型,建议先阅读之前的一篇介绍泛型的文章「Go 1.18 新增三大功能之一“泛型”怎么使用?」。

[cmp.Ordered]类型源码:

代码语言:javascript复制
// 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
}

内置函数 minmax 分别计算任意数量(至少有一个参数)的可比较类型参数的最小值和最大值。

所谓可比较类型参数,即可以使用运算符比较的参数,比如整型、浮点型、字符串。

如果泛型 T 是浮点数类型,并且任意参数是 NaN("not-a-number"),则函数的返回结果是 NaN

所谓不可比较类型参数,即不可以使用运算符比较的参数,比如 slicemapfunction,它们不可以作为 minmax 的参数。

注意:虽然 slicemapfunction 三种类型不可比较,但是,有个特例,即它们都可以和 nil 比较。

接下来,我们使用 minmax 分别计算整型、浮点型、字符串,代码如下:

示例一

代码语言: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
}

阅读上面这段代码,我们定义整型参数 xy,分别赋值 12,并将 xy 作为 min 函数的入参,返回值赋值给参数 m,打印 m 输出的值为 1,即参数 xy 中值最小的参数的值。

示例五

代码语言: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 输出的值为空字符串。

根据 tt1 的值,我们可以得出结论,即如果 min 函数的入参为字符串类型的参数,则按照字典序返回最小的字符串,如果有空字符串,则返回空字符串。

示例七

代码语言:javascript复制
func main() {
    m := min(3.14, math.NaN(), 1.0)
    fmt.Println(m) // NaN
}

阅读上面这段代码,参数为浮点数类型,包含 NaN,返回结果则是 NaN

函数 max 和函数 min 的使用方式相同,返回结果相反。

在项目开发中,我们可以使用 minmax 直接比较一组数据,得出最小/最大的结果值,而不再需要循环遍历。

特别提示:

  1. 整型参数,minmax 的参数可以交换和组合。
  2. 字符串类型参数,minmax 的参数逐个字节比较,得出最小/最大的字符串,参数可以交换和组合。
  3. 浮点型参数-0.00.0 作为参数,-0.0 小于 0.0;负无穷大,小于任意其它数值;正无穷大,大于任意其它数值。
  4. minmax 的任意参数是 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 也是泛型函数,入参为 mapslicetype parameter (类型参数),如果是 map,则删除 map 中的所有元素,返回一个空 map;如果是 slice,则将 slice 中的所有元素改为切片类型的零值。

需要注意的是,如果函数 clear 的入参是 type parameter (类型参数),则类型参数的集合必须仅包含 mapslice,函数 clear 则按照类型参数集合中的字段类型,执行相应的操作。

注意:如果 mapslicenil,函数 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 的类型是 sliceslice类型的参数类型是 structstruct 的字段类型为 map,返回结果是按照 struct 的字段类型做相应处理,该示例是删除 map 中的所有元素;参数 d1 同理。

关于类型参数的示例,阅读起来比较烧脑,建议读者朋友们运行代码加深理解。

在项目开发中,我们可以使用函数 clear 删除 map 中的元素,替代通过循环遍历调用 delete 删除 map 中的元素;使用函数 clearslice 中的元素的值修改为切片类型的零值,替代通过循环遍历修改切片中的元素的值为切片类型的零值。

04

总结

本文我们介绍 Go 1.21.0 新增的 3 个内置函数,通过示例代码介绍函数的使用方式和注意事项。

读者朋友们可以尝试在项目开发中使用新增的 3 个内置函数,逐步加深对这 3 个新增内置函数的理解。

关于其它内置函数,我们在之前的文章「Go 语言 15 个内置函数详解」中已经介绍,建议还没有阅读的读者朋友们一起阅读。

推荐阅读

  1. Go 语言怎么使用类型转换和类型断言?
  2. Go 配置管理库 Viper 怎么读取结构体嵌套的配置信息?
  3. Go 语言实现创建型设计模式 - 工厂模式
  4. Go 语言各个版本支持 Go Modules 的演进史
  5. Go 1.18 新增三大功能之一“泛型”怎么使用?

参考资料

[1]

NaN: https://en.wikipedia.org/wiki/NaN

0 人点赞