【问题标题】:Equality (Identity) of Go SlicesGo Slices 的相等性(身份)
【发布时间】:2019-03-31 06:30:06
【问题描述】:

我的问题与this question 略有不同,后者询问如何检查 Go 切片的相等性。

就像article 建议的那样,Go 切片是一个 ,由三部分组成:指向数组的指针、段的长度和它的容量(段的最大长度)。那么是否可以(便宜地)检查两个这样的切片是否相等,因为它们指向同一个底层数组并且具有相同的长度和容量值(最好不遍历两个切片检查单个元素的相等性)?看来== 运算符没有在切片上定义。

当我实现一个位向量 (IntSet) 时,问题出现了,它在内部使用 []uint64 来表示元素,我偶然发现实现了一个 方法 func (*IntSet) Equals(that *IntSet) bool,它可以被称为喜欢s.Equals(s)

(看来我可以针对这种情况进行优化,如下所示,但问题仍然存在:

func (this *IntSet) Equals(that *IntSet) bool {
    if this == that { // use equality of pointers!
        return true
    }
// omitted for brevity 
}

【问题讨论】:

  • 根据该定义,[]int{1, 2, 3} == []int{1, 2, 3} 为假。似乎语言作者决定在切片上未定义相等运算符,而不是让特定的潜在错误点。
  • @TimCooper 我认为这是可以理解的,因为它们实际上是两个不同的切片 ,它们恰好具有相同的元素。
  • golang.org/ref/spec#Comparison_operators 讨论了==d 可以是什么(切片可以检查== nil,但不能相互检查)。作为语言设计师,我还猜想人们可能期望两个切片具有相同的值(即使在完全独立的数组中),它们是 ==,在某些情况下,即使是简单的值比较也可能会出现不一致的行为底层数组可能会或可能不会重新分配。

标签: go slice


【解决方案1】:

使用第一个元素的地址

最简单的方法是简单地获取切片的第一个元素的地址,然后比较它们(指针是comparable)。我们可以通过简单地使用address operator 来获取第一个元素的地址,例如&s[0]。如果切片为空,则没有第一个元素,在这种情况下,我们只检查两者是否为空。我们还必须比较切片的长度:

func identical(s1, s2 []int) bool {
    if len(s1) != len(s2) {
        return false
    }

    return len(s1) == 0 || &s1[0] == &s2[0]
}

我故意省略了容量的比较,因为只有在切片被重新切片时才会起作用。

这个identical() 函数只检查切片是否相同。 2 个不相同的切片可能相等(它们可能包含相等的元素),即使它们不相同。

测试它:

s := []int{1, 2, 3}
fmt.Println(identical(s, s))

s2 := []int{1, 2, 3}
fmt.Println(identical(s, s2))

输出是(在Go Playground上试试):

true
false

使用reflect.SliceHeader

我们可以选择获取和使用包含指针、长度和容量的切片描述符。这是reflect.SliceHeader建模的:

type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
}

要获得reflect.SliceHeader,我们可以使用包unsafeunsafe.Pointer这样的类型:

var s []int = ... // s is a slice

// and h will be its descriptor, of type *reflect.SliceHeader
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))

一个简单的比较器函数,它检查 2 个切片是否相同,这意味着它们指向相同的后备数组并且具有相同的长度(无论它们的容量如何):

func identical(s1, s2 []int) bool {
    h1 := (*reflect.SliceHeader)(unsafe.Pointer(&s1))
    h2 := (*reflect.SliceHeader)(unsafe.Pointer(&s2))

    return h1.Data == h2.Data && h1.Len == h2.Len
}

测试它:

s := []int{1, 2, 3}
fmt.Println(identical(s, s))

s2 := []int{1, 2, 3}
fmt.Println(identical(s, s2))

输出(在Go Playground 上试试):

true
false

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    • 2016-07-14
    • 1970-01-01
    相关资源
    最近更新 更多