- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
写这篇文章的时候,已经离我找工作有一段时间了,但是觉得这道题不管是面试还是日常的工作中,都会经常遇到,所以还是特意写一篇文章,记录下自己对Golang中 == 的理解。如文章中出现不对的地方,请不吝赐教,谢谢.
注意,以下文章内容是基于 go1.16.4 进行演示的,如果和你验证时,结果不一致,可能 Go 的判断规则有所改变.
大家可以先不看结果,想想答案,再看后面的结果以及相关的分析.
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 中的 == ,有较为详细的介绍.
Golang中的数据类型分为 4大类 ,他们分别是:
int/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64/byte/rune
等)、浮点数( float32/float64
)、复数类型( complex64/complex128
)、字符串( string
)、布尔(true/false)。这些是Go语言内置的基本数据类型,它们是Go语言的原始数据类型,不能再细分。 数组、结构体
。复合类型允许将多个值组合成一个新的数据结构。 指针、切片(slice)、映射(map)、通道(channel)、函数类型(func)
。引用类型允许在函数间共享和修改数据。 error
。 其实接口类型可以看作是引用类型,在 Go 中,接口类型是一种特殊的引用类型,它包含一个指向实际数据的指针以及类型信息。当你将一个具体类型的值赋给接口变量时,接口会存储一个指向实际数据的指针或实际数据的拷贝。因此,接口可以看作是对其他类型的引用,而不是直接包含实际数据.
在Go语言中,自定义类型属于 基本类型 的概念中.
自定义类型属于基本类型的一种,它通过使用 type 关键字来创建新的类型,底层使用基本数据类型。通过自定义类型,我们可以为基本类型赋予更多语义,并且可以为它们定义自己的方法。自定义类型和其他基本类型具有相同的操作和运算规则,但在类型系统中它们是不同的类型.
例如使用 type number int64 时,我们自定义了一种数据类型,叫做 number 。虽然它底层使用了 int64 ,但在类型系统中, number 和 float64 是不同的类型.
在Go语言中,还有一种 类型别名 的叫法,是 Go1.9 引用的新功能.
类型别名规定: TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。 例如:
type byte = uint8
type rune = int32
== 操作最重要的一个前提是:两个操作数类型必须相同!!! 。
golang 的类型系统非常严格,没有 C/C++/python 中的隐式类型转换。这个需要注意.
基本类型的比较,就比较简单直观,直接使用 == 判断就好了,注意的是 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
}
合类型也叫做聚合类型。golang 中的复合类型只有两种: 数组和结构体 。它们是逐元素/字段比较的.
注意: 数组的长度视为类型的一部分,长度不同的两个数组是不同的类型,不能直接比较 .
注意:如包含了不支持直接使用 == 符号的类型,在编译阶段会报错.
例如:
package main
import "fmt"
type Student struct {
Name string
Age int
Sex bool
}
type S1 struct {
Name string
Scores []int8 // 注意这里定义的是 slice 类型
}
type ITest interface{}
func main() {
arrayA := [...]int64{2, 3, 4}
arrayB := [...]int64{2, 3, 4}
arrayC := [...]int64{1, 3, 4}
fmt.Println(arrayA == arrayB) // true
fmt.Println(arrayB == arrayC) // false
fmt.Println("-------")
s1 := Student{"xiaoming", 18, false}
s2 := Student{"xiaoming", 18, false}
s3 := Student{"xiaowang", 18, false}
fmt.Println(s1 == s2) // true
fmt.Println(s1 == s3) // false
fmt.Println("-------")
a1 := [...]Student{s1, s2}
// 注意这两个元素!
a2 := [2]Student{s2, s2}
a3 := [2]Student{s2, s3}
fmt.Println(a1 == a2) // true
fmt.Println(a3 == a2) // false
fmt.Println("-------")
var i1 ITest = 23
var i2 ITest = 23
var i3 ITest = "tt"
var i4 ITest = 23
fmt.Println(i1 == i2) // true
fmt.Println(i3 == i4) // false
is1 := [...]ITest{i1, i2}
is2 := [...]ITest{i1, i4}
is3 := [...]ITest{i1, i3}
fmt.Println(is1 == is2) // true
fmt.Println(is1 == is3) // false
fmt.Println("-------")
t1 := S1{"xw", []int8{66, 88}}
t2 := S1{"xw", []int8{66, 88}}
t3 := S1{"xw", []int8{66, 99}}
// 为什么这里会报错呢,因为我们定义的结构体中的 Score 字段是 slice, slice 是不支持使用 == 符号的
// Invalid operation: t1 == t2 (the operator == is not defined on S1)
//fmt.Println(t1 == t2)
// Invalid operation: t1 == t2 (the operator == is not defined on S1)
//fmt.Println(t1 == t3)
// go 中 slice 使用 reflect.DeepEqual 判断是否相等
fmt.Println(reflect.DeepEqual(t1, t2)) // true
fmt.Println(reflect.DeepEqual(t1, t3)) // false
}
引用类型 是指 那些底层数据结构的值是引用地址(指针)的类型 。它们在内存中存储的是 指向实际数据的指针,而不是实际数据本身 。切片、映射、通道和函数都是引用类型,因为它们在底层都使用了指针来引用实际的数据.
引用类型的比较实际判断的是两个变量是不是指向同一份数据,它不会去比较实际指向的数据.
关于引用类型,有几个比较特殊的规定:
nil
值比较。 map
之间不允许比较。 map
只能与 nil
值比较。 函数
之间不允许比较。 函数
只能与 nil
值比较。 接下来我们在仔细看看各个类型的具体介绍.
package main
import (
"fmt"
)
type Student struct {
Name string
Age int
Sex bool
}
func main() {
s1 := &Student{"xiaoming", 18, false}
s2 := &Student{"xiaoming", 18, false}
s3 := s1
fmt.Println(s1 == s2) // false
fmt.Println(s1 == s3) // true
}
s1 和 s2 虽然数据一样,但是他们在内存中的地址并不相等,所以他们是不相等的,s1 和 s3 指向的是同一份内存地址,所以是相等的.
接下来我们再看看 channel 和 函数类型
package main
import "fmt"
type Student struct {
Name string
Age int
Sex bool
}
func main() {
ch1 := make(chan bool, 1)
ch2 := make(chan bool, 1)
ch3 := ch1
fmt.Println(ch1 == ch2) // false
fmt.Println(ch1 == ch3) // true
fmt.Println("-----")
a := TestFunc
b := TestFunc
c := a
// invalid operation: a == b (func can only be compared to nil)
//fmt.Println(a == b)
// invalid operation: a == c (func can only be compared to nil)
//fmt.Println(a == c)
fmt.Println(a) // 0x10a3400
fmt.Println(b) // 0x10a3400
fmt.Println(c) // 0x10a3400
}
func TestFunc() {
}
从上面可以看出来, 函数类型不支持直接判等操作 。原因是:函数类型不支持直接的判等操作是因为函数类型是一种复杂的类型,它包含了函数的签名和实现代码等信息。由于函数可以是闭包,可能捕获了外部变量,因此函数的判等操作会涉及到比较函数的底层实现和捕获的变量等细节,这会导致判等操作的复杂性和不确定性.
所以从中也可以看出来 Go 中判断引用类型是否相等,不是简单的判断变量所在的内存地址是否一致,而是根据相应的类型,有不同的判断规则,这里大家需要注意.
再看看切片。因为切片是引用类型,它可以间接的指向自己。例如:
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)
如果实在是需要判断 slice 中元素是否相等,我们一般是自定义一个 判断函数或者使用 reflect.DeepEqual 函数.
package main
import (
"fmt"
"reflect"
)
func slicesAreEqual(slice1, slice2 []int) bool {
if len(slice1) != len(slice2) {
return false
}
for i := 0; i < len(slice1); i++ {
if slice1[i] != slice2[i] {
return false
}
}
return true
}
func main() {
slice1 := []int{1, 2, 3}
slice2 := []int{1, 2, 3}
slice3 := []int{4, 5, 6}
fmt.Println(reflect.DeepEqual(slice1, slice2)) // 输出: false (reflect.DeepEqual 可以进行值相等判断)
fmt.Println(slicesAreEqual(slice1, slice2)) // 输出: true
fmt.Println(slicesAreEqual(slice1, slice3)) // 输出: false
}
注意,在上面的示例中,我们自定义了 slicesAreEqual 函数来判断两个切片是否拥有相同的元素。这个示例中我们使用了 reflect.DeepEqual 来进行值相等的判断,但是不推荐在切片的值相等判断中使用 reflect.DeepEqual ,因为它会将切片的元素逐个进行深度比较,效率较低,尤其在切片较大时。通常最好手动遍历比较切片的元素.
在 Go 中, map 类型不支持直接的判等操作是因为 map 是一个引用类型,并不存储在变量中的实际数据,而是一个指向底层数据结构的指针。 map 是一种哈希表的实现,它包含了键值对的集合.
在 Go 中, map 是一个引用类型,类似于切片、通道和函数等。当你将一个 map 赋值给另一个变量时,它们引用同一个底层的 map 数据。因此,两个 map 可能引用相同的底层数据,但它们仍然是不同的 map 对象。直接比较两个 map 是否相等,并不能确定它们是否引用同一个底层数据.
如果你需要判断两个 map 是否包含相同的键值对,你可以通过手动遍历比较 map 的键值对来实现。这涉及到比较每个键值对的键和值是否相等.
如果实在是需要判断两个 map 是否相等,我们可以使用自定义函数来判断两个 map 是否包含相同的键值对的示例:
package main
import (
"fmt"
"reflect"
)
func mapsAreEqual(map1, map2 map[string]int) bool {
if len(map1) != len(map2) {
return false
}
for key, value := range map1 {
if map2Value, ok := map2[key]; !ok || map2Value != value {
return false
}
}
return true
}
func main() {
map1 := map[string]int{"a": 1, "b": 2, "c": 3}
map2 := map[string]int{"a": 1, "b": 2, "c": 3}
map3 := map[string]int{"a": 1, "b": 2, "c": 4}
fmt.Println(reflect.DeepEqual(map1, map2)) // 输出: false (reflect.DeepEqual 可以进行值相等判断)
fmt.Println(mapsAreEqual(map1, map2)) // 输出: true
fmt.Println(mapsAreEqual(map1, map3)) // 输出: false
}
在上面的示例中,我们自定义了 mapsAreEqual 函数来判断两个 map 是否包含相同的键值对。请注意,与前面提到的 reflect.DeepEqual 一样,我们也不推荐在 map 的值相等判断中使用 reflect.DeepEqual ,因为它会将 map 的键值对逐个进行深度比较,效率较低,尤其在 map 较大时。通常最好手动遍历比较 map 的键值对.
以下内容来自后面的参考链接 深入理解Go之== ,十分感谢原博文作者.
接口类型的值可以是任意一个实现了该接口的类型值,所以接口值除了需要记录具体 值 之外,还需要记录这个值属于的 类型 。也就是说接口值由“类型”和“值”组成,鉴于这两部分会根据存入值的不同而发生变化,我们称之为接口的 动态类型 和 动态值 .
接口值的比较涉及这两部分的比较,只有当 动态类型完全相同 且动态值相等(动态值使用 == 比较),两个接口值才是相等的.
package main
import "fmt"
func main() {
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 ,故两者不等.
package main
import "fmt"
func main() {
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 .
接口值的比较不要求接口类型(注意不是动态类型)完全相同,只要一个接口可以转化为另一个就可以比较 。例如:
package main
import (
"fmt"
"io"
"os"
)
func main() {
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)
}
type ReadCloser interface {
Reader
Closer
}
r 的类型为 io.Reader 接口, rc 的类型为 io.ReadCloser 接口。查看源码, io.ReadCloser 的定义如下:
io.ReadCloser 可转化为 io.Reader ,故两者可比较.
而 io.Writer 不可转化为 io.Reader ,编译报错.
不可比较性:
前面说过,golang 中的 切片类型、map类型、函数类型(func) 是不可比较的。所有含有切片的类型都是不可比较的。例如:
不可比较性会传递,如果一个结构体由于含有切片字段不可比较,那么将它作为元素的数组不可比较,将它作为字段类型的结构体不可比较 .
package main
import "fmt"
func main() {
type T struct {
a map[string]bool
}
t1 := T{
a: map[string]bool{"ni": true},
}
t2 := T{
a: map[string]bool{"ni": true},
}
// invalid operation: t1 == t2 (struct containing map[string]bool cannot be compared)
fmt.Println(t1 == t2)
type T1 struct {
a func()
}
t3 := T1{
a: func() {},
}
t4 := T1{
a: func() {},
}
// invalid operation: t1 == t2 (struct containing func() cannot be compared)
fmt.Println(t3 == t4)
}
关于引用类型,有几个比较特殊的规定:
nil
值比较。 map
之间不允许比较。 map
只能与 nil
值比较。 函数
之间不允许比较。 函数
只能与 nil
值比较。 参考链接
Go语言基础之接口 。
4、interface 。
深入理解Go之== 。
最后此篇关于从一道面试题来谈谈Golang中的==的文章就讲到这里了,如果你想了解更多关于从一道面试题来谈谈Golang中的==的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
php7发布已有半月,最近有时间了解一下php7的新特性,当然,这个版本最大的特点是性能的提升。在下并非高手,欢迎大家指出错误,同时期待共同交流。 PHP语言一个非常重要的特点就是“弱类型”,它让
前言 最近业务开发中,有遇到我们的项目 app 定位被篡改的情况,在 android 端表现的尤为明显。为了防止这种黑产使用虚拟定位薅羊毛,iOS 也不得不进行虚拟定位的规避。 在做技术调研
2014 年发布的 Kubernetes 在今天俨然已成为容器编排领域的事实标准,相信谈到 Kubernetes 的开发者都会一再复述上述现象。如下图所示,今天的大多数个人或者团队都会选择 Ku
不用程序员操心的堆 —托管堆 程序在计算机上跑着,就难免会占用内存资源来存储在程序运行过程中的数据,我们按照内存资源的存取方式将内存划分为堆内存和栈内存。 栈内存,通常使用的场景是:对
前言 在上一讲 谈谈 Nginx 那点事【一】 中,介绍了Nginx的安装及基本结构,今天将工作中Nginx的一些配置,及常用的场景做一些总结。 这一讲总结的内容主要是关于Nginx服务配置、静态资源
1. this是指当前对象自己。 当在一个类中要明确指出使用对象自己的的变量或函数时就应该加上this引用。如下面这个例子中: &nb
前言 ESLint 在项目中已经是大家见惯不惯的存在,你可能很厌烦动不动跳出来的 ESLint 报错,也可能很享受经过统一校验的工工整整的代码,无论如何,我的意见是,在稍微正式点的项目中都要有
背景 从写 Flutter 第一行程序开始我们就知道在 Dart 的 main 方法中通过调用 runApp 方法把自己编写的 Widget 传递进去,只有这样编译运行后才能得到预期效果。你有
我是一名优秀的程序员,十分优秀!