MENU

【GO】等于运算符的二三事

April 25, 2022 • Golang

序言

众所周知,在go语言里面,有一个叫做等于号的东西,平时微不足道,如果不是原生go语言程序员,甚至都不一定能注意得到它,不就是一个等号么?有什么不一样的?但是你知道么,go的等号,可不仅仅只能在基础类型中进行比较

基础类型

这个简单,也是go语言的等号最常常用到的地方

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 14:18:22
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func main() {
  var a, b, c int = 12, 12, 34
  fmt.Println(a == b)
  fmt.Println(b == c)
}

但是你也应当注意,在go语言或者其他的语言中,我们都不应该将两个float类型进行等于运算,这是因为计算机中的数据存储是以二进制的形式存储的,在进行加减运算时难免会遇到精度丢失的问题,所以如果要将两个小数进行比较或者其他运算,建议将这个小数转换成string类型后处理(参考大整数相加的算法),这个问题以后有空了我再来细说。

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 14:20:18
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func main() {
  var a, b, c = 0.1, 0.2, 0.3
  fmt.Println(a+b == c) // false
  fmt.Println(a + b)    // 0.30000000000000004
  fmt.Println(c)        // 0.3
}

然后还有一点值得注意的问题,这个虽然不常用,但是数组之间也是可以相互比较(仅限等于符号)的哦

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 14:47:27
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

type INT1 int
type INT2 int

func main() {
  var a, b = [10]int{1, 2, 3, 4, 5, 6}, [10]int{1, 2, 3, 4, 5, 6}
  fmt.Println(a == b) // true
}

自定义类型

在go语言中,我们可以通过

type typeA typeB
type typeC typeB

来自定义一个类型typeA和一个typeC,那你有没有想过,因为typeA和typeC实质都是typeB,那么此时的typeA和typeC,可以进行比较么?答案当然是否定的,我们看下面这个代码片段

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 13:57:20
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

type INT1 int
type INT2 int

func main() {
  var a INT1 = 12
  var b INT2 = 12

  fmt.Println(a == b) // invalid operation: a == b (mismatched types INT1 and INT2)
}

go语言在进行比较之前,会先检查等于号两边的变量是否是相同类型的,如果类型不相同,则不能进行比较,在上述这个例子中,尽管a和b的底层都是int类型,但是在进行等于运算的时候,go依然会认为a和b的类型是INT1和INT2

当然,作为一个go程序员,你也应该要想到这些:

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 14:35:54
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

type INT1 int
type INT2 int

func main() {
  var a INT1 = 12
  var b INT2 = a // cannot use a (variable of type INT1) as type INT2 in variable declaration

  fmt.Println(a == b)
}

既然这俩不是同一类型,当然也不能相互赋值,但是你可能会发现,var a INT1 = 12,这个12难道不是int型的么?为什么可以被赋值给a呢?什么?你难道不知道var a float = 12么?

引用类型

说到引用类型,我们再熟悉不过了,我们先回忆下有哪些:指针切片字典函数通道

我们前文说了,数组也能比较,那我们看看与数组比较类似的切片能比较么:

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 14:51:24
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func main() {
  var a, b = []int{1, 2, 3, 4, 5, 6}, []int{2, 2, 3, 4, 5, 6}
  fmt.Println(a == b) // invalid operation: a == b (slice can only be compared to nil)
}

切片不行,那我map总行了吧:

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 14:53:10
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func main() {
  var a = map[string]string{"hello": "world"}
  var b = map[string]string{"hello": "world"}
  fmt.Println(a == b)  // invalid operation: a == b (map can only be compared to nil)
}

为什么还是不行?这map是不是也有什么大病?还是说所有的引用类型都是不能比较的?

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 15:00:54
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func main() {
  var ch1, ch2 = make(chan int, 10), make(chan int, 10)
  var ch3 chan int = ch1

  ch1 <- 1
  ch2 <- 2
  fmt.Println(ch1 == ch2) // false
  fmt.Println(ch1 == ch3) // true
}

结局很意外,channel竟然比较上了,而且从这段代码里面,我们可以知道,channel的比较,是基于比较引用类型底层的指针来实现的,如果两个channel的底层的指针是一样的,那么channel的比较结果及时true,反之则为false

那现在就有一个问题,为什么map和slice不可进行比较呢?我们先说说slice

前文我们也提过,slice的底层结构如下:

type slice struct {
    array unsafe.Pointer    // 指向底层数组
    len int                 // slice 长度
    cap int                 // slice 底层数组容量|预分配的大小
}

如果我们要进行比较,那么是不是应该比较指针、len、以及cap?那判断两个slice是否相等,是不是应该让三个值都相等?那这样判断是否还存在意义?因此设计go语言的时候,设计者就没有考虑切片的比较问题

我们再说说map,map的value因为可以是切片等等类型,所以在比较的时候会出现一些问题,因此go的设计也也没有考虑map的比较问题

函数类型也是,这个函数类型也是不可比较的

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 14:56:50
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func main() {
  var fun1 = func() int {
    return 1
  }
  var fun2 = func() int {
    return 2
  }

  fmt.Println(fun1 == fun2) // invalid operation: fun1 == fun2 (func can only be compared to nil)
}

接口类型

go语言中的接口类型也是非常有特点的,我们来看看下面的一段代码

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 15:53:03
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func compare(a, b interface{}) bool {
  return a == b
}

func main() {
  var a, b = 12, 12
  var c string = "hello"
  fmt.Println(compare(a, b)) // true
  fmt.Println(compare(a, c)) // false
}

go中的interface都有默认实现一个T(类型)和一个V(值),比较的时候会先比较interface的T,如果相同继续比较V,否则就返回false

这里说一下,一下这种情况也会出错:

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 17:44:20
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func compare(a, b interface{}) bool {
  return a == b
}

func main() {
  var a = []int{1, 2, 4}
  var b = []int{1, 2, 4}
  fmt.Println(compare(a, b)) // panic: runtime error: comparing uncomparable type []int
}

结构体

go语言的结构体也是可以比较的,还是和基础类型一样,会优先比较数据类型,如果数据类型不同就会报错,否则会一个一个去比较内部的值是否相等,参考下面代码

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 17:47:03
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

type person struct {
  Age  int
  Name string
}

type Vampare struct {
  Age  int
  Name string
}

func main() {
  var p1 = person{Age: 12, Name: "dio"}
  var p2 = Vampare{Age: 12, Name: "dio"}

  fmt.Println(p1 == p2) // invalid operation: p1 == p2 (mismatched types person and Vampare)
}
/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 13:58:54
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

type person struct {
  Age  int
  Name string
}

func main() {
  var p1 = person{Age: 12, Name: "dio"}
  var p2 = person{Age: 12, Name: "dio"}

  fmt.Println(p1 == p2) // true
}

值得一提的是,如果结构体中出现了无法比较的字段,比如出现了slice或者map之类的东西,还是会报错

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 17:50:09
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

type person struct {
  Age  int
  Name string
  Box  []int
}

func main() {
  var p1 = person{Age: 12, Name: "dio", Box: []int{1, 2, 3, 4}}
  var p2 = person{Age: 12, Name: "dio", Box: []int{1, 2, 3, 4}}

  fmt.Println(p1 == p2) // invalid operation: p1 == p2 (struct containing []int cannot be compared)
}
作者:NorthCity1984
出处:https://grimoire.cn/golang/equals.html
版权:本文《【GO】等于运算符的二三事》版权归作者所有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

Last Modified: October 15, 2022
Leave a Comment

2 Comments
  1. 鸟叔来串门,通过虫洞穿梭至此,期待回访!

    1. @鸟叔欢迎常来