深入理解Go之==的使用

2025-11-14 18:50
文章标签 go 深入 理解 使用

本文主要是介绍深入理解Go之==的使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《深入理解Go之==的使用》本文主要介绍了深入理解Go之==的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...

概述

相信==判等操作,大家每天都在用。之前在论坛上看到不少人在问 golang ==比较的结果。看到很多人对 golang 中==的结果不太了解。确实,golang 中对==的处理有一些细节的地方需要特别注意。虽然平时可能不太会遇到,但是碰到了就是大坑。本文将对 golang 中==操作做一个系统的介绍。希望能对大家有所帮助。

类型

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

  1. 基本类型:整型(int/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64/byte/rune等)、浮点数(float32/float64)、复数类型(complex64/complex128)、字符串(string)。
  2. 复合类型(又叫聚合类型):数组和结构体类型。
  3. 引用类型:切片(slice)、map、channel、指针。
  4. 接口类型:如error

==操作最重要的一个前提是:两个操作数类型必须相同!类型必须相同!类型必须相同!

如果类型不同,那么编译时就会报错。

注意:

  1. golang 的类型系统非常严格,没有C/C++中的隐式类型转换。虽然写起来稍微有些麻烦,但是能避免今后非常多的麻烦!!!
  2. golang 中可以通过type定义新类型。新定义的类型与底层类型不同,不能直接比较。

为了更容易看出类型,示例代码中的变量定义都显式指定了类型。

看下面的代码:

package main
 
import "fmt"
 
func main() {
    var a int8
    var b int16
    // 编译错误:invalid operation a == b (mismatched types int8 and int16)
    fmt.Println(a == b)
}

没有隐式类型转换。

package main
 
import "fmt"
 
http://www.chinasem.cnfunc main() {
    type int8 myint8
    var a int8
    var b myint8
    // 编译错误:invalid operation a == b (mismatched types int8 and myint8)
    fmt.Println(a == b)
}

虽然myint8的底层类型是int8,但是他们是不同的类型。

下面依次通过这 4 种类型来说明==是如何做比较的。

基本类型

这是最简单的一种类型。比较操作也很简单,直接比较值是否相等。没啥好说的,直接看例子。

var a uint32 = 10
var b uint32 = 20
var c uint32 = 10
fmt.Println(a == b) // false
fmt.Println(a == c) // true

有一点需要注意,浮点数的比较问题:

var a float64 = 0.1
var b float64 = 0.2
var c float64 = 0.3
fmt.Println(a + b == c) // false

因为计算机中,有些浮点数不能精确表示,浮点运算结果会有误差。如果我们分别输出a+bc的值,会发现它们确实是不同的:

fmt.Println(a + b)
fmt.Println(c)
 
// 0.30000000000000004
// 0.3

这个问题不是 golang 独有的,只要浮点数遵循 IEEE 754 标准的编程语言都有这个问题。需要特别注意,尽量不要做浮点数比较,确实需要比较时,计算两个浮点数的差的绝对值,如果小于一定的值就认为它们相等,比如1e-9

复合类型

复合类型也叫做聚合类型。golang 中的复合类型只有两种:数组和结构体。它们是逐元素/字段比较的。

注意:数组的长度视为类型的一部分,长度不同的两个数组是不同的类型,不能直接比较

  • 对于数组来说,依次比较各个元素的值。根据元素类型的不同,再依据是基本类型、复合类型、引用类型或接口类型,按照特定类型的规则进行比较。所有元素全都相等,数组才是相等的。
  • 对于结构体来说,依次比较各个字段的值。根据字段类型的不同,再依据是 4 中类型中的哪一种,按照特定类型的规则进行比较。所有字段全都相等,结构体才是相等的。

例如:

a := [4]int{1, 2, 3, 4}
b := [4]int{1, 2, 3, 4}
c := [4]int{1, 3, 4, 5}
fmt.Println(a == b) // true
fmt.Println(a == c) // false
 
type A struct {
    a int
    b string
}
aa := A { a : 1, b : "test1" }
bb := A { a : 1, b : "test1" }
cc := A { a : 1, b : "test2" }
fmt.Println(aa == bb)
fmt.Println(aa == cc)

引用类型

引用类型是间接指向它所引用的数据的,保存的是数据的地址。引用类型的比较实际判断的是两个变量是不是指向同一份数据,它不会去比较实际指向的数据。

例如:

type A struct {
    a int
    b string
}
 
aa := &A { a : 1, b : "test1" }
bb := &A { a : 1, b : "test1" }
cc := aa
fmt.Println(aa == bb)
fmt.Println(aa == cc)

因为aabb指向的两个不同的结构体,虽然它们指向的值是相等的(见上面复合类型的比较),但是它们不等。 aacc指向相同的结构体,所以它们相等。

再看看channel的比较:

ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch3 := ch1
 
fmt.Println(ch1 == ch2)
fmt.Println(ch1 == ch3)

ch1ch2虽然类型相同,但是指向不同的channel,所以它们不等。 ch1ch3指向相同的channel,所以它们相等。

关于引用类型,有两个比较特殊的规定:

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

为什么要做这样的规定?我们先来说切片。因为切片是引用类型,它可以间接的指向自己。例如:

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 团队认为不值得为此耗费精力。

基于上面两点原因,golang 直接规定切片类型不可比较。使用==比较切片直接编译报错。

例如:

var a []int
var b []int
 
// invalid operation: a == b (http://www.chinasem.cnslice can only be compared to nil)
fmt.Println(a == b)

错误信息很明确。

因为map的值类型可能为不可比较类型(见下面,切片是不可比较类型),所以map类型也不可比较。

接口类型

接口类型是 golang 中比较重要的一种类型。接口类型的值,我们称为接口值。一个接口值是由两个部分组成的,具体类型(即该接口存储的值的类型)和该类型的一个值。引用《go 程序设计语言》的名称,分别称为动态类型动态值。接口值的比较涉及这两部分的比较,只有当动态类型完全相同且动态值相等(动态值使用==比较),两个接口值才是相等的。

例如:

var a interface{} = 1
var b interface{} = 1
var c interface{} = 2
var d interface{} = 1.0
fmt.Println(a == b) // false
fmt.Println(a == c) // true
fmt.Println(a == d) // false

ab动态类型相同(都是int),动态值也相同(都是1,基本类型比较),故两者相等。 ac动态类型相同,动态值不等(分别为12China编程,基本类型比较),故两者不等。 ad动态类型不同,aintdfloat64,故两者不等。

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
复制代码

aabb动态类型相同(都是A),动态值也相同(结构体A,见上面复合类型的比较规则),故两者相等。 aacc动态类型相同,动态值不同,故两者不等。 ddee动态类型相同(都是*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)

ab的动态值是切片类型,而切片类型不可比较,所以a == bpanic

接口值的比较不要求接口类型(注意不是动态类型)完全相同,只要一个接口可以转化为另一个就可以比较。例如:

var f *os.File
var r io.Reader = f
var rc io.ReadCloser = f
fmt.Println(r == rc) // true
 
vYvoTLqzar w io.Writer = f
// invalid operation: r == w (mismatched types io.Reader and io.Writer)
fmt.Println(r == w)

r的类型为io.Reader接口,rc的类型为io.ReadCloser接口。查看源码,io.ReadCloser的定义如下:

type ReadCloser interface {
	Reader
	Closer
}

io.ReadCloser可转化为io.Reader,故两者可比较。

io.Writer不可转化为io.Reader,编译报错。

使用type定义的类型

使用type可以基于现有类型定义新的类型。新类型会根据它们的底层类型来比较。例如:

type myint int
var a myint = 10
var b myint = 20
var c myint = 10
fmt.Println(a == b) // false
fmt.Println(a == c) // true
 
type arr4 [4]int
var aa arr4 = [4]int{1, 2, 3, 4}
var bb arr4 = [4]int{1, 2, 3, 4}
var cc arr4 = [4]int{1, 2, 3, 5}
fmt.Println(aa == bb)
fmt.Println(aa == cc)

myint根据底层类型int来比较。 arr4根据底层类型[4]int来比较。

不可比较性

前面说过,golang 中的切片类型是不可比较的。所有含有切片的类型都是不可比较的。例如:

  • 数组元素是切片类型。
  • 结构体有切片类型的字段。
  • 指针指向的是切片类型。

不可比较性会传递,如果一个结构体由于含有切片字段不可比较,那么将它作为元素的数组不可比较,将它作为字段类型的结构体不可比较

谈谈map

由于mapkey是使用==来判等的,所以所有不可比较的类型都不能作为mapkey。例如:

// invalid map key type []int
m1 := make(map[[]int]int)
 
type A struct {
    a []int
    b string
}
// invalid map key type A
m2 := make(map[A]int)

由于切片类型不可比较,不能作为mapkey,编译时m1 := make(map[[]int]int)报错。 由于结构体A含有切片字段,不可比较,不能作为mapkey,编译报错。

总结

到此这篇关于深入理解Go之==的使用的文章就介绍到这了,更多相关Go ==内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多android支持China编程(www.chinasem.cn)!

这篇关于深入理解Go之==的使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1156175

相关文章

python中的flask_sqlalchemy的使用及示例详解

《python中的flask_sqlalchemy的使用及示例详解》文章主要介绍了在使用SQLAlchemy创建模型实例时,通过元类动态创建实例的方式,并说明了如何在实例化时执行__init__方法,... 目录@orm.reconstructorSQLAlchemy的回滚关联其他模型数据库基本操作将数据添

Spring配置扩展之JavaConfig的使用小结

《Spring配置扩展之JavaConfig的使用小结》JavaConfig是Spring框架中基于纯Java代码的配置方式,用于替代传统的XML配置,通过注解(如@Bean)定义Spring容器的组... 目录JavaConfig 的概念什么是JavaConfig?为什么使用 JavaConfig?Jav

Go异常处理、泛型和文件操作实例代码

《Go异常处理、泛型和文件操作实例代码》Go语言的异常处理机制与传统的面向对象语言(如Java、C#)所使用的try-catch结构有所不同,它采用了自己独特的设计理念和方法,:本文主要介绍Go异... 目录一:异常处理常见的异常处理向上抛中断程序恢复程序二:泛型泛型函数泛型结构体泛型切片泛型 map三:文

Java使用Spire.Doc for Java实现Word自动化插入图片

《Java使用Spire.DocforJava实现Word自动化插入图片》在日常工作中,Word文档是不可或缺的工具,而图片作为信息传达的重要载体,其在文档中的插入与布局显得尤为关键,下面我们就来... 目录1. Spire.Doc for Java库介绍与安装2. 使用特定的环绕方式插入图片3. 在指定位

Springboot3 ResponseEntity 完全使用案例

《Springboot3ResponseEntity完全使用案例》ResponseEntity是SpringBoot中控制HTTP响应的核心工具——它能让你精准定义响应状态码、响应头、响应体,相比... 目录Spring Boot 3 ResponseEntity 完全使用教程前置准备1. 项目基础依赖(M

Java使用Spire.Barcode for Java实现条形码生成与识别

《Java使用Spire.BarcodeforJava实现条形码生成与识别》在现代商业和技术领域,条形码无处不在,本教程将引导您深入了解如何在您的Java项目中利用Spire.Barcodefor... 目录1. Spire.Barcode for Java 简介与环境配置2. 使用 Spire.Barco

Android使用java实现网络连通性检查详解

《Android使用java实现网络连通性检查详解》这篇文章主要为大家详细介绍了Android使用java实现网络连通性检查的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录NetCheck.Java(可直接拷贝)使用示例(Activity/Fragment 内)权限要求

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

C# 预处理指令(# 指令)的具体使用

《C#预处理指令(#指令)的具体使用》本文主要介绍了C#预处理指令(#指令)的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录1、预处理指令的本质2、条件编译指令2.1 #define 和 #undef2.2 #if, #el

C#中Trace.Assert的使用小结

《C#中Trace.Assert的使用小结》Trace.Assert是.NET中的运行时断言检查工具,用于验证代码中的关键条件,下面就来详细的介绍一下Trace.Assert的使用,具有一定的参考价值... 目录1、 什么是 Trace.Assert?1.1 最简单的比喻1.2 基本语法2、⚡ 工作原理3