0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

struct,slice,map是否相等以及几种对比方法的区别

马哥Linux运维 来源:稀土掘金 2024-01-08 18:20 次阅读

一、前言

对比两个struct或者map,slice是否相等是大家经常会有的需求,想必大家也都接触过很多对比的方式,比如==,reflect.DeepEqual(),cmp.Equal()等。

这么多种对比方式,适用场景和优缺点都有哪些呢?为什么可以用==,有的却不可以呢?除了这三个,还有其他的方式可以判断相等吗?问题多多,且一起研究研究。

二、== 的对比方式

1、golang的四大类型

golang中的数据类型可以分为以下 4 大类:


基本类型:整型(
int/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64/byte/rune等)、浮点数(
float32/float64)、复数类型(
complex64/complex128)、字符串(
string)。
复合类型(又叫聚合类型):数组和结构体类型。
引用类型:切片(slice)、map、channel、指针。
接口类型:如error :类型一致且是基本类型,值相等的时候,才能==,非基本类型会panic panic: runtime er
ror: comparing uncomparable type []int

2、== 适用的类型

我们日常开发中,经常见到使用==的类型一般是:string,int等基本类型。struct有时候可以用有时候不可以。slice和map使用 ==会报错.


int1:=10
int2:=10
str1:="11"
str2:="11"
if int1 == int2{}
if str1 == str2{}

int和string是值类型,我们直接对比他们的值。当然,前提是类型要一致,类型不一致编译过不了。

3、slice和map使用 ==

首先golang里面有种说法:


切片之间不允许比较。切片只能与nil值比较。
map之间不允许比较。map只能与nil值比较。

那么我们分别测试下发现:


(1)map比较会报错:map can only be compared to nil
(2)切片报错:the operator == is not defined on []int64
    slice can only be compared to nil

(1)那么两个nil是否可以==比较呢

答案是不能:invalid operation: nil == nil (operator == not defined on nil)

(2)slice,map使用==的场景

就像上面说的,slice和map只能和nil使用==,他们各自之间是不可以的。


s1 := []int64{1, 2}
if s1 == nil {} //编辑器不会提示报错

(3)为什么slice和map不可以

因为slice和map不止是需要比较值,还需要比较len和cap,层级比较深的话还需要递归比较,不是简单的==就可以比较的,具体的我们可以参照reflect.DeepEqual()中实现的切片对比代码。另外有大佬也说会出现循环引用的问题。

4、channel使用 ==

channel是引用类型,对比的是存储数据的地址。channel是可以使用==的,只要类型一样就可以。


ch1 := make(chan int, 1)
ch2 := ch1
if cha2 == cha1{fmt.Println("true")}

5、struct结构体使用==

(1)首先要明确几点:


1)结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存。
实例化就是根据结构体定义的格式创建一份与格式一致的内存区域,结构体实例与实例间的
内存是完全独立的
2)对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作
因此:go中的结构体: v = Struct {}, v = &Struct{} 这个两种写法是等价的

结构体这里比较复杂一些。我们可以先下结论:

1、简单结构的结构体,里面都是值类型或者指针的话,是可以使用 ==的
2、结构体中含有slice或者map,都是不可以用==

(2)简单结构体的==


type Value struct {
    Name   string
    Gender string
    }
 
    func main() {
        v1 := Value{Name: "test", Gender: "男"}
        v2 := Value{Name: "test", Gender: "男"}
        if v1 == v2 {
            fmt.Println("true")
            return
        }
    }

(3)带指针的结构体==

    首先要明确,指针类型指向的地址不一样,肯定是没办法比较的,如果地址一样,那么也可以用==
    首先要明确,指针类型指向的地址不一样,肯定是没办法比较的,如果地址一样,那么也可以用==
    type Value struct {
    Name   string
    Gender *string
    }
 
    func main() {
         Gender :=new(string) //下面赋值用的同一个变量,地址相同
        v1 := Value{Name: "test", Gender: Gender}
        v2 := Value{Name: "test", Gender: Gender}
        if v1 == v2 {
            fmt.Println("true")
            return
        }
    }


(4)强制转换类型的==


type StructA struct {
    Name string
    }
 
    type StructB struct {
        Name string
    }
 
    func main() {
        s1 := StructA{Name: "test1"}
        s2 := StructB{Name: "test1"}
        if s1 == StructA(s2) {
            fmt.Println("true")
            return
        }    
   }

那复杂类型的结构体呢,要如何对比相等?包括slice和map如何对比相等呢?接下里就引入其他的对比方式。

三、reflect.DeepEqual() 和cmp.Equal()

1、reflect.DeepEqual()

reflect包提供的深度对比(递归)的方法,适用于go中的slice,map,struct,function的对比。

(1)对比规则


    相同类型的值是深度相等的,不同类型的值永远不会深度相等。
    当数组值(array)的对应元素深度相等时,数组值是深度相等的。
    当结构体(struct)值如果其对应的字段(包括导出和未导出的字段)都是深度相等的,则该值是深度相等的。
    当函数(func)值如果都是零,则是深度相等;否则就不是深度相等。
    当接口(interface)值如果持有深度相等的具体值,则深度相等。
    当切片(slice)序号相同,如果值,指针都相等,那么就是深度相等的
    当哈希表(map)相同的key,如果值,指针都相等,那么就是深度相等的。

(2)对比实例

通过规则可以知道,reflect.DeepEqual是可以比较struct的,同时也可以用来比较slice和map。
     func main() {
        s1 := StructA{Name: "test", Hobby: []string{"唱", "跳"}}
        s2 := StructA{Name: "test", Hobby: []string{"唱", "跳"}}
        if reflect.DeepEqual(s1, s2) {
            fmt.Println("struct true")
        }
        mp1 := map[int]int{1: 10, 2: 20}
  mp2 := map[int]int{1: 10, 2: 20}
         if ok := reflect.DeepEqual(mp1, mp2);ok {
    fmt.Println("mp1 == mp2!")
      } else {
    fmt.Println("mp1 != mp2!")
      }
    }
  

2、cmp.Equal()

go-cmp是Google开源的比较库,它提供了丰富的选项。

这个包旨在成为reflect.DeepEqual比较两个值在语义上是否相等的更强大和更安全的替代方案。

参考:在测试中使用go-cmp

(1)对比规则:


1.在经过路径过滤,值过滤和类型过滤之后,会生一些忽略、转换、比较选项,如果选项中存在忽略,
则忽略比较,如果转换器比较器的数据大于1,则会panic(因为比较操作不明确)。如果选项中
存在转换器,则调用转换器转换当前值,再递归调用转换器输出类型的Equal。如果包含一个比较器。
则比较使用比较器比较当前值。否则进入下一比较阶段。


2.如果比较值有一个(T) Equal(T) bool 或者 (T) Equal(I) bool,那么,即使x与y是nil,
也会调用x.Equal(y)做为结果。如果不存在这样的方法,则进入下一阶段。


3.在最后阶段,Equal方法尝试比较x与y的基本类型。使用go语言的 == 比较基本类型
(bool, intX, float32,float64, complex32,complex64, string, chan)。


4.在比较struct时,将递归的比较struct的字段。如果结构体包含未导出的字段,函数会panic。
可以通过指定cmpopts.IgnoreUnexported来忽略未导出的字段,也可以使用
cmp.AllowUnexported来指定比较未导出的字段。

(2)代码


// 传入要对比的结构即可
func Equal(x, y interface{}, opts ...Option) bool {
  s := newState(opts)
  s.compareAny(rootStep(x, y))
  return s.result.Equal()
}


//获取diff差异
func Diff(x, y interface{}, opts ...Option) string {
}


3、cmp和DeepEqual的区别?


安全:cmp.Equal()函数不会比较未导出字段(即字段名首字母小写的字段)。遇到未导出字段,
cmp.Equal()直接panic,reflect.DeepEqual()会比较未导出的字段。


强大:cmp.Equal()函数提供丰富的函数参数,让我们可以实现:忽略部分字段,比较零值,
转换某些值,允许误差等。


共同点:两种比较类型,都会比较:对象类型,值,指针地址等。切片会按照索引比较值,
map是按照key相等比较值

四、其他对比方式

1、testify库的 assert.Equal()

参考:Go 每日一库之 testify

我们在写单测的时候,经常会使用assert.Equal()来做对比,原理如下:

(1)[]byte类型就使用bytes.Equal()
(2)非[]byte类型,使用reflect.DeepEqual()
(3)不相等则获取diff

2、bytes.Equal()

标准库中针对特定类型进行比较相等性的函数或方法。例如:想比较两个byte slice就可以使用bytes.Compare函数。

reflect.DeepEqual()函数在比对slice的时候,如果发现是uint8类型,也就是[]byte类型,也会调用bytes包的对比方法


case Slice:
        // Special case for []byte, which is common.
        if v1.Type().Elem().Kind() == Uint8 {
         return bytealg.Equal(v1.Bytes(), v2.Bytes())
        }
        //转化为string之间的对比
        func Equal(a, b []byte) bool {
         return string(a) == string(b)
        }


五、效率对比及总结

1、效率对比

效率对比参考:Golang几种对象比较方法

1、简单类型的==对比速度最快
2、复杂类型,自己知道结构之后写的自定义对比速度次之
3、复杂结构且不确定结构的,使用cmp.Equal()或者reflect.DeepEqual()都可以,就是效率低点
4、assert.Equal()底层使用的就是reflect.DeepEqual()

2、总结

我们发现对比的两个结构是否相等,方式很多,效率也有高有低。选择合适自己需求的最重要。相对来说,cmp包是要更安全且可操作性更强一点,主要是看大家的喜好了。

链接:https://juejin.cn/post/7316450414158200847






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 转换器
    +关注

    关注

    27

    文章

    8213

    浏览量

    142012
  • 比较器
    +关注

    关注

    14

    文章

    1527

    浏览量

    106505
  • 存储数据
    +关注

    关注

    0

    文章

    74

    浏览量

    14032

原文标题:golang中如何比较struct,slice,map是否相等以及几种对比方法的区别

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    请教Xilinx Slice使用的问题

    在使用ISE进行实现过程中,占用slice的资源较多,如图中所示,想用unrelated logic部分,期望能够将slice资源均衡使用,而折腾了好久,改了好多综合、map等的设置,可都
    发表于 02-28 14:55

    mspware与map430ware的区别吗?

    mspware与map430ware的区别吗?
    发表于 06-21 04:42

    是否有指示MAP方法

    寄存器上的SRL。是否有指示MAP方法,它不应该在某些信号上推断SRL?谢谢杰夫以上来自于谷歌翻译以下为原文I have a design that has high clock rates.I
    发表于 10-10 10:52

    labview中二值化结果显示的几种方式对比

    不同的颜色显示到image上。对比方法②和方法③的效果一样,但是方法②更简便,推荐使用方法②龙哥手把手教您LabVIEW视觉设计课程火热上线!!详情可点击下方链接进行查看:http:/
    发表于 08-16 18:19

    低功耗有哪几种以及不同模式之间的区别

    使用低功耗功能后,大家铁定会通过不同途径查找相关的资料,了解到什么是低功耗、低功耗有哪几种以及不同模式之间的区别,最后还有一些程序截取、说明,资料不少,但是能让人一目了然理解...
    发表于 08-18 06:22

    XILINX MIG(DDR3) IP的AXI接口与APP接口的区别以及优缺点对比

    XILINX MIG(DDR3) IP的AXI接口与APP接口的区别以及优缺点对比
    发表于 11-24 21:47

    Delphi中比较GUID是否相等

    Delphi中比较GUID是否相等,CompareMem(@guid1, @guid2, SizeOf(TGUID)),最开始时想到的方法
    发表于 06-22 10:11 1536次阅读

    几种芯片的对比

    几种芯片的对比,,希望有帮助对大家
    发表于 01-20 17:31 2次下载

    几种步进电机加减速方法对比研究及其应用

    几种步进电机加减速方法对比研究及其应用。
    发表于 05-03 11:44 11次下载

    typedef struct的用法

    typedef是类型定义的意思。typedef struct 是为了使用这个结构体方便。具体区别在于:若struct node{ }这样来定义结构体的话。在定义 node 的结构体变量时,需要这样写:
    发表于 11-09 17:20 3106次阅读

    Java Map几种循环方式学习总结

    本文档内容介绍了基于Java Map几种循环方式学习总结,供参考
    发表于 03-19 15:51 0次下载

    分析对比几种常用轴修复方法

    分析对比几种常用轴修复方法
    发表于 12-02 11:05 1次下载

    C++中struct和class的区别

    C++中struct和class的区别是什么?C++中struct和class的最大区别在于:         struct的成员默认是公有
    的头像 发表于 03-10 17:41 603次阅读

    PostgreSQL准确且快速的数据对比方法

    NineData 数据对比是一款云原生数据对比产品,具备每秒处理 100 万笔记录的高效能力。它提供了一站式支持,适用于 IDC 自建、云主机自建以及云数据库。NineData 支持多种数据库,包括
    的头像 发表于 09-12 15:46 350次阅读
    PostgreSQL准确且快速的数据<b class='flag-5'>对比方法</b>

    List 转 Map方法

    ; // 构造函数 、 get 、 set } 我们假定 id 字段 是唯一的, 所以我们把 id 作为 Map 的key。 使用 Java 8 之前的方法 在使用Java 8 之前,就只能使用比
    的头像 发表于 10-09 16:10 812次阅读