码农桃花源
GitHub
知乎
掘金
博客园
Search…
码农桃花源
README
channel
map
interface
标准库
goroutine 调度器
编译和链接
反射
Go 语言中反射有哪些应用
什么情况下需要使用反射
如何比较两个对象完全相同
Go 语言如何实现反射
什么是反射
数组和切片
GC
Powered By
GitBook
如何比较两个对象完全相同
Go 语言中提供了一个函数可以完成此项功能:
1
func
DeepEqual
(
x
,
y
interface
{})
bool
Copied!
DeepEqual
函数的参数是两个
interface
,实际上也就是可以输入任意类型,输出 true 或者 flase 表示输入的两个变量是否是“深度”相等。
先明白一点,如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是“深度”相等。
1
type
MyInt
int
2
type
YourInt
int
3
4
func
main
()
{
5
m
:=
MyInt
(
1
)
6
y
:=
YourInt
(
1
)
7
8
fmt
.
Println
(
reflect
.
DeepEqual
(
m
,
y
))
// false
9
}
Copied!
上面的代码中,m, y 底层都是 int,而且值都是 1,但是两者静态类型不同,前者是
MyInt
,后者是
YourInt
,因此两者不是“深度”相等。
在源码里,有对 DeepEqual 函数的非常清楚地注释,列举了不同类型,DeepEqual 的比较情形,这里做一个总结:
类型
深度相等情形
Array
相同索引处的元素“深度”相等
Struct
相应字段,包含导出和不导出,“深度”相等
Func
只有两者都是 nil 时
Interface
两者存储的具体值“深度”相等
Map
1、都为 nil;2、非空、长度相等,指向同一个 map 实体对象,或者相应的 key 指向的 value “深度”相等
Pointer
1、使用 == 比较的结果相等;2、指向的实体“深度”相等
Slice
1、都为 nil;2、非空、长度相等,首元素指向同一个底层数组的相同元素,即 &x[0] == &y[0] 或者 相同索引处的元素“深度”相等
numbers, bools, strings, and channels
使用 == 比较的结果为真
一般情况下,DeepEqual 的实现只需要递归地调用 == 就可以比较两个变量是否是真的“深度”相等。
但是,有一些异常情况:比如 func 类型是不可比较的类型,只有在两个 func 类型都是 nil 的情况下,才是“深度”相等;float 类型,由于精度的原因,也是不能使用 == 比较的;包含 func 类型或者 float 类型的 struct, interface, array 等。
对于指针而言,当两个值相等的指针就是“深度”相等,因为两者指向的内容是相等的,即使两者指向的是 func 类型或者 float 类型,这种情况下不关心指针所指向的内容。
同样,对于指向相同 slice, map 的两个变量也是“深度”相等的,不关心 slice, map 具体的内容。
对于“有环”的类型,比如循环链表,比较两者是否“深度”相等的过程中,需要对已比较的内容作一个标记,一旦发现两个指针之前比较过,立即停止比较,并判定二者是深度相等的。这样做的原因是,及时停止比较,避免陷入无限循环。
来看源码:
1
func
DeepEqual
(
x
,
y
interface
{})
bool
{
2
if
x
==
nil
||
y
==
nil
{
3
return
x
==
y
4
}
5
v1
:=
ValueOf
(
x
)
6
v2
:=
ValueOf
(
y
)
7
if
v1
.
Type
()
!=
v2
.
Type
()
{
8
return
false
9
}
10
return
deepValueEqual
(
v1
,
v2
,
make
(
map
[
visit
]
bool
),
0
)
11
}
Copied!
首先查看两者是否有一个是 nil 的情况,这种情况下,只有两者都是 nil,函数才会返回 true
接着,使用反射,获取x,y 的反射对象,并且立即比较两者的类型,根据前面的内容,这里实际上是动态类型,如果类型不同,直接返回 false。
最后,最核心的内容在子函数
deepValueEqual
中。
代码比较长,思路却比较简单清晰:核心是一个 switch 语句,识别输入参数的不同类型,分别递归调用 deepValueEqual 函数,一直递归到最基本的数据类型,比较 int,string 等可以直接得出 true 或者 false,再一层层地返回,最终得到“深度”相等的比较结果。
实际上,各种类型的比较套路比较相似,这里就直接节选一个稍微复杂一点的
map
类型的比较:
1
// deepValueEqual 函数
2
// ……
3
4
case
Map
:
5
if
v1
.
IsNil
()
!=
v2
.
IsNil
()
{
6
return
false
7
}
8
if
v1
.
Len
()
!=
v2
.
Len
()
{
9
return
false
10
}
11
if
v1
.
Pointer
()
==
v2
.
Pointer
()
{
12
return
true
13
}
14
for
_
,
k
:=
range
v1
.
MapKeys
()
{
15
val1
:=
v1
.
MapIndex
(
k
)
16
val2
:=
v2
.
MapIndex
(
k
)
17
if
!
val1
.
IsValid
()
||
!
val2
.
IsValid
()
||
!
deepValueEqual
(
v1
.
MapIndex
(
k
),
v2
.
MapIndex
(
k
),
visited
,
depth
+
1
)
{
18
return
false
19
}
20
}
21
return
true
22
23
// ……
Copied!
和前文总结的表格里,比较 map 是否相等的思路比较一致,也不需要多说什么。说明一点,
visited
是一个 map,记录递归过程中,比较过的“对”:
1
type
visit
struct
{
2
a1 unsafe
.
Pointer
3
a2 unsafe
.
Pointer
4
typ Type
5
}
6
7
map
[
visit
]
bool
Copied!
比较过程中,一旦发现比较的“对”,已经在 map 里出现过的话,直接判定“深度”比较结果的是
true
。
Previous
什么情况下需要使用反射
Next
Go 语言如何实现反射
Last modified
2yr ago
Copy link