Golang 之 struct能不能比較


struct能不能比較? 很顯然這句話包含了兩種情況:

  • 同一個struct的兩個實例能不能比較?
  • 兩個不同的struct的實例能不能比較?

划重點

在分析上面兩個問題前,先跟大家梳理一下golang中,哪些數據類型是可比較的,哪些是不可比較的:

  • 可比較:IntegerFloating-pointStringBooleanComplex(復數型)PointerChannelInterfaceArray
  • 不可比較:SliceMapFunction

下面就跟大家分別分析一下上面兩種情況吧

同一個struct的兩個實例能不能比較

首先,我們構造一個struct結構體來玩玩吧

type S struct {
    Name    string
    Age     int
    Address *int
}

func main() {
    a := S{
        Name:    "aa",
        Age:     1,
        Address: new(int),
    }
    b := S{
        Name:    "aa",
        Age:     1,
        Address: new(int),
    }

	  fmt.Println(a == b)
}

運行上面的代碼發現會打印false。既然能正常打印輸出,說明是可以個比較的,接下來讓我們來個死亡兩問

什么可以比較?

回到上面的划重點部分,在總結中我們可以知道,golang中 SliceMapFunction 這三種數據類型是不可以直接比較的。我們再看看S結構體,該結構體並沒有包含不可比較的成員變量,所以該結構體是可以直接比較的。

為什么打印輸出false?

a 和 b 雖然是同一個struct 的兩個實例,但是因為其中的指針變量 Address 的值不同,所以 a != b,如果a b 在初始化時把 Address 去掉(不給 Address 初始化),那么這時 a == b 為true, 因為ptr變量默認值是nil,又或者給 Address 成員變量賦上同一個指針變量的值,也是成立的。

如果給結構體S增加一個Slice類型的成員變量后又是什么情況呢?

type S struct {
    Name    string
    Age     int
    Address *int
	  Data    []int
}

func main() {
    a := S{
        Name:    "aa",
        Age:     1,
        Address: new(int),
		    Data:    []int{1, 2, 3},
    }
    b := S{
        Name:    "aa",
        Age:     1,
        Address: new(int),
		    Data:    []int{1, 2, 3},
    }

	  fmt.Println(a == b)
}

這時候會打印輸出什么呢?true?false?實際上運行上面的代碼會報下面的錯誤:

# command-line-arguments
./test.go:37:16: invalid operation: a == b (struct containing []int cannot be compared)

a, b 雖然是同一個struct兩個賦值相同的實例,因為結構體成員變量中帶有了不能比較的成員(slice),是不可以直接用 == 比較的,所以只要寫 == 就報錯

總結

同一個struct的兩個實例可比較也不可比較,當結構不包含不可直接比較成員變量時可直接比較,否則不可直接比較


但在平時的實踐過程中,當我們需要對含有不可直接比較的數據類型的結構體實例進行比較時,是不是就沒法比較了呢?事實上並非如此,golang還是友好滴,我們可以借助 reflect.DeepEqual 函數 來對兩個變量進行比較。所以上面代碼我們可以這樣寫:

type S struct {
    Name    string
    Age     int
    Address *int
	  Data    []int
}

func main() {
    a := S{
        Name:    "aa",
        Age:     1,
        Address: new(int),
		    Data:    []int{1, 2, 3},
    }
    b := S{
        Name:    "aa",
        Age:     1,
        Address: new(int),
		    Data:    []int{1, 2, 3},
    }

	  fmt.Println(reflect.DeepEqual(a, b))
}

打印輸出:

true

那么 reflect.DeepEqual 是如何對變量進行比較的呢?

reflect.DeepEqual

DeepEqual函數用來判斷兩個值是否深度一致。具體比較規則如下:

  • 不同類型的值永遠不會深度相等
  • 當兩個數組的元素對應深度相等時,兩個數組深度相等
  • 當兩個相同結構體的所有字段對應深度相等的時候,兩個結構體深度相等
  • 當兩個函數都為nil時,兩個函數深度相等,其他情況不相等(相同函數也不相等)
  • 當兩個interface的真實值深度相等時,兩個interface深度相等
  • map的比較需要同時滿足以下幾個
    • 兩個map都為nil或者都不為nil,並且長度要相等
    • 相同的map對象或者所有key要對應相同
    • map對應的value也要深度相等
  • 指針,滿足以下其一即是深度相等
    • 兩個指針滿足go的==操作符
    • 兩個指針指向的值是深度相等的
  • 切片,需要同時滿足以下幾點才是深度相等
    • 兩個切片都為nil或者都不為nil,並且長度要相等
    • 兩個切片底層數據指向的第一個位置要相同或者底層的元素要深度相等
    • 注意:空的切片跟nil切片是不深度相等的
  • 其他類型的值(numbers, bools, strings, channels)如果滿足go的==操作符,則是深度相等的。要注意不是所有的值都深度相等於自己,例如函數,以及嵌套包含這些值的結構體,數組等

兩個不同的struct的實例能不能比較

結論:可以比較,也不可以比較

可通過強制轉換來比較:

type T2 struct {
    Name  string
    Age   int
    Arr   [2]bool
    ptr   *int
}

type T3 struct {
    Name  string
    Age   int
    Arr   [2]bool
    ptr   *int
}

func main() {
    var ss1 T2
    var ss2 T3
    // Cannot use 'ss2' (type T3) as type T2 in assignment
    //ss1 = ss2     // 不同結構體之間是不可以賦值的
    ss3 := T2(ss2)
    fmt.Println(ss3==ss1) // true
}

如果成員變量中含有不可比較成員變量,即使可以強制轉換,也不可以比較

type T2 struct {
    Name  string
    Age   int
    Arr   [2]bool
    ptr   *int
    map1  map[string]string
}

type T3 struct {
    Name  string
    Age   int
    Arr   [2]bool
    ptr   *int
    map1  map[string]string
}

func main() {
    var ss1 T2
    var ss2 T3
    
    ss3 := T2(ss2)
    fmt.Println(ss3==ss1)   // 含有不可比較成員變量
}

編譯報錯:

# command-line-arguments
./test.go:28:18: invalid operation: ss3 == ss1 (struct containing map[string]string cannot be compared)

問:struct可以作為map的key么

struct必須是可比較的,才能作為key,否則編譯時報錯

type T1 struct {
    Name  string
    Age   int
    Arr   [2]bool
    ptr   *int
    slice []int
    map1  map[string]string
}

type T2 struct {
    Name string
    Age  int
    Arr  [2]bool
    ptr  *int
}

func main() {
    // n := make(map[T2]string, 0) // 無報錯
    // fmt.Print(n)                // map[]

    m := make(map[T1]string, 0)
    fmt.Println(m) // invalid map key type T1
}

上面就是今天要跟大家分享的內容,有什么問題歡迎大家在后台給大叔留言,關注大叔說碼,我們下期見~

參考文檔:

  1. https://studygolang.com/pkgdoc
  2. https://studygolang.com/articles/12944?fr=sidebar


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM