概念

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

可赋值

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

两个变量是否可比较这个规则是在编译的时候由编译器负责静态检查的。

string 比较

  1. “==”:逐个比较且不分大小写,相等返回true,不相等返回false

    1
    2
    3
    
    fmt.Println("你好" == "你好")	//true
    fmt.Println("sa" == "sa")	//true
    fmt.Println("SA" == "Sa")	//false
    
  2. strings.EqualFold:区分utf-8字符在忽略大小写的情况下是否相等,相等返回true,不相等返回false

    1
    2
    
    fmt.Println(strings.EqualFold("das","Das"))	//true
    fmt.Println(strings.EqualFold("你好","你好"))	//true
    
  3. strings.Compare:相当于“==”比较,效率更高一些,相等返回0,不相等返回1

    1
    2
    
    fmt.Println(strings.Compare("你好","你好"))	//0
    fmt.Println(strings.Compare("das","Das"))	//1
    

float 比较

golang 支持两种浮点float32和float64,众所众知,涉及浮点数比较或运算是会遇到精度问题,具体要根据golang实现IEEE 754的情况定。

默认情况下,float32精度是小数后7位,float64精度是小数点后15位。

如例1:

float32

1
2
3
4
5
6
7
8
9
    var a float32 = 1.00000001
    var b float32 = 1.000000000001
    var c float32 = 1.0000001
    var d float32 = 1.000000000001

    fmt.Println(a == b) //true
    fmt.Println(a > b)  //false
    fmt.Println(c == d) //false
    fmt.Println(c > d)  //true

float64

1
2
3
4
5
6
7
8
9
    var a float64 = 1.0000000000000001
    var b float64 = 1.000000000000000001
    var c float64 = 1.000000000000001
    var d float64 = 1.0000000000000000001

    fmt.Println(a == b) //true
    fmt.Println(a > b)  //false
    fmt.Println(c == d) //false
    fmt.Println(c > d)  //true

这里写了一个根据精度进行float比较的简单的类,注意最大精度为小数点后15位,超出会丢失精度。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
    "fmt"
    "math"
)

type Floater struct {
    Accuracy float64   //精度,最大为小数点后15位
}
//是否相等
func (f Floater) IsEqual(a, b float64) bool {
    return math.Abs(a-b) < f.Accuracy
}
//0为相等 1为a大于b -1为a小于b
func (f Floater) Bccomp(a, b float64) int8 {
    if math.Abs(a-b) < f.Accuracy {
        return 0
    }
    if math.Max(a, b) == a {
        return 1
    } else {
        return -1
    }
}

func main() {

    f := Floater{Accuracy: 0.000000000001}
    var a float64 = 1.0000000002
    var b float64 = 1.0000000001

    fmt.Println(f.Bccomp(a, b)) //1
    fmt.Println(f.Bccomp(b, a)) //-1
    fmt.Println(f.Bccomp(a, a)) //0
}

指针类型比较

1
2
3
4
a := "hello"
b := &a
c := &a
fmt.Println(b == c)

当变量是相同或者都为nil时,指针值相等。

interface 比较

两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的==操作相等。

在Go语言中,interface的实际实现结构可以理解为下图:

其中type就是它的类型(动态类型),value部分是它的值(动态值)。

一个interface类型的变量 w 为nil,就代表着其动态类型和动态值都为 nil 。考虑这种情况:

图中代表着类型不为空,但是interface的动态值是 nil ,那么这种情况下,如果去判断 w 是否为 nil 时,会得到一个false

1
fmt.Println(w == nil)  //输出 false

要特别注意这钟情况,因为我们可能会犯这样的错误:

1
2
3
if w != nil {
    w.Write([]byte("done!\n")) // 当w的动态值为nil时,会发生panic
}

当interface的动态类型是指针的时候,且其动态值不为 nil 时,我们可以理解为其结构如下图所示。

这里为什么需要特别拿出来说明呢?因为动态类型为指针的interface的动态值保存的就是一个指针值,这个指针指向一块内存。下面以Golang的error接口来说明这个问题。

下面是error包的代码:

1
2
3
4
5
6
7
8
9
type error interface {
    Error() string
}

func New(text string) error { return &errorString{text} }

type errorString struct { text string }

func (e *errorString) Error() string { return e.text }

这里需要理解的:是指针类型 *errorString 实现了error接口,而不是 errorString 。

1
2
3
w1 := errors.New("ERR")
w2 := errors.New("ERR")
fmt.Println(w1 == w2) // 输出false

以 w1 为例子, 由于是指针类型 *errorString 实现了error接口,所以 w1 的动态类型是*errorString,那么 w1 的动态值就是一个指针,w2 也是同理。那么上面的等于(==)比较我们可以用下图和伪代码来理解。

1
w1.type == w2.type && w1.value == w2.value

由于 w1.value 和 w2.value 都是指针类型,它们又分别保存着不同的内存地址,所以他们的比较是得出 false

也正是这种实现,每个New函数的调用都分配了一个独特的和其他错误不相同的实例,这能方便的让我们可以定义自己特定的错误,就如同Golang定义的 io.EOF 一样,不必担心刚好有相同的错误消息:

1
fmt.Println(errors.New("EOF") == io.EOF)  //输出false

如果想要让w1 := errors.New("ERR")w2 := errors.New("ERR")比较指针指向的真实值呢?需要使用reflect.DeepEqual()函数

1
2
3
	w1 := errors.New("ERR")
	w2 := errors.New("ERR")
	fmt.Println(reflect.DeepEqual(w1,w2))

结果返回true

array 比较

两个数组只要他们包括的元素,每个元素的值相同,则他们相等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
	"fmt"
)

func main() {
	var arr = [3]int{1, 2, 3}
	var brr = [3]int{1, 2, 3}

	fmt.Println(arr==brr) //true
}

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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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不是可相互赋值的类型。

关于类型相同判断的问题,再举一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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语言还是认为他们是不同的数据类型。

如果结构体能够通过强制转换成为相同的结构体,那么他们可以比较:w

是否所有的域(field)都可比较

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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不可比较。

包含空域(Blank Field)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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不可以引用,因而不参与比较。

匿名类型比较

go语言定义了两种类型:命名类型,和匿名类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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类型就行

time.time 比较

先把当前时间格式化成相同格式的字符串,然后使用time的Before, After, Equal 方法即可.

1
2
3
4
5
6
7
8
9
time1 := "2015-03-20 08:50:29"
    time2 := "2015-03-21 09:04:25"
    //先把时间字符串格式化成相同的时间类型
    t1, err := time.Parse("2006-01-02 15:04:05", time1)
    t2, err := time.Parse("2006-01-02 15:04:05", time2)
    if err == nil && t1.Before(t2) {
        //处理逻辑
        fmt.Println("true")
    }

DeepEqual

slice/struct/map 这三个都可以用reflect.DeepEqual来判断是否相等

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
    "fmt"
    "reflect"
)

type S struct {
    s string
}

func main() {
    s1 := S{s: "hello"}
    s2 := S{s: "hello"}
    if reflect.DeepEqual(s1, s2) {
        fmt.Println(s1, "==", s2)
    }

    a1 := []int{1, 2}
    a2 := []int{1, 2}
    if reflect.DeepEqual(a1, a2) {
        fmt.Println(a1, "==", a2)
    }

    m1 := map[int]string{1: "a", 2: "b"}
    m2 := map[int]string{1: "a", 2: "b"}
    if reflect.DeepEqual(m1, m2) {
        fmt.Println(m1, "==", m2)
    }
}

但因为reflect.DeepEqual的性能不好,实际上只针对特定环境使用.

参考: https://studygolang.com/articles/11342 https://blog.csdn.net/double_happiness/article/details/80098816 https://www.veaxen.com/golang%E6%8E%A5%E5%8F%A3%E5%80%BC%EF%BC%88interface%EF%BC%89%E7%9A%84%E6%AF%94%E8%BE%83%E6%93%8D%E4%BD%9C%E5%88%86%E6%9E%90.html