写这篇文章的时候,已经离我找工作有一段时间了,但是觉得这道题不管是面试还是日常的工作中,都会经常遇到,所以还是特意写一篇文章,记录下自己对Golang中==
的理解。如文章中出现不对的地方,请不吝赐教,谢谢。
注意,以下文章内容是基于 go1.16.4 进行演示的,如果和你验证时,结果不一致,可能 Go 的判断规则有所改变。
1、面试题
大家可以先不看结果,想想答案,再看后面的结果以及相关的分析。
type T interface {
}
func main() {
var (
t T
p *T
i1 interface{} = t
i2 interface{} = p
)
fmt.Println(i1 ==t, i1 == nil)
fmt.Println(i2 ==p, i2 == nil)
fmt.Println(t == nil)
fmt.Println(p == nil)
}
执行结果:
true true
true false
true
true
分析:
1、interface 值由动态类型
和动态值
组成。只有在类型
和值
都相同时才相等。接口变量i1
是接口类型的零值,也就是它的类型和值部分都是nil
,接口变量i2
的动态值虽然是零值,但是动态类型为 *T
。
2、变量 t、p 都没有初始化,未分配内存,所以 变量t、p 都等于 nil。
对于上面的描述不太清楚的同学,不用着急,我们一起来学习 Golang 中的
==
,有较为详细的介绍。
2、Golang中的数据类型
Golang中的数据类型分为4大类
,他们分别是:
- 基本类型 (Primary types): 整型(
int/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64/byte/rune
等)、浮点数(float32/float64
)、复数类型(complex64/complex128
)、字符串(string
)、布尔(true/false)。这些是Go语言内置的基本数据类型,它们是Go语言的原始数据类型,不能再细分。 - 复合类型 (Composite types):又叫聚合类型。包括
数组、结构体
。复合类型允许将多个值组合成一个新的数据结构。 - 引用类型 (Reference types):这些类型在内存中存储的是数据的地址,包括
指针、切片(slice)、映射(map)、通道(channel)、函数类型(func)
。引用类型允许在函数间共享和修改数据。 - 接口类型 (Interface types):接口类型是一种抽象类型,它定义了对象的行为,而不关心对象的具体类型。通过实现接口,可以实现多态性和代码复用。比如
error
。
其实接口类型可以看作是引用类型,在 Go 中,接口类型是一种特殊的引用类型,它包含一个指向实际数据的指针以及类型信息。当你将一个具体类型的值赋给接口变量时,接口会存储一个指向实际数据的指针或实际数据的拷贝。因此,接口可以看作是对其他类型的引用,而不是直接包含实际数据。
在Go语言中,自定义类型属于基本类型
的概念中。
自定义类型属于基本类型的一种,它通过使用 type 关键字来创建新的类型,底层使用基本数据类型。通过自定义类型,我们可以为基本类型赋予更多语义,并且可以为它们定义自己的方法。自定义类型和其他基本类型具有相同的操作和运算规则,但在类型系统中它们是不同的类型。
例如使用
type number int64
时,我们自定义了一种数据类型,叫做number
。虽然它底层使用了int64
,但在类型系统中,number
和float64
是不同的类型。
在Go语言中,还有一种类型别名
的叫法,是 Go1.9 引用的新功能。
类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。
例如:
type byte = uint8
type rune = int32
==
操作最重要的一个前提是:两个操作数类型必须相同!!!
3、四大类型如何使用 ==
3.1、基本类型
基本类型的比较,就比较简单直观,直接使用==
判断就好了,注意的是Go中并没有隐式转换,而且类型一致才可以
。
package main
import "fmt"
func main() {
var a int64
var b int64
var c int32
fmt.Println(a == b)
fmt.Println(c)
// Invalid operation: a == c (mismatched types int64 and int32)
//fmt.Println(a == c)
}
接下来我们看看浮点数的比较:
package main
import "fmt"
func main() {
var a float64 = 0.1
var b float64 = 0.2
var c float64 = 0.3
fmt.Println(a + b) // 0.30000000000000004
fmt.Println(a+b == c) // false
}
是不是有点小惊讶,这个是因为Go 中的 浮点数遵循 IEEE 754 标准,所以会有有些浮点数不能精确表示,浮点运算结果会有误差。
想大概了解计算机是如何表示浮点数的可以看看下面的文章,有一个基础的了解。
注意:
浮点数做 判等 操作一般是使用 计算两个浮点数的差的绝对值,如果小于一定的值就认为它们相等,比如
1e-9
。
package main
import (
"fmt"
"math"
)
func main() {
var a = 0.1
var b = 0.2
var c = 0.3
fmt.Println(a + b) // 0.30000000000000004
fmt.Println(math.Abs((a+b)-c) < 1e-9) // true
fmt.Printf("%T", a) // float64
}
3.2、复合类型
合类型也叫做聚合类型。golang 中的复合类型只有两种:数组和结构体
。它们是逐元素/字段比较的。
注意:数组的长度视为类型的一部分,长度不同的两个数组是不同的类型,不能直接比较。
- 对于数组来说,依次比较各个元素的值。根据元素类型的不同,再依据是基本类型、复合类型、引用类型或接口类型,按照特定类型的规则进行比较。所有元素全都相等,数组才是相等的。
- 对于结构体来说,依次比较各个字段的值。根据字段类型的不同,再依据是 4 中类型中的哪一种,按照特定类型的规则进行比较。所有字段全都相等,结构体才是相等的。
注意:如包含了不支持直接使用 == 符号的类型,在编译阶段会报错。
例如:
package main
import "fmt"
type Student struct {
Name string
Age int
Sex bool
}
type S1 struct {
Name string
Scores []int8 // 注意这里定义的是 slice 类型
}
type ITest interface{}