Smalltalk / Squeak 字符串浅相等

Smalltalk / Squeak string shallow equality

以下代码打印 "false":

a := 'aaa'.
b := a deepCopy.
Transcript show: (a == b).

我确实预料到这种行为,我对此的解释是 deepCopy returns 一个新对象 "b" 与 "a" 完全不同,因为运算符“== " 对比结果为 "false"。对吗?

但是,我不明白为什么下面的代码会产生"true":

a := 'aaa'.
b := 'aaa'.
Transcript show: (a == b).

这里我们对两个不同的对象进行了两次赋值,"a"和"b",它们之间应该没有任何关系,除了它们包含相同的值。但是如果运算符“==”是按引用比较而不是按值比较,为什么这个比较的结果是"true"呢?

两种情况下同样的误解是问题不是"what happens?",而是"what is guaranteed?"。关键是不能保证 'aaa' == 'aaa',但编译器和 VM 可以自由地以这种方式做事。复制的情况似乎也是如此。由于字符串是不可变的,我想没有什么可以说复制字符串不能 return 同一个对象!

在你的第一个例子中,像往常一样,最好的老师就是形象。 #deepCopy 委托给 #shallowCopy,它在某个时候计算 class basicNew: index,并将字符复制到新对象中。因此,这个特定的实现将始终创建一个新对象。

除了Sean DeNigris所说的,第二种情况比较true的原因是,当你一起执行所有三个语句时,编译器想要聪明,只once'aaa' 创建对象并为 ab 共享它们。

如果将其放入一个方法中,也会发生同样的情况 *:

Object subclass: #MyClassA
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'MyApp'


!MyClassA methodsFor: 'testing' stamp: nil prior: nil!
testStrings

    | a b |
    a := 'aaa'
    b := 'aaa'
    ^ a == b
! !
MyClassA testStrings " ==> true"

但如果它们采用不同的方法,不会发生这种情况:

Object subclass: #MyClassB
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'MyApp'


!MyClassB methodsFor: 'testing' stamp: nil prior: nil!
a

    | a |
    a := 'aaa'
    ^ a
! !
!MyClassB methodsFor: 'testing' stamp: nil prior: nil!
b

    | b |
    b := 'aaa'
    ^ b
! !
!MyClassB methodsFor: 'testing' stamp: nil prior: nil!
testStrings

    ^ self a == self b
! !
MyClassB testStrings " ==> false"

那是因为在 Squeak 中,像 stings 这样的文字对象存储在它们定义的方法的方法对象中

*:从技术上讲,每个 DoItPrintIt,即当您仅通过击键执行代码时,都会在 Squeak 中编译为一个方法。

这是我从散布在网上的一本免费 Smalltalk 书籍中了解到的,但我找不到参考资料:

如您所料,class 的实例是内存中的唯一对象。 deepCopy 故意先创建一个对象,然后将现有实例的副本存储在其中。

然而,数字、字符和字符串被 Smalltalk 视为原始数据类型。当 literal 数据(也称为 literals)分配给变量时,首先会根据用户不可见的本地范围字典检查它们,并且保存文字以检查它们是否已添加到其中。如果没有,它们将被添加到字典中,并且变量将指向字典字段。如果之前已经分配了相同的文字数据,则新变量将仅指向包含相同文字的本地作用域字典字段。这意味着分配相同文字的两个或多个变量指向相同的字典字段,因此是相同的对象。这就是为什么你的问题中的第二个比较返回 true。