假设有一个学生结构体(面向对象中的类),拥有名字和年龄两个字段。现在想仅根据年龄的大小来判断学生的大小和相等关系。这个需求很简单,一个方法就可以搞定。那是否可以用 = != > < >= <=
这四个运算符来直接对实例进行比较呢?
先看一下 Python 中怎样实现:
class Student:
def __init__(self, name: str, score: int) :
self.name = name
self.score = score
def __eq__(self, other): # equal
if isinstance(other, Student):
return self.score == other.score
else:
raise TypeError(type(other))
def __lt__(self, other): # less than
if isinstance(other, Student):
return self.score < other.score
else:
raise TypeError(type(other))
def __gt__(self, other): # greater than
if isinstance(other, Student):
return self.score > other.score
else:
raise TypeError(type(other))
def __le__(self, other): # less equal
if isinstance(other, Student):
return self.score <= other.score
else:
raise TypeError(type(other))
def __ge__(self, other): # greater equal
if isinstance(other, Student):
return self.score >= other.score
else:
raise TypeError(type(other))
if __name__ == '__main__':
s1 = Student("Paul", 88)
s2 = Student("Tom", 88)
print(s1 >= s2)
print(s1 <= s2)
print(s1 < s2)
print(s1 == s2)
借助双下方法,我们可以轻松实现。
这种对『运算符赋予多重含义,使同一个运算符在作用于不同类型数据时产生不同行为』的现象,被称为运算符重载。
那么回到题目中提出的问题,Go 中可以实现运算符重载吗?
答案是不能,但是也不是完全不能。
『不能』是因为,Go 不允许对结构体使用 >
等大小运算符,也就更不能自定义结构体的大小关系了。
『不是完全不能』是因为,尽管无法自定义结构体的大小,但是我们却可以重载 = !=
,对结构体的相等性做出判断。要求也很简单,只要结构体中所有字段类型都是可以用 = !=
的就行。上述特性被称为可比较性(comparable) ,而之前说到的 > < >= <=
被称为顺序性(ordered)。下面是不同类型可比较性和顺序性的汇总。
图片来源:深入理解 Go Comparable Type | SORCERERXW
也就是说,尽管我们不能对学生结构体做大小判断,也不能自定义两个学生的相等性,但是我们还是可以使用 = !=
的,只要结构体中字段类型都是可比较的。如果学生结构体两个实例中所有字段值相等,那么 Go 就认为这两个实例相等。
示例程序如下:
package main
import "fmt"
type Student struct {
name string
score int
}
func main() {
s1 := Student{"Paul", 88}
s2 := Student{"Tom", 78}
s3 := Student{"Paul", 88}
fmt.Println(s1 == s2) // false
fmt.Println(s1 == s3) // true
}
而这里说到的可比较性其实也是 map 键值的要求:map 只能用可比较的类型作为键。更多信息可以参考延伸阅读第二条。
结论
尽管 Go 不允许运算符重载,但是对于所有字段都是可比较的结构体来说,却可以重载 = !=
,当所有字段的值都相等时,Go 认为实例相等。
延伸阅读
-
《Go 语言底层原理剖析》郑建勋 电子工业出版社 P88