手撕 Go 面試官:Go 結構體是否可以比較,為什么?
今天的男主角,是 Go 工程師的必修技能,也是極容易踩坑的地方,就是 “Go 面試題:Go 結構體(struct)是否可以比較?”
如果可以比較,是為什么?如果不可以比較,又是為什么?
請在此處默念自己心目中的答案,再往和煎魚一起研討一波 Go 的技術哲學。
結構體是什么
在 Go 語言中有個基本類型,開發(fā)者們稱之為結構體(struct)。是 Go 語言中非常常用的,基本定義:
type struct_variable_type struct {
member definition
member definition
...
member definition
}
簡單示例:
package main
import "fmt"
type Vertex struct {
Name1 string
Name2 string
}
func main() {
v := Vertex{"腦子進了", "煎魚"}
v.Name2 = "蒸魚"
fmt.Println(v.Name2)
}
輸出結果:
蒸魚
這部分屬于基礎知識,因此不再過多解釋。如果看不懂,建議重學 Go 語言語法基礎。
比較兩下
例子一
接下來正式開始研討 Go 結構體比較的問題,第一個例子如下:
type Value struct {
Name string
Gender string
}
func main() {
v1 := Value{Name: "煎魚", Gender: "男"}
v2 := Value{Name: "煎魚", Gender: "男"}
if v1 == v2 {
fmt.Println("腦子進煎魚了")
return
}
fmt.Println("腦子沒進煎魚")
}
我們聲明了兩個變量,分別是 v1 和 v2。其都是 Value 結構體的實例化,是同一個結構體的兩個實例。
他們的比較結果是什么呢,是輸出 ”腦子進煎魚了“,還是 ”腦子沒進煎魚“?
輸出結果:
腦子進煎魚了
最終輸出結果是 ”腦子進煎魚了“,初步的結論是可以結構體間比較的。皆大歡喜,那這篇文章是不是就要結束了?
當然不是...很多人都會踩到這個 Go 語言的坑,真實情況是結構體是可比較,也不可比較的,不要誤入歧途了,這是一個非常 "有趣" 的現(xiàn)象。
例子二
接下來繼續(xù)改造上面的例子,我們在原本的結構體中增加了指針類型的引用。
第二個例子如下:
type Value struct {
Name string
Gender *string
}
func main() {
v1 := Value{Name: "煎魚", Gender: new(string)}
v2 := Value{Name: "煎魚", Gender: new(string)}
if v1 == v2 {
fmt.Println("腦子進煎魚了")
return
}
fmt.Println("腦子沒進煎魚")
}
這段程序輸出結果是什么呢,我們猜測一下,變量依然是同一結構體的兩個實例,值的賦值方式和內(nèi)容都是一樣的,是否應當輸出 “腦子進煎魚了”?
答案是:腦子沒進煎魚。
例子三
我們繼續(xù)不信邪,試試另外的基本類型,看看結果是不是還是相等的。
第三個例子如下:
type Value struct {
Name string
GoodAt []string
}
func main() {
v1 := Value{Name: "煎魚", GoodAt: []string{"炸", "煎", "蒸"}}
v2 := Value{Name: "煎魚", GoodAt: []string{"炸", "煎", "蒸"}}
if v1 == v2 {
fmt.Println("腦子進煎魚了")
return
}
fmt.Println("腦子沒進煎魚")
}
這段程序輸出結果是什么呢?
答案是:
# command-line-arguments
./main.go:15:8: invalid operation: v1 == v2 (struct containing []string cannot be compared)
程序運行就直接報錯,IDE 也提示錯誤,一只煎魚都沒能輸出出來。
例子四
那不同結構體,相同的值內(nèi)容呢,能否進行比較?
第四個例子:
type Value1 struct {
Name string
}
type Value2 struct {
Name string
}
func main() {
v1 := Value1{Name: "煎魚"}
v2 := Value2{Name: "煎魚"}
if v1 == v2 {
fmt.Println("腦子進煎魚了")
return
}
fmt.Println("腦子沒進煎魚")
}
顯然,會直接報錯:
# command-line-arguments
./main.go:18:8: invalid operation: v1 == v2 (mismatched types Value1 and Value2)
那是不是就完全沒法比較了呢?并不,我們可以借助強制轉換來實現(xiàn):
if v1 == Value1(v2) {
fmt.Println("腦子進煎魚了")
return
}
這樣程序就會正常運行,且輸出 “腦子進煎魚了”。當然,若是不可比較類型,依然是不行的。
為什么
為什么 Go 結構體有的比較就是正常,有的就不行,甚至還直接報錯了。難道是有什么 “潛規(guī)則” 嗎?
在 Go 語言中,Go 結構體有時候并不能直接比較,當其基本類型包含:slice、map、function 時,是不能比較的。若強行比較,就會導致出現(xiàn)例子中的直接報錯的情況。
而指針引用,其雖然都是 new(string),從表象來看是一個東西,但其具體返回的地址是不一樣的。
因此若要比較,則需改為:
func main() {
gender := new(string)
v1 := Value{Name: "煎魚", Gender: gender}
v2 := Value{Name: "煎魚", Gender: gender}
...
}
這樣就可以保證兩者的比較。如果我們被迫無奈,被要求一定要用結構體比較怎么辦?
這時候可以使用反射方法 reflect.DeepEqual,如下:
func main() {
v1 := Value{Name: "煎魚", GoodAt: []string{"炸", "煎", "蒸"}}
v2 := Value{Name: "煎魚", GoodAt: []string{"炸", "煎", "蒸"}}
if reflect.DeepEqual(v1, v2) {
fmt.Println("腦子進煎魚了")
return
}
fmt.Println("腦子沒進煎魚")
}
這樣子就能夠正確的比較,輸出結果為 “腦子進煎魚了”。
例子中所用到的反射比較方法 reflect.DeepEqual 常用于判定兩個值是否深度一致,其規(guī)則如下:
相同類型的值是深度相等的,不同類型的值永遠不會深度相等。 當數(shù)組值(array)的對應元素深度相等時,數(shù)組值是深度相等的。 當結構體(struct)值如果其對應的字段(包括導出和未導出的字段)都是深度相等的,則該值是深度相等的。 當函數(shù)(func)值如果都是零,則是深度相等;否則就不是深度相等。 當接口(interface)值如果持有深度相等的具體值,則深度相等。 ...
更具體的大家可到 golang.org/pkg/reflect/#DeepEqual 進行詳細查看:

該方法對 Go 語言中的各種類型都進行了兼容處理和判別,由于這不是本文的重點,因此就不進一步展開了。
總結
在本文中,我們針對 Go 語言的結構體(struct)是否能夠比較進行了具體例子的展開和說明。
其本質(zhì)上還是對 Go 語言基本數(shù)據(jù)類型的理解問題,算是變形到結構體中的具體進一步拓展。
不知道你有沒有在 Go 結構體吃過什么虧呢,歡迎在下方評論區(qū)留言和我們一起交流和討論。
?? 點擊關注煎魚,在知識的海洋里遨游
