一道比较运算符相关的面试题把我虐的体无完肤
来自公众号:新世界杂货铺
杂(货铺)谈
今天这篇文章相对来说比较基础,大家花几分钟时间看看,有所收获自然是最好,没有收获也就消磨几分钟时间罢了,你不亏,笔者也不亏~
前几期还是有一定难度的HTTP系列文章,今天却是画风突变讲起了基础,这当然是因为基础重要呀。正所谓万丈高楼平地起,我们夯实基础,楼才能建的高,毕竟精美的小矮楼总是很容易被高楼遮挡。嗨,扯远了,总之笔者今天写这篇文章绝对不是下面这个原因:
累呀!真的累!工作嘛?当然不是!前几期分析HTTP系列文章确实耗费了太多精力,周末连续熬夜就算是铁打的人也扛不住,所以本周只有养精蓄锐这一个目的。
杂(货铺)言
Go中的比较操作符,你真的了解吗?假如面试官问你下面输出什么,你的答案是什么?
今天,笔者总结了一份比较运算符的相关文档,助力读者夯实基础(上述答案请参考后文)。
基本定理
在Go中,比较运算符也是遵循定理的, 两条基本定理如下:
定理一:相等运算符==
和!=
适用于具有可比性的操作数,排序运算符<
,<=
,>
和>=
适用于可排序的操作数。
定理二:在任何比较中,至少满足一个操作数能赋值给另一个操作数类型的变量。
常见类型的比较
常见类型的比较大家都懂,在这里笔者就不详细介绍了,仅枚举一下原则加深大家的印象:
布尔值是可比较的,但不可排序,即仅适用于
==
和!=
运算符。整数和浮点数是可比较的且可排序,适用于所有比较运算符。
字符串的值是可比较的,且按字节排序,即比较时按字节比较大小(不理解字符串和字节切片关系的,请参考深入理解go中字符串这篇文章)。
以上即为常见类型的比较原则,下面我们结合例子逐步理解各种类型之间的比较。
不可比较的类型
在Go中,切片
,map
,和func
是不可比较的,他们仅可以和预声明表示符nil
进行比较。能和nil进行比较的还有指针
,管道
和interface{}
。
复数之间的比较
复数可比较但不可排序,实部和虚部均相等两个复数才相等。复数仅适用==
和!=
这两个比较运算符:
上述输出结果为false
,证明复数之间可比较。
此时程序无法运行,在vscode中的错误提醒为cannot compare c1 >= c2 (operator >= not defined for complex128)compiler
。由此确认复数不可排序。
结构体之间的比较
如果结构体的所有字段都是可比较的,则他们所有非匿名字段的值相等,结构体的值才相等。验证如下:
上述输出分别为true
和false
。由此验证非匿名字段的值相等,结构体的值才相等。
此行代码在vscode中的错误提醒为cannot compare st1 <= st2 (operator <= not defined for canC)compiler
。由此可知,即使结构体满足比较条件也无法适用于<
,<=
,>
和>=
运算符。
注:后文中提到不可排序均代表着无法适用于<
,<=
,>
和>=
运算符,在后文中不再对不可排序给出例子。
下面看看包含匿名字段的结构体比较时有什么不同:
上述输出为true
,符合非匿名字段的值相等时结构体的值相等这一原则。注意,如果匿名字段是不可比较的类型时,上述代码会编译报错。
最后我们看看包含不可比较类型的结构体:
上述代码在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的两个指针可能相等也可能不相等。
笔者多次运行,parr1 == parr2
始终输出为false
,目前尚未发现输出为true
的情况,在https://github.com/golang/go/issues/23440也有人遇到同笔者相同的情况,所以笔者就不再对此问题做更近一步的分析。
Channel之间的比较
在写这篇文章前,笔者从来都没有想过Channel之间是可以比较的。事实上,管道是可比较类型,golang原文如下:
这里需要注意的是,只有相同调用的管道才是相等的:
上述中,cint1
和cint2
初始值均为nil,所以输出true
。双向通道cint4
赋值给单向通道cint1
时,满足相同的make
调用这一条件,所以输出也为true
。
Interface{}之间比较
Interface{}是可比较的,但是不可排序。两个Interface{}变量的动态类型和动态value均一致它们才相等,两个变量均为nil也是相等的。针对这一原则笔者对其分以下几种情况讨论。
一、interface{}不为nil,且动态类型均为可比较类型时:
上述输出结果为true false false
,这符合动态类型和动态value均一致时才相等的原则。
二、如果比较双方动态类型一致且为不可比较类型时会panic:
这种情况可正常编译,但是会造成运行时崩溃,所以一定要注意!!!
上述比较i5
和i6
时能够正常输出,但是i6
和i7
比较时出现如下错误:
所以,笔者在这里再次强调,如果项目中有不小心直接使用了interface{}
进行比较的,请一定要注意⚠️。
三、动态value为nil的interface{}不一定等于nil:
由上述代码知,如果不是直接返回nil
的interface{}
和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#nil_error
非接口类型X实现了接口T,则X的变量x能和T的变量t进行比较
原则:非接口类型X实现了接口T,则X的变量x能和T的变量t进行比较。只有当t的动态变量类型为X且t的动态value和x相等时,t才等于x。
推论:又因为go中任意类型都默认实现了interface{}
,则意味着interface{}
变量能和任意的非interface{}
类型的可比较类型进行比较。
验证原则:
上面的输出分别为false
和true
,符合原则。
验证推论:
上面能够正常比较,说明推论正确,且输出为false
,符合原则。
注意:下面情况会panic
上述代码发生panic,符和Interface{}之间比较
的原则。
数组之间的比较
两个数组的元素类型相同且是可比较类型,并且数组的长度相同,则这两个数组可比较。当两个可比较的数组对应元素均相等时,则这两个数组相等。即使两个数组可比较,但依旧不可排序。
类型相同但元素不可比较时:
上述代码在vscode中的错误为cannot compare array1 == array2 (operator == not defined for [3][]int)compiler
,所以如果数组元素为不可比较类型,则数组也不可比较。
数组元素可比较但数组长度不一致时:
上述代码在vscode中的错误为cannot compare array3 == array4 (mismatched types [3]int and [2]int)compiler
,所以如果数组长度不一致时,则两个数组不可比较。
满足数组长度相等且元素类型可比较时:
上述输出分别为true
和false
,符合可比较数组一一判断对应元素是否相等这一原则。所以,我们平时在开发中可以利用该原则快速比较数组是否相等。
最后,衷心希望本文能够对各位读者有一定的帮助。
注:
1. 写本文时, 笔者所用go版本为: go1.14.2
2. 文章中所用完整例子:https://github.com/Isites/go-coder/blob/master/comparison-operators/main.go
参考:
https://golang.org/ref/spec#Comparison_operators
版权声明: 本文为 InfoQ 作者【新世界杂货铺】的原创文章。
原文链接:【http://xie.infoq.cn/article/10b3c3150ba7a4503a0751777】。未经作者许可,禁止转载。
评论