這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
首先區分幾個概念:變數可比較,可排序,可賦值
可賦值
規範裡面對賦值是這麼定義的:https://golang.org/ref/spec#Assignability
A value x is assignable to a variable of type T ("x is assignable to T") in any of these cases:
- x's type is identical to T.
- x's type V and T have identical underlying types and at least one of V or T is not a defined type.
- T is an interface type and x implements T.
- x is a bidirectional channel value, T is a channel type, x's type V and T have identical element types, and at least one of V or T is not a defined type.
- x is the predeclared identifier nil and T is a pointer, function, slice, map, channel, or interface type.
- x is an untyped constant representable by a value of type T.
概括起來就是他們的類型需要滿足某種條件,或者類型相同,或者底層類型(underlying types)相同。
可比較
規範裡面對比較操作是這麼定義的:https://golang.org/ref/spec#Comparison_operators
可比較又可以分為兩個小類
- 可比較,包括相等(==),和不相等(!=)
- 可排序,包括大於(>),大於等於(>=),小於(>),小於等於(<=)
可排序的一定是可比較的,反之不成立,即可比較的不一定是可排序的,例如struct類型就是可比較的,但不可排序。
- 可排序的資料類型有三種,Integer,Floating-point,和String
- 可比較的資料類型除了上述三種外,還有Boolean,Complex,Pointer,Channel,Interface,Struct,和Array
- 不可比較的資料類型包括,Slice, Map, 和Function
上述規範裡面對哪種資料類型如何進行比較,如何相等都做了描述,不細說,請參考原文。
至於如何定義他們相等的規則,也請參考上述規範文檔。
可賦值和可比較的關係
規範裡是這麼說的:
In any comparison, the first operand must be assignable to the type of the second operand, or vice versa.
也就是說如果兩個變數可比較,那麼他們必然是可賦值的,要麼左邊變數可賦值給右邊變數,要麼右邊變數可賦值給左邊變數。反之則不一定,即可賦值的變數,不一定可比較,比如前面提到的map類型變數。
所以兩個可比較的變數,也必須滿足他們或者類型相同,或者他們的底層類型(underlying types)相同。
兩個變數是否可比較這個規則是在編譯的時候由編譯器負責靜態檢查的。
舉例struct類型的比較
基本類型變數的比較很直觀,不在展開討論,這裡我們舉幾個struct類型的比較的例子來說明struct的比較。
注意這裡指的是相等比較,而不是排序比較,因為struct不是可排序的。
規範裡面對struct比較的規則定義:
Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.
例子1:類型是否相同問題
package mainimport "fmt"type T1 struct { name string }type T2 struct { name string }func main() { v11 := T1 { "foo" } v12 := T1 { "foo" } v21 := T2 { "foo" } v22 := T2 { "foo" } fmt.Printf("v11 == v12 is %v\n", v11 == v12) // output: v11 == v12 is true //fmt.Printf("v11 == v21 is %v\n", v11 == v21) // compile error, invalid operation: v11 == v21 (mismatched types T1 and T2) //fmt.Printf("v11 == v22 is %v\n", v11 == v22) // compile error, invalid operation: v11 == v22 (mismatched types T1 and T2) //fmt.Printf("v12 == v21 is %v\n", v12 == v21) // compile error, invalid operation: v12 == v21 (mismatched types T1 and T2) //fmt.Printf("v12 == v22 is %v\n", v12 == v22) // compile error, invalid operation: v12 == v22 (mismatched types T1 and T2) fmt.Printf("v21 == v22 is %v\n", v21 == v22) // output: v21 == v22 is true}
這個例子說明,struct類型不相同時,他們是不可進行比較的,編譯器在編譯的時候靜態檢查類型;此例中變數v1x和v2x的類型不相同,一個是T1,另一個是T2,所以他們不能進行比較,雖然他們的內部底層類型一樣,因為T1和T2的定義內容是一樣的,但是go認定他們是不同的類型。
因為這違背了可比較的第一個限定條件,即變數必須是可賦值的;T1和T2不是可相互賦值的類型。
關於類型相同判斷的問題,再舉一個例子:
package mainimport "fmt" type Int intfunc main() { var v11 int = 1 var v12 int = 1 var v21 Int = 1 var v22 Int = 1 fmt.Printf("v11 == v12 is %v\n", v11 == v12) // output: v11 == v12 is true //fmt.Printf("v11 == v21 is %v\n", v11 == v21) // compile error, invalid operation: v11 == v21 (mismatched types int and Int) //fmt.Printf("v11 == v22 is %v\n", v11 == v22) // compile error, invalid operation: v11 == v22 (mismatched types int and Int) //fmt.Printf("v12 == v21 is %v\n", v12 == v21) // compile error, invalid operation: v12 == v21 (mismatched types int and Int) //fmt.Printf("v12 == v22 is %v\n", v12 == v22) // compile error, invalid operation: v12 == v22 (mismatched types int and Int) fmt.Printf("v21 == v22 is %v\n", v21 == v22) // output: v21 == v22 is true}
這個例子中我們定義了一種新資料類型Int,雖然實際上他就是int,Int只是int的一個wrapper,go語言還是認為他們是不同的資料類型。
例子2:是否所有的域(field)都可比較
package mainimport "fmt"type T1 struct { name string }type T2 struct { name string; attrs map[string]interface{} }func main() { v11 := T1 { "foo" } v12 := T1 { "foo" } v21 := T2 { "foo", make(map[string]interface{}) } v22 := T2 { "foo", make(map[string]interface{}) } fmt.Printf("v11 == v12 is %v\n", v11 == v12) // output: v11 == v12 is true fmt.Printf("v21 == v22 is %v\n", v21 == v22) // compile error: invalid operation: v21 == v22 (struct containing map[string]interface {} cannot be compared)}
按照規範描述類型T2是否可比較需要它的所有域都是可比較的,這裡因為T2含有一個attrs域,其類型是map,而map是不可比較的,所以T2不可比較。
例子3:包含空域(Blank Field)
package mainimport "fmt"type T1 struct { i int64 j int32 _ int32}// About blank field:// You cannot set or get a blank field; it cannot be refered.// You can't do it in a composite literal either.// The only use for a blank field in a struct is for padding.func main() { v11 := T1 { i:10, j:10 } v12 := T1 { i:10, j:10 } fmt.Printf("v11 == v12 is %v\n", v11 == v12) // output: v11 == v12 is true}
這個例子使用了blank field,可見struct在比較的時候是丟棄blank field的,不管blank field的值是什麼;進而我們猜測,go語言內部比較struct類型的邏輯是遍曆遞迴所有的域,針對每個域分別比較,當所有的遞迴域都返回true時,就返回true,當任何一個返回false時,就返回false;可見struct並不是比較對象地址,也不是比較對象記憶體塊值,而是一個一個域遍曆遞迴比較的,而blank field不可以引用,因而不參與比較。
例子4:匿名型別比較
go語言定義了兩種類型:命名類型,和匿名型別。
package mainimport "fmt"import "reflect"type T1 struct { name string }type T2 struct { name string }func main() { v1 := T1 { "foo" } v2 := T2 { "foo" } v3 := struct{ name string } {"foo"} v4 := struct{ name string } {"foo"} fmt.Println("v1: type=", reflect.TypeOf(v1), "value=", reflect.ValueOf(v1)) // v1: type= main.T1 value= {foo} fmt.Println("v2: type=", reflect.TypeOf(v2), "value=", reflect.ValueOf(v2)) // v2: type= main.T2 value= {foo} fmt.Println("v3: type=", reflect.TypeOf(v3), "value=", reflect.ValueOf(v3)) // v3: type= struct { name string } value= {foo} fmt.Println("v4: type=", reflect.TypeOf(v4), "value=", reflect.ValueOf(v4)) // v4: type= struct { name string } value= {foo} //fmt.Println(v1 == v2) // compiler error: invalid operation: v1 == v2 (mismatched types T1 and T2) fmt.Println(v1 == v3) // true, why? their type is different fmt.Println(v2 == v3) // true, why? fmt.Println(v3 == v4) // true}
這個地方比較好理解的是v1和v2是不同的類型,一個是T1一個是T2,前面我們講過雖然T1和T2底層類型一樣,但是go認為他們就是不同的類型。
然後v3和v4也好理解,他們的類型是一樣的匿名型別。
不好理解的是v1和v3,v2和v3明明他們的類型是不一樣的,為什麼輸出true呢?
要回答這個問題,我們還是回到規範定義上面
Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.
關於struct是否可比較,只看一點,是不是他的所有域都是可比較的,在這個例子總,只有一個域即name string,它是可比較的,所以這一條是滿足的,即此struct是可比較的。
再看規範裡的另一條定義,這條定義是針對通用變數的,不只是struct
In any comparison, the first operand must be assignable to the type of the second operand, or vice versa.
只有這條規則也能滿足的時候,兩個變數才可以比較;在我們例子中v1和v2就不滿足這條,所有不可比較,而v3和v4是滿足這條的,所有v3和v4是可比較的。
總結:struct的比較
struct的比較只需要滿足兩個條件:
- 從所有比較操作繼承下來的規則,即兩個變數必須是可賦值的。
- 針對struct本身的規則,即struct的所有域必須都是可比較的;注意這裡並不管struct本身的定義類型。
只要滿足這兩個條件,struct就是可比較的;可見並沒有限定兩個struct的類型必須一致,從而解釋了命名類型和匿名型別struct的比較規則,就是它並不管名字,反之都是struct類型就行。