判等与哈希(Equality)
== 与 != 的口径全语言二分(ADR-0102):值语义类型比值,引用语义类型比身份。类型用 operator == 重载后按重载走,未重载时按本页口径。
二分口径
| 类型 | == 比什么 |
|---|---|
| 标量(整数族 / float / decimal / bool / char) | 数值 |
| string | 内容(序数逐码点) |
| enum / flags(含载荷变体) | 值 |
| struct | 逐字段值 |
| data | 深结构相等 |
| class 实例 | 引用同一性 |
| 容器五族(List / 数组 / Set / Map / frozen) | 引用同一性 |
| 元组 | 引用同一性(同内容异实例不等;比内容逐分量) |
| 函数值 | 引用同一性(具名 intern 单例恒等;lambda 每次求值新闭包) |
class Pt
{
public var x: int = 0;
public var y: int = 0;
}
struct Sp
{
public var x: int = 0;
public var y: int = 0;
}
func main() -> int
{
val a = new Pt() { x: 1, y: 2 };
val b = new Pt() { x: 1, y: 2 };
print(a == b, a == a);
val q = new Sp() { x: 1, y: 2 };
val q2 = new Sp() { x: 1, y: 2 };
print(q == q2);
return 0;
}False True
True两个内容相同的 Pt 不相等:各自 new 出来是两个对象,同一对象的别名才相等。Sp 是 struct,赋值即拷贝,== 逐字段比,内容同即 True。
enum 的载荷变体同样按值:Opt.Some(5) == Opt.Some(5) 为 True。decimal 的相等是纯数值:1.10d64 == 1.1d64 为 True(尾零不参与),0.1d64 + 0.2d64 == 0.3d64 也成立,十进制运算在此精度无损。
元组与函数值:引用同一性
func Add1(x: int) -> int { return x + 1; }
func Add2(x: int) -> int { return x + 2; }
func main() -> int
{
val p = (1, "甲");
val q = (1, "甲");
val alias = p;
val a = Add1;
val b = Add1;
val c = Add2;
val f1 = (x: int) => x * 2;
val f2 = (x: int) => x * 2;
print(p == q, p == alias, a == b, a == c, f1 == f1, f1 == f2);
return 0;
}False True True False True Falsep == q 是两次构造,内容相同也不等;p == alias 是同一实例。函数值的身份是(函数, 闭包环境)实例:具名函数 intern 成单例,a == b 恒 True,a == c 为 False;lambda 每次求值产生新闭包,写法相同的 f1 == f2 为 False;具名与 lambda 相比为 False。可空对(可空比可空、可空与非空混对)解包后同律,非可空值与 null 的比较报 MS3101(死比较,全语言一切非可空类型同律);异形对(元组比列表)报 MS4201。跨签名的函数对(参数或返回任一不同)编译期一律报 MS4201,注解变量、推断局部与裸名同律;序比较与算术仍报 MS3104。any 通道是豁免域:动态值比较按运行时对象身份,两个函数值经 any 相比编译通过、恒 False,与类型面分轨。
容器:身份判等 + ContentEquals
容器五族的 == 一律引用同一性,与赋值/传参的引用共享语义一致。按内容比较用 ContentEquals,逐元素递归、环安全:
val l1: list<int> = [1, 2, 3];
val l2: list<int> = [1, 2, 3];
print(l1 == l2, l1.ContentEquals(l2));
val s1: set<int> = [1, 2];
val s2: set<int> = [2, 1];
print(s1.ContentEquals(s2));
val m1: map<string, int> = new map<string, int>();
m1["甲"] = 1;
val m2: map<string, int> = new map<string, int>();
m2["甲"] = 1;
print(m1.ContentEquals(m2));False True
True
True- Set 的内容判等是「同元素集」,与顺序无关;
- Map 是「同键集 + 逐键值等」;
- frozen 的
==同样是身份:freeze([1, 2]) == freeze([1, 2])为 False(每次构造都是新的深拷副本),详见深冻。
容器各页的查找与删除方法全部走本页口径:list.Contains / IndexOf / Remove(v) 对 struct 元素按字段值比、对 class 元素按身份比;set / map 按哈希口径。
哈希与作键
Map 的键与 Set 的元素按上面的默认口径取哈希:struct / 标量 / string / enum 按值,class 实例按身份。class 作键时只有同一个对象查得回来,struct 作键则同内容即同键:
val a = new Pt() { x: 1, y: 2 };
val byClass: map<Pt, string> = new map<Pt, string>();
byClass[a] = "甲";
print(byClass[a], byClass.Contains(new Pt() { x: 1, y: 2 }));
val q = new Sp() { x: 1, y: 2 };
val byStruct: map<Sp, string> = new map<Sp, string>();
byStruct[q] = "乙";
print(byStruct[new Sp() { x: 1, y: 2 }]);甲 False
乙想要「同内容即同键」,用 struct 或把键先规约成标量;class 作键的优势是对象存续期内哈希稳定(改字段不影响)。
