相信==
判等操做,你们天天都在用。以前在论坛上看到很多人在问 golang ==
比较的结果。看到不少人对 golang 中==
的结果不太了解。确实,golang 中对==
的处理有一些细节的地方须要特别注意。虽然平时可能不太会遇到,可是碰到了就是大坑。本文将对 golang 中==
操做作一个系统的介绍。但愿能对你们有所帮助。git
golang 中的数据类型能够分为如下 4 大类:github
int/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64/byte/rune
等)、浮点数(float32/float64
)、复数类型(complex64/complex128
)、字符串(string
)。error
。==
操做最重要的一个前提是:两个操做数类型必须相同!类型必须相同!类型必须相同!golang
若是类型不一样,那么编译时就会报错。编程
注意:数组
C/C++
中的隐式类型转换。虽然写起来稍微有些麻烦,可是能避免从此很是多的麻烦!!!type
定义新类型。新定义的类型与底层类型不一样,不能直接比较。为了更容易看出类型,示例代码中的变量定义都显式指定了类型。bash
看下面的代码:编程语言
package main
import "fmt"
func main() {
var a int8
var b int16
// 编译错误:invalid operation a == b (mismatched types int8 and int16)
fmt.Println(a == b)
}
复制代码
没有隐式类型转换。ui
package main
import "fmt"
func main() {
type int8 myint8
var a int8
var b myint8
// 编译错误:invalid operation a == b (mismatched types int8 and myint8)
fmt.Println(a == b)
}
复制代码
虽然myint8的底层类型是int8,可是他们是不一样的类型。spa
下面依次经过这 4 种类型来讲明==
是如何作比较的。设计
这是最简单的一种类型。比较操做也很简单,直接比较值是否相等。没啥好说的,直接看例子。
var a uint32 = 10
var b uint32 = 20
var c uint32 = 10
fmt.Println(a == b) // false
fmt.Println(a == c) // true
复制代码
有一点须要注意,浮点数的比较问题:
var a float64 = 0.1
var b float64 = 0.2
var c float64 = 0.3
fmt.Println(a + b == c) // false
复制代码
由于计算机中,有些浮点数不能精确表示,浮点运算结果会有偏差。若是咱们分别输出a+b
和c
的值,会发现它们确实是不一样的:
fmt.Println(a + b)
fmt.Println(c)
// 0.30000000000000004
// 0.3
复制代码
这个问题不是 golang 独有的,只要浮点数遵循 IEEE 754 标准的编程语言都有这个问题。须要特别注意,尽可能不要作浮点数比较,确实须要比较时,计算两个浮点数的差的绝对值,若是小于必定的值就认为它们相等,好比1e-9
。
复合类型也叫作聚合类型。golang 中的复合类型只有两种:数组和结构体。它们是逐元素/字段比较的。
注意:数组的长度视为类型的一部分,长度不一样的两个数组是不一样的类型,不能直接比较。
例如:
a := [4]int{1, 2, 3, 4}
b := [4]int{1, 2, 3, 4}
c := [4]int{1, 3, 4, 5}
fmt.Println(a == b) // true
fmt.Println(a == c) // false
type A struct {
a int
b string
}
aa := A { a : 1, b : "test1" }
bb := A { a : 1, b : "test1" }
cc := A { a : 1, b : "test2" }
fmt.Println(aa == bb)
fmt.Println(aa == cc)
复制代码
引用类型是间接指向它所引用的数据的,保存的是数据的地址。引用类型的比较实际判断的是两个变量是否是指向同一份数据,它不会去比较实际指向的数据。
例如:
type A struct {
a int
b string
}
aa := &A { a : 1, b : "test1" }
bb := &A { a : 1, b : "test1" }
cc := aa
fmt.Println(aa == bb)
fmt.Println(aa == cc)
复制代码
由于aa
和bb
指向的两个不一样的结构体,虽然它们指向的值是相等的(见上面复合类型的比较),可是它们不等。 aa
和cc
指向相同的结构体,因此它们相等。
再看看channel
的比较:
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch3 := ch1
fmt.Println(ch1 == ch2)
fmt.Println(ch1 == ch3)
复制代码
ch1
和ch2
虽然类型相同,可是指向不一样的channel
,因此它们不等。 ch1
和ch3
指向相同的channel
,因此它们相等。
关于引用类型,有两个比较特殊的规定:
nil
值比较。map
之间不容许比较。map
只能与nil
值比较。为何要作这样的规定?咱们先来讲切片。由于切片是引用类型,它能够间接的指向本身。例如:
a := []interface{}{ 1, 2.0 }
a[1] = a
fmt.Println(a)
// !!!
// runtime: goroutine stack exceeds 1000000000-byte limit
// fatal error: stack overflow
复制代码
上面代码将a
赋值给a[1]
致使递归引用,fmt.Println(a)
语句直接爆栈。
基于上面两点缘由,golang 直接规定切片类型不可比较。使用==
比较切片直接编译报错。
例如:
var a []int
var b []int
// invalid operation: a == b (slice can only be compared to nil)
fmt.Println(a == b)
复制代码
错误信息很明确。
由于map
的值类型可能为不可比较类型(见下面,切片是不可比较类型),因此map
类型也不可比较🤣。
接口类型是 golang 中比较重要的一种类型。接口类型的值,咱们称为接口值。一个接口值是由两个部分组成的,具体类型(即该接口存储的值的类型)和该类型的一个值。引用《go 程序设计语言》的名称,分别称为动态类型和动态值。接口值的比较涉及这两部分的比较,只有当动态类型彻底相同且动态值相等(动态值使用==
比较),两个接口值才是相等的。
例如:
var a interface{} = 1
var b interface{} = 2
var c interface{} = 1
var d interface{} = 1.0
fmt.Println(a == b) // false
fmt.Println(a == c) // true
fmt.Println(a == d) // false
复制代码
a
和b
动态类型相同(都是int
),动态值也相同(都是1
,基本类型比较),故二者相等。 a
和c
动态类型相同,动态值不等(分别为1
和2
,基本类型比较),故二者不等。 a
和d
动态类型不一样,a
为int
,d
为float64
,故二者不等。
type A struct {
a int
b string
}
var aa interface{} = A { a: 1, b: "test" }
var bb interface{} = A { a: 1, b: "test" }
var cc interface{} = A { a: 2, b: "test" }
fmt.Println(aa == bb) // true
fmt.Println(aa == cc) // false
var dd interface{} = &A { a: 1, b: "test" }
var ee interface{} = &A { a: 1, b: "test" }
fmt.Println(dd == ee) // false
复制代码
aa
和bb
动态类型相同(都是A
),动态值也相同(结构体A
,见上面复合类型的比较规则),故二者相等。 aa
和cc
动态类型相同,动态值不一样,故二者不等。 dd
和ee
动态类型相同(都是*A
),动态值使用指针(引用)类型的比较,因为不是指向同一个地址,故不等。
注意:
若是接口的动态值不可比较,强行比较会panic
!!!
var a interface{} = []int{1, 2, 3, 4}
var b interface{} = []int{1, 2, 3, 4}
// panic: runtime error: comparing uncomparable type []int
fmt.Println(a == b)
复制代码
a
和b
的动态值是切片类型,而切片类型不可比较,因此a == b
会panic
。
接口值的比较不要求接口类型(注意不是动态类型)彻底相同,只要一个接口能够转化为另外一个就能够比较。例如:
var f *os.File
var r io.Reader = f
var rc io.ReadCloser = f
fmt.Println(r == rc) // true
var w io.Writer = f
// invalid operation: r == w (mismatched types io.Reader and io.Writer)
fmt.Println(r == w)
复制代码
r
的类型为io.Reader
接口,rc
的类型为io.ReadCloser
接口。查看源码,io.ReadCloser
的定义以下:
type ReadCloser interface {
Reader
Closer
}
复制代码
io.ReadCloser
可转化为io.Reader
,故二者可比较。
而io.Writer
不可转化为io.Reader
,编译报错。
type
定义的类型使用type
能够基于现有类型定义新的类型。新类型会根据它们的底层类型来比较。例如:
type myint int
var a myint = 10
var b myint = 20
var c myint = 10
fmt.Println(a == b) // false
fmt.Println(a == c) // true
type arr4 [4]int
var aa arr4 = [4]int{1, 2, 3, 4}
var bb arr4 = [4]int{1, 2, 3, 4}
var cc arr4 = [4]int{1, 2, 3, 5}
fmt.Println(aa == bb)
fmt.Println(aa == cc)
复制代码
myint
根据底层类型int
来比较。 arr4
根据底层类型[4]int
来比较。
前面说过,golang 中的切片类型是不可比较的。全部含有切片的类型都是不可比较的。例如:
不可比较性会传递,若是一个结构体因为含有切片字段不可比较,那么将它做为元素的数组不可比较,将它做为字段类型的结构体不可比较。
map
因为map
的key
是使用==
来判等的,因此全部不可比较的类型都不能做为map
的key
。例如:
// invalid map key type []int
m1 := make(map[[]int]int)
type A struct {
a []int
b string
}
// invalid map key type A
m2 := make(map[A]int)
复制代码
因为切片类型不可比较,不能做为map
的key
,编译时m1 := make(map[[]int]int)
报错。 因为结构体A
含有切片字段,不可比较,不能做为map
的key
,编译报错。
本文详尽介绍了 golang 中==
操做的细节,但愿能对你们有所帮助。