乐趣区

关于golang:一道比较运算符相关的面试题把我虐的体无完肤

来自公众号:新世界杂货铺

杂(货铺)谈

明天这篇文章相对来说比拟根底,大家花几分钟工夫看看,有所播种天然是最好,没有播种也就消磨几分钟工夫罢了,你不亏,笔者也不亏~

前几期还是有肯定难度的 HTTP 系列文章,明天却是画风渐变讲起了根底,这当然是因为根底重要呀。正所谓万丈高楼平地起,咱们夯实根底,楼能力建的高,毕竟精美的小矮楼总是很容易被高楼遮挡。嗨,扯远了,总之笔者明天写这篇文章相对不是上面这个起因:

累呀!真的累!工作嘛?当然不是!前几期剖析 HTTP 系列文章的确消耗了太多精力,周末间断熬夜就算是铁打的人也扛不住,所以本周只有竭尽全力这一个目标。

杂(货铺)言

Go 中的比拟操作符,你真的理解吗?如果面试官问你上面输入什么,你的答案是什么?

type blankSt struct {
  a int
  _ string
}
var (bst1 = blankSt{1, "333"}
  bst2 = blankSt{1, "44444"}
)
fmt.Println(bst1 == bst2)

明天,笔者总结了一份比拟运算符的相干文档,助力读者夯实根底(上述答案请参考后文)。

根本定理

在 Go 中,比拟运算符也是遵循定理的,两条根本定理如下:

定理一 :相等运算符==!=实用于具备可比性的操作数,排序运算符 <<=>>=实用于可排序的操作数。

定理二:在任何比拟中,至多满足一个操作数能赋值给另一个操作数类型的变量。

常见类型的比拟

常见类型的比拟大家都懂,在这里笔者就不具体介绍了,仅枚举一下准则加深大家的印象:

  • 布尔值是可比拟的,但不可排序,即仅实用于 ==!=运算符。
  • 整数和浮点数是可比拟的且可排序,实用于所有比拟运算符。
  • 字符串的值是可比拟的,且按字节排序,即比拟时按字节比拟大小(不了解字符串和字节切片关系的,请参考深刻了解 go 中字符串这篇文章)。

以上即为常见类型的比拟准则,上面咱们联合例子逐渐了解各种类型之间的比拟。

不可比拟的类型

在 Go 中,切片 map,和func 是不可比拟的,他们仅能够和预申明示意符 nil 进行比拟。能和 nil 进行比拟的还有 指针 管道 interface{}

复数之间的比拟

复数可比拟但不可排序,实部和虚部均相等两个复数才相等 。复数仅实用==!=这两个比拟运算符:

var c1 complex128 = complex(1, 2) // 1+2i
var c2 complex128 = complex(3, 4) // 3+4i
fmt.Println(c1 == c2)

上述输入后果为false,证实复数之间可比拟。

var c1 complex128 = complex(1, 2) // 1+2i
var c2 complex128 = complex(3, 4) // 3+4i
fmt.Println(c1 >= c2)

此时程序无奈运行,在 vscode 中的谬误揭示为cannot compare c1 >= c2 (operator >= not defined for complex128)compiler。由此确认复数不可排序。

构造体之间的比拟

如果构造体的所有字段都是可比拟的,则他们所有非匿名字段的值相等,构造体的值才相等。验证如下:

type canC struct {c int}
var st1, st2 canC
fmt.Println(st1 == st2)
st1.c = 3
fmt.Println(st1 == st2)

上述输入别离为 truefalse。由此验证非匿名字段的值相等,构造体的值才相等。

fmt.Println(st1 <= st2)

此行代码在 vscode 中的谬误揭示为 cannot compare st1 <= st2 (operator <= not defined for canC)compiler。由此可知,即便构造体满足比拟条件也无奈实用于<<=>>=运算符。

:后文中提到不可排序均代表着无奈实用于<<=>>=运算符,在后文中不再对不可排序给出例子。

上面看看蕴含匿名字段的构造体比拟时有什么不同:

type blankSt struct {
  a int
  _ string
}
var (bst1 = blankSt{1, "333"}
  bst2 = blankSt{1, "44444"}
)
fmt.Println(bst1 == bst2)

上述输入为 true,合乎非匿名字段的值相等时构造体的值相等这一准则。留神, 如果匿名字段是不可比拟的类型时,上述代码会编译报错

最初咱们看看蕴含不可比拟类型的构造体:

type canNotC struct {m func() int
}
fmt.Println(canNotC{} == canNotC{})

上述代码在 vscode 中的谬误揭示为cannot compare (canNotC literal) == (canNotC literal) (operator == not defined for canNotC)compiler,由此可知,构造体如果要可比拟,则其外部的所有字段必须全为可比拟类型。

指针之间的比拟

指针是可比拟的,但不可排序。如果两个指针指向同一个变量,或者两个指针值均为 nil,则这两个指针相等。置信读者对于这一点应该是没有异议的,然而有一个状况却是非常须要留神的。

zero-size variables:如果构造体没有任何字段或者数组没有任何元素,则其大小为 0,即 unsafe.Sizeof 的计算结果为 0。两个不同的 zero-size variables 在内存中可能具备雷同的地址。

指向不同 zero-size variables 的两个指针可能相等也可能不相等

var arr1, arr2 [0]int
parr1 := &arr1
parr2 := &arr2
fmt.Println(unsafe.Sizeof(arr1))
fmt.Println(parr1 == parr2)
fmt.Println(uintptr(unsafe.Pointer(parr1)), uintptr(unsafe.Pointer(parr2)))
// 输入如下:0
false
824634830552 824634830552 // 每次运行输入的地址不肯定雷同

笔者屡次运行,parr1 == parr2始终输入为 false,目前尚未发现输入为true 的状况,在 https://github.com/golang/go/…,所以笔者就不再对此问题做更近一步的剖析。

Channel 之间的比拟

在写这篇文章前,笔者素来都没有想过 Channel 之间是能够比拟的。事实上,管道是可比拟类型,golang 原文如下:

Channel values are comparable. 
Two channel values are equal if they were created by the same call to make or if both have value nil

这里须要留神的是,只有雷同调用的管道才是相等的:

var cint1, cint2 chan<- string
cint3 := make(chan string, 2)
cint4 := make(chan string, 2)
cint5 := make(chan string)
fmt.Println(cint1 == cint2, cint3 == cint4, cint5 == cint1) // true false false
cint1 = cint4
fmt.Println(cint1 == cint4) //true

上述中,cint1cint2 初始值均为 nil,所以输入 true。双向通道cint4 赋值给单向通道 cint1 时,满足雷同的 make 调用这一条件,所以输入也为true

Interface{}之间比拟

Interface{}是可比拟的,然而不可排序。两个 Interface{}变量的动静类型和动静 value 均统一它们才相等,两个变量均为 nil 也是相等的。针对这一准则笔者对其分以下几种状况探讨。

一、interface{}不为 nil,且动静类型均为可比拟类型时:

var (i1 interface{} = uint(1)
  i2 interface{} = uint(1)
  i3 interface{} = uint(3)
  i4 interface{} = int(3)
  i5 interface{} = []int{}
  i6 interface{} = map[int]string{}
  i7 interface{} = map[int]string{})
fmt.Println(i1 == i2, i1 == i3,i3 == i4)

上述输入后果为 true false false,这合乎 动静类型和动静 value 均统一时才相等 的准则。

二、如果比拟单方动静类型统一且为不可比拟类型时会 panic

这种状况可失常编译,然而会造成运行时解体,所以肯定要留神!!!

fmt.Println(i5 == i6)
fmt.Println(i7 == i6)

上述比拟 i5i6时可能失常输入,然而 i6i7比拟时呈现如下谬误:

所以,笔者在这里再次强调,如果我的项目中有不小心间接应用了 interface{} 进行比拟的,请肯定要留神⚠️。

三、动静 value 为 nil 的 interface{}不肯定等于 nil:

func t() interface{} {
    var err *error
    return err
}

func t1() interface{} {return nil}
fmt.Println(t() == nil, t1() == nil) // 输入 false, true

由上述代码知,如果不是间接返回 nilinterface{}nil 进行比拟时是不相等的。置信很多人在平时的开发中都有可能会疏忽这个问题。上面咱们对它为什么不相等进行简略的剖析。

在 Go 中, interface{}的实现为两个元素,类型 t 和值v。v 是一个具体的值,如 int、struct、或指针等。

如果,咱们在接口中存储 int 值 3,则接口中的值为(t=int,v=3)。

值 v 也称为接口的动静值,因为在程序执行期间,给定的接口变量可能蕴含不同的值 v(以及相应的类型 t)。

只有当 v 和 t 都未设置时,接口值才是 nil(t=nil,未设置 v)。

如果,咱们在接口中存储一个类型为int 的 nil 指针,那么不论指针的值是什么,外部类型都会是int:(t=*int,v=nil)。因而,即便外部的指针 v 为 nil,这样的接口值也是非 nil 的。

本局部内容翻译整顿自 https://golang.org/doc/faq#ni…

非接口类型 X 实现了接口 T,则 X 的变量 x 能和 T 的变量 t 进行比拟

准则:非接口类型 X 实现了接口 T,则 X 的变量 x 能和 T 的变量 t 进行比拟。只有当 t 的动静变量类型为 X 且 t 的动静 value 和 x 相等时,t 才等于 x

推论:又因为 go 中任意类型都默认实现了 interface{},则意味着interface{} 变量能和任意的非 interface{} 类型的可比拟类型进行比拟。

验证准则:

type it interface {f()
}
type ix1 int
func (x ix1) f() {}
type ix2 map[int]int
func (x ix2) f() {}

x1 := ix1(2)
var t1 it = ix2{}
// 类型不统一时
fmt.Println(x1 == t1) // fasle
// 类型统一时
t1 = ix1(2)
fmt.Println(t1 == x1) // true

下面的输入别离为 falsetrue,合乎准则。

验证推论:

var it1 interface{} = "111"
fmt.Println(it1 == 1)

下面可能失常比拟,阐明推论正确,且输入为false,合乎准则。

留神:上面状况会 panic

var t2 it = ix2{}
var t3 it = ix2{}
fmt.Println(t2 == t3)

上述代码产生 panic,符和 Interface{} 之间比拟 的准则。

数组之间的比拟

两个数组的元素类型雷同且是可比拟类型,并且数组的长度雷同,则这两个数组可比拟。当两个可比拟的数组对应元素均相等时,则这两个数组相等。即便两个数组可比拟,但仍旧不可排序

类型雷同但元素不可比拟时:

var array1 [3][]int
var array2 [3][]int
fmt.Println(array1 == array2)

上述代码在 vscode 中的谬误为cannot compare array1 == array2 (operator == not defined for [3][]int)compiler,所以如果数组元素为不可比拟类型,则数组也不可比拟。

数组元素可比拟但数组长度不统一时:

var array3 [3]int
var array4 [2]int
fmt.Println(array3 == array4)

上述代码在 vscode 中的谬误为cannot compare array3 == array4 (mismatched types [3]int and [2]int)compiler,所以如果数组长度不统一时,则两个数组不可比拟。

满足数组长度相等且元素类型可比拟时:

var array5, array6 [3]int
fmt.Println(array5 == array6)
array5 = [...]int{3, 2, 1}
array6 = [...]int{1, 2, 3}
fmt.Println(array5 == array6)

上述输入别离为 truefalse,合乎可比拟数组一一判断对应元素是否相等这一准则。所以,咱们平时在开发中能够利用该准则疾速比拟数组是否相等。

最初,衷心希望本文可能对各位读者有肯定的帮忙。

  1. 写本文时,笔者所用 go 版本为: go1.14.2
  2. 文章中所用残缺例子:https://github.com/Isites/go-…

参考:

https://golang.org/ref/spec#C…

退出移动版