我正在阅读文档,我经常对语言的一些设计决策摇头。但真正让我困惑的是数组是如何处理的。
我冲到操场上试了试。你也可以试试。第一个例子:
var a = [1, 2, 3]
var b = a
a[1] = 42
a
b
这里a和b都是[1,42,3],我可以接受。数组被引用-好的!
现在来看这个例子:
var c = [1, 2, 3]
var d = c
c.append(42)
c
d
c是[1,2,3,42]但d是[1,2,3]。也就是说,d在上一个例子中看到了变化,但在这个例子中没有看到。文档上说这是因为长度变了。
现在,看看这个:
var e = [1, 2, 3]
var f = e
e[0..2] = [4, 5]
e
f
E是[4,5,3],很酷。有一个多索引替换是很好的,但是f STILL没有看到变化,即使长度没有改变。
总而言之,如果更改了一个元素,对数组的常见引用就会看到变化,但如果更改了多个元素或附加项,则会生成一个副本。
在我看来,这是一个非常糟糕的设计。我这样想对吗?有什么原因让我不明白数组为什么应该这样做吗?
编辑:数组已经改变,现在有值语义。理智多了!
对我来说,如果你先用变量替换常量,这更有意义:
a[i] = 42 // (1)
e[i..j] = [4, 5] // (2)
第一行永远不需要改变a的大小,特别是,它永远不需要做任何内存分配。不管i的值是多少,这都是一个轻量级操作。如果你把a想象成一个指针,它可以是一个常量指针。
第二行可能要复杂得多。根据i和j的值,您可能需要进行内存管理。如果你想象e是一个指向数组内容的指针,你不能再假设它是一个常量指针;您可能需要分配一个新的内存块,将数据从旧的内存块复制到新的内存块,并更改指针。
看起来语言设计者们已经试图保持(1)尽可能的轻量级。因为(2)可能涉及复制,他们已经采取了解决方案,它总是表现得好像你做了一个复制。
这很复杂,但我很高兴他们没有让它变得更复杂,例如,特殊情况下,如“如果在(2)I和j是编译时常数,编译器可以推断出e的大小不会改变,那么我们不复制”。
最后,基于我对Swift语言设计原则的理解,我认为一般规则如下:
默认情况下,在所有地方都使用常量(let) always,这样就不会有什么大的意外了。
只有在绝对必要的情况下才使用变量(var),并在这些情况下要小心,因为会出现令人惊讶的情况[这里:在某些情况下,但不是所有情况下,数组的奇怪隐式副本]。