- 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的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试运行这段代码,用随机数替换字符串中的一个字符: //Get the position between 0 and the length of the string-1 to insert
我有一个包含 3 个位置的数组,假设它的所有位置都是数字 5。 [5 5 5] 我怎样才能以保持 555 的方式将它传递给 var?就像这样。 n:= 555 最佳答案 与使用任何其他语言的方式相同:
我使用 go dep 工具版本 v0.4.1,现在当我运行 dep init 时它会按预期创建 2 个文件,当我打开 gopkg.lock 我发现例如以下内容 [[projects]] name
我正在制作学习联系申请。我有一个 NewContact()。 // Contact - defines the fields of an entire Contact type Contact str
我一直在尝试使用该模块: https://godoc.org/github.com/hirochachacha/go-smb2#RemoteFile.ReadAt 为了在 Windows 机器上对我的
我需要在 golang 中编译 golang 中的程序。有没有不使用 exec.Command("go","build") 的原生形式? 最佳答案 不幸的是,我认为使用 exec.Command 是利
编写输出有效 go 代码的 go 应用程序可能最好使用内置的“go”包及其一些子包(“go/ast”、“go/token”、“go/printer”、等)。 要创建字符串文字表达式,您需要创建一个 a
我正在尝试使用 Golang 和 gin 为我的 api 和前端编写代理。如果请求转到除“/api”之外的任何内容,我想代理到 svelte 服务器。如果出现“/api/something”,我想在
我偶然发现了这个博客:using go as a scripting language并尝试创建一个可用于运行 golang 脚本的自定义图像,即 FROM golang:1.15 RUN go ge
我刚开始接触golang,我需要从json字符串中获取数据。 {"data" : ["2016-06-21","2016-06-22","2016-06-25"], "sid" : "ab", "di
关闭。这个问题是opinion-based .它目前不接受答案。 想要改进这个问题? 更新问题,以便 editing this post 可以用事实和引用来回答它. 关闭 3 年前。 Improve
我是 goland 的新手,试图在我的第一个项目中使用它。我注意到在 goland 中它没有显示通过容器引入的相同 golang SDK。 这是我的 Dockerfile: FROM golang:1
我正在试用 golang-neo4j-bolt-driver 包 github.com/johnnadratowski/golang-neo4j-bolt-driver 我已经导入了包并正在使用创建新
如果我安装了Go发行版软件包,则会在/usr/lib/golang/pkg中看到很多文件,在/usr/lib/golang/src中看到非常相似的文件集。这两组之间有什么关系? pkg是从src中的源
我发现 golang 上下文对于在客户端-服务器请求范围内取消服务器的处理很有用。 我可以使用 http.Request.WithContext 方法发出带有上下文的 http 请求,但是如果客户端不
我正在尝试将一个 golang 数组(还有 slice、struct 等)放置到 HTML 中,这样当从 golang gin web 框架返回 HTML 时,我可以在 HTML 元素内容中使用数组元
目前正在使用这个 ffmpeg 命令编辑视频 ffmpeg -i "video1.ts" -c:v libx264 -crf 20 -c:a aac -strict -2 "video1-fix.ts
我需要从 play.golang.org 链接读取 golang 代码并保存到 .go 文件。我想知道 play.golang.org 是否有任何公共(public) API 支持。我用谷歌搜索但没有
我第一次使用 IntelliJ 的最新 (2014-01-03) Golang 插件。 通常,我的终端工作流程是 go build && ./executable -args=1 所以我试图创建一个启
这个问题只是在构建之间随机出现,现在甚至我们的生产 repo,几个月都没有改变,在构建时也会出现这个问题。我已经坚持了一段时间。它不会发生在我们的本地机器上,只有在使用 dockerfile 时才会发
我是一名优秀的程序员,十分优秀!