go语言的比较运算

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

可赋值

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容