指针类型结构体,可以作为map的key吗

531 阅读5分钟

先说答案

可以。Go 中的指针类型结构体可以作为 map 的 key。

在使用指针类型结构体作为 map 的 key 时,需要注意结构体的值应该是可比较的,以便用作 map 的 key。

如果结构体中的字段是不可比较的类型,则无法作为 map 的 key。

以下是一个示例说明:

package main

import (
    "fmt"
)

// person 结构体
type person struct {
    name string
    age  int
}

func main() {
    // 使用指针类型结构体作为 map 的 key
    personMap := make(map[*person]string)

    // 创建两个 person 结构体的实例
    p1 := &person{name: "Alice", age: 30}
    p2 := &person{name: "Bob", age: 25}

    // 将指针类型结构体作为 key,与对应的值关联起来
    personMap[p1] = "Engineer"
    personMap[p2] = "Doctor"

    // 使用指针类型结构体作为 key 查找值
    fmt.Println("Alice's profession:", personMap[p1])
    fmt.Println("Bob's profession:", personMap[p2])
}

在这个示例中,我们定义了一个 person 结构体,然后创建了两个 person 结构体的指针。我们将这两个指针类型结构体作为 map 的 key,并与对应的职业字符串关联起来。最后,通过使用指针类型结构体作为 key 来查找关联的值。

那问题来了,什么是Go中的可比较类型呢?

怎么样才算是可比较的类型?

在Go中,结构体是可比较的(comparable)当且仅当所有字段都是可比较的类型时。可比较的类型包括基本类型(如整数、浮点数、字符串、布尔值等)以及指向可比较类型的指针。

以下是一些可比较的类型的例子:

  • 基本类型:int、float64、string、bool等
  • 数组类型,只有数组中的元素类型是可比较的情况下数组才是可比较的
  • 结构体类型,只有结构体中的字段类型都是可比较的情况下结构体才是可比较的
  • 指针类型,只有指向可比较类型的指针才是可比较的
  • 接口类型,只有接口中的动态值和动态类型是可比较的情况下接口才是可比较的

以下是一个示例,展示了可比较和不可比较的情况:

package main

import "fmt"

// 可比较的结构体
type ComparableStruct struct {
    a int
    b string
}

// 不可比较的结构体,包含不可比较的切片类型
type NonComparableStruct struct {
    c []int
    d map[string]int
}

func main() {
    // 示例:可比较的类型
    fmt.Println("=== 可比较的类型 ===")
    // int 是可比较的
    fmt.Println("1 == 1:", 1 == 1)
    // 字符串是可比较的
    fmt.Println(""hello" == "world":", "hello" == "world")

    // 结构体是可比较的,因为结构体的字段都是可比较的
    compStruct1 := ComparableStruct{a: 1, b: "hello"}
    compStruct2 := ComparableStruct{a: 1, b: "hello"}
    fmt.Println("compStruct1 == compStruct2:", compStruct1 == compStruct2)

    // 指针是可比较的
    ptr1 := &compStruct1
    ptr2 := &compStruct2
    fmt.Println("ptr1 == ptr2:", ptr1 == ptr2)

    // 示例:不可比较的类型
    fmt.Println("\n=== 不可比较的类型 ===")
    // 切片是不可比较的
    slice1 := []int{1, 2, 3}
    slice2 := []int{1, 2, 3}
    //fmt.Println("slice1 == slice2:", slice1 == slice2) // 这行会导致编译错误

    // 不可比较的结构体,因为包含不可比较的切片类型
    nonCompStruct1 := NonComparableStruct{c: []int{1, 2, 3}, d: map[string]int{"a": 1}}
    nonCompStruct2 := NonComparableStruct{c: []int{1, 2, 3}, d: map[string]int{"a": 1}}
    //fmt.Println("nonCompStruct1 == nonCompStruct2:", nonCompStruct1 == nonCompStruct2) // 这行会导致编译错误
}

在这个示例中,ComparableStruct 是可比较的,因为它的所有字段都是可比较的类型。相比之下,NonComparableStruct 包含不可比较的切片类型,因此它是不可比较的。

类似于切片 和 map channel 等都是不可比较的类型。

reflect.DeepEqual

在 Go 语言中,reflect.DeepEqual 是一个用于比较两个值是否深度相等的函数。它可以比较包括结构体、数组、切片、映射等复杂类型在内的任意值。与使用 == 运算符进行简单比较不同,reflect.DeepEqual 会递归地比较复合类型的每一个元素,因此适用于深度比较。

示例

下面是一些示例代码,展示了 reflect.DeepEqual 的用法和效果:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// 比较基础类型
	a := 123
	b := 123
	fmt.Println(reflect.DeepEqual(a, b)) // 输出: true

	// 比较切片
	slice1 := []int{1, 2, 3}
	slice2 := []int{1, 2, 3}
	fmt.Println(reflect.DeepEqual(slice1, slice2)) // 输出: true

	// 比较映射
	map1 := map[string]int{"one": 1, "two": 2}
	map2 := map[string]int{"one": 1, "two": 2}
	fmt.Println(reflect.DeepEqual(map1, map2)) // 输出: true

	// 比较结构体
	type Person struct {
		Name string
		Age  int
	}

	person1 := Person{Name: "Alice", Age: 30}
	person2 := Person{Name: "Alice", Age: 30}
	fmt.Println(reflect.DeepEqual(person1, person2)) // 输出: true

	// 比较不同类型
	c := 123
	d := "123"
	fmt.Println(reflect.DeepEqual(c, d)) // 输出: false

	// 比较不同长度的切片
	slice3 := []int{1, 2}
	fmt.Println(reflect.DeepEqual(slice1, slice3)) // 输出: false
}

代码解释

  1. 基础类型比较:两个整数 ab 直接比较,结果为 true
  2. 切片比较slice1slice2 包含相同的元素,结果为 true。注意,切片的顺序也必须一致。
  3. 映射比较map1map2 包含相同的键值对,结果为 true
  4. 结构体比较person1person2 的所有字段相同,结果为 true
  5. 不同类型比较:一个整数 c 和一个字符串 d 直接比较,结果为 false
  6. 不同长度的切片比较slice1slice3 长度不同,结果为 false

使用场景

  • 单元测试:在单元测试中,reflect.DeepEqual 非常有用,可以用来比较预期结果和实际结果,特别是当结果是复杂数据结构时。
  • 调试:在调试代码时,可以使用 reflect.DeepEqual 比较复杂数据结构,检查它们是否如预期那样相等。

注意事项

  • 性能reflect.DeepEqual 可能比简单的 == 比较慢,因为它需要递归地遍历复杂数据结构。因此,在性能关键的代码中使用时需要注意。
  • 深度比较reflect.DeepEqual 进行的是深度比较,而不仅仅是引用比较。例如,两个不同的切片引用但内容相同的切片也会被认为是相等的。

总结

在使用指针类型结构体作为 map 的 key 时,需要注意结构体的值应该是可比较的,以便用作 map 的 key。 如果结构体中的字段是不可比较的类型,则无法作为 map 的 key。

reflect.DeepEqual 是 Go 语言中的一个强大工具,用于比较两个值是否深度相等。它适用于各种复杂数据结构的比较,特别是在单元测试和调试中非常有用。