首頁 > 軟體

Golang判斷struct/slice/map是否相等以及對比的方法總結

2022-11-28 22:00:16

前言

平時開發中對比兩個struct或者mapslice是否相等是經常遇到的,有很多對比的方式,比如==reflect.DeepEqual()cmp.Equal()等也是經常容易混淆的,這麼多種對比方式,適用場景和優缺點都有哪些呢?為什麼可以用==,有的卻不可以呢?問題多多,今天我們來具體總結一下,感興趣的小夥伴們可以參考借鑑,希望對大家能有所幫助。

== 的對比方式

== 適用的型別

相信==判等操作,大家每天都在用。golang中對==的處理有一些細節的地方需要特別注意,==操作最重要的一個前提是:兩個運算元型別必須相同!如果型別不同,那麼編譯時就會報錯。

範例程式碼:

package main

import "fmt"

func main() {
    var a int32
    var b int64
    // 編譯錯誤:invalid operation a == b (mismatched types int32 and int64)
    fmt.Println(a == b)
}

經常見到使用==的型別一般是:string,int等基本型別。struct有時候可以用有時候不可以。slicemap使用 ==會報錯。

slice和map使用 ==

因為slice和map不止是需要比較值,還需要比較len和cap,層級比較深的話還需要遞迴比較,不是簡單的==就可以比較的,所以他們各自之間是不可以直接用==比較的,slice和map只能和nil使用==。

  • 切片之間不允許比較。切片只能與nil值比較。
  • map之間不允許比較。map只能與nil值比較。
s1 := []int64{1, 3}
s2 := []int64{1, 2}
if s1 == nil {} //編輯器不會提示報錯
if s1 == s2 {} //編輯器會提示報錯

channel使用 ==

channel是參照型別,對比的是儲存資料的地址。channel是可以使用==的,只要型別一樣就可以。

ch1 := make(chan int, 1)
ch2 := ch1
if cha2 == cha1{fmt.Println("true")} // true

struct結構體使用==

結構體的定義只是一種記憶體佈局的描述,只有當結構體範例化時,才會真正地分配記憶體。

範例化就是根據結構體定義的格式建立一份與格式一致的記憶體區域,結構體範例與範例間的

記憶體是完全獨立的。對結構體進行&取地址操作時,視為對該型別進行一次 new 的範例化操作

因此:go中的結構體: v = Struct {}, v = &Struct{} 這個兩種寫法是等價的。

  • 簡單結構的結構體,裡面都是值型別或者指標的話,是可以使用 ==的
  • 結構體中含有slice或者map,都是不可以用==

範例程式碼:

package main

import (
    "fmt" 
)
type User struct {
    Name string
    Age  int64
}
type People struct {
    Name string
    Hobby []string
}

func main() {
        p1 := People{Name: "test", Hobby: []string{"唱", "跳"}}
        p2 := People{Name: "test", Hobby: []string{"唱", "跳"}}

        u1 := User{Name: "test", Age:18}
		u2 := User{Name: "test", Age:18}

        if p1 == p2 {
            fmt.Println("p1 ==  p2") //報錯
        }

    	if u1 == u2 {
            fmt.Println("u1 ==  u2")
        }
    }

reflect.DeepEqual() 和cmp.Equal()

reflect.DeepEqual()

reflect包提供的深度對比(遞迴)的方法,適用於go中的slice,map,struct,function的對比。

對比規則

  • 相同型別的值是深度相等的,不同型別的值永遠不會深度相等。
  • 當陣列值array的對應元素深度相等時,陣列值是深度相等的。
  • 當結構體struct值如果其對應的欄位(包括匯出和未匯出的欄位)都是深度相等的,則該值是深度相等的。
  • 當函數func值如果都是零,則是深度相等;否則就不是深度相等。
  • 當介面interface值如果持有深度相等的具體值,則深度相等。
  • 當切片slice序號相同,如果值,指標都相等,那麼就是深度相等的
  • 當雜湊表map相同的key,如果值,指標都相等,那麼就是深度相等的。

通過以上規則可以看到,reflect.DeepEqual是可以比較struct的,同時也可以用來比較slicemap

範例程式碼:

package main

import (
    "fmt"
    "reflect"
)

type People struct {
    Name string
    Hobby []string
}

func main() {
        p1 := People{Name: "test", Hobby: []string{"唱", "跳"}}
        p2 := People{Name: "test", Hobby: []string{"唱", "跳"}}
        if reflect.DeepEqual(p1, p2) {
            fmt.Println("struct true")
        }
        mp1 := map[int]int{1: 1, 2: 2}
	    mp2 := map[int]int{1: 1, 2: 2}
        if ok := reflect.DeepEqual(mp1, mp2);ok {
			fmt.Println("mp1 == mp2!")
	    } else {
			fmt.Println("mp1 != mp2!")
	    }
    }

cmp.Equal()

go-cmp是 Google 開源的比較庫,它提供了豐富的選項。

對比規則

  • 在經過路徑過濾,值過濾和型別過濾之後,會生一些忽略、轉換、比較選項,如果選項中存在忽略,則忽略比較,如果轉換器和比較器的資料大於1,則會panic(因為比較操作不明確)。如果選項中存在轉換器,則呼叫轉換器轉換當前值,再遞迴呼叫轉換器輸出型別的Equal。如果包含一個比較器。則比較使用比較器比較當前值。否則進入下一比較階段。
  • 如果比較值有一個(T) Equal(T) bool 或者 (T) Equal(I) bool,那麼,即使x與y是nil,也會呼叫x.Equal(y)做為結果。如果不存在這樣的方法,則進入下一階段。
  • 在最後階段,Equal方法嘗試比較x與y的基本型別。使用go語言的 == 比較基本型別(bool, intX, float32,float64, complex32,complex64, string, chan)。
  • 在比較struct時,將遞迴的比較struct的欄位。如果結構體包含未匯出的欄位,函數會panic可以通過指定cmpopts.IgnoreUnexported來忽略未匯出的欄位,也可以使用cmp.AllowUnexported來指定比較未匯出的欄位。

範例程式碼:

package main

import (
  "fmt"

  "github.com/google/go-cmp/cmp"
)

type Contact struct {
  Phone string
  Email string
}

type User struct {
  Name    string
  Age     int
  Contact *Contact
}

func main() {
  u1 := User{Name: "test", Age: 18}
  u2 := User{Name: "test", Age: 18}

  fmt.Println("u1 == u2?", u1 == u2)  //true
  fmt.Println("u1 equals u2?", cmp.Equal(u1, u2)) //true

  c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
  c2 := &Contact{Phone: "123456789", Email: "dj@example.com"}

  u1.Contact = c1
  u2.Contact = c1
  fmt.Println("u1 == u2 with same pointer?", u1 == u2) //true
  fmt.Println("u1 equals u2 with same pointer?", cmp.Equal(u1, u2)) //true

  u2.Contact = c2
  fmt.Println("u1 == u2 with different pointer?", u1 == u2) //false 
  fmt.Println("u1 equals u2 with different pointer?", cmp.Equal(u1, u2)) //true
}

cmp和DeepEqual的區別

  • 安全:cmp.Equal()函數不會比較未匯出欄位(即欄位名首字母小寫的欄位)。遇到未匯出欄位,cmp.Equal()直接panic,reflect.DeepEqual()會比較未匯出的欄位。
  • 強大:cmp.Equal()函數提供豐富的函數引數,讓我們可以實現:忽略部分欄位,比較零值,轉換某些值,允許誤差等。
  • 共同點:兩種比較型別,都會比較:物件型別,值,指標地址等。切片會按照索引比較值,map是按照key相等比較值。

效能比較

簡單型別的==對比速度最快

複雜型別,自己知道結構之後寫的自定義對比速度次之

複雜結構且不確定結構的,使用cmp.Equal()或者reflect.DeepEqual()都可以,就是效率低點

assert.Equal()底層使用的就是reflect.DeepEqual()

總結

對於比較兩個struct或者map,slice是否相等,方式很多,效率也有差異。選擇合適自己需求的最重要。相對來說,cmp包是要更安全且可操作性更強一點。

到此這篇關於Golang判斷struct/slice/map是否相等以及對比的方法總結的文章就介紹到這了,更多相關Golang struct slice map內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com