go语言的比较运算

CodeGeek · · 7953 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

首先区分几个概念:变量可比较,可排序,可赋值

可赋值

规范里面对赋值是这么定义的: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

可比较又可以分为两个小类

  1. 可比较,包括相等(==),和不相等(!=)
  2. 可排序,包括大于(>),大于等于(>=),小于(>),小于等于(<=)

可排序的一定是可比较的,反之不成立,即可比较的不一定是可排序的,例如struct类型就是可比较的,但不可排序。

  1. 可排序的数据类型有三种,Integer,Floating-point,和String
  2. 可比较的数据类型除了上述三种外,还有Boolean,Complex,Pointer,Channel,Interface,Struct,和Array
  3. 不可比较的数据类型包括,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 main

import "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 main

import "fmt"
 
type Int int

func 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 main

import "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 main

import "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 main

import "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的比较只需要满足两个条件:

  1. 从所有比较操作继承下来的规则,即两个变量必须是可赋值的。
  2. 针对struct本身的规则,即struct的所有域必须都是可比较的;注意这里并不管struct本身的定义类型。

只要满足这两个条件,struct就是可比较的;可见并没有限定两个struct的类型必须一致,从而解释了命名类型和匿名类型struct的比较规则,就是它并不管名字,反之都是struct类型就行。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:CodeGeek

查看原文:go语言的比较运算

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

7953 次点击  ∙  1 赞  
加入收藏 微博
下一篇:gRPC
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传