我正在阅读文档,我经常对语言的一些设计决策摇头。但真正让我困惑的是数组是如何处理的。

我冲到操场上试了试。你也可以试试。第一个例子:

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没有看到变化,即使长度没有改变。

总而言之,如果更改了一个元素,对数组的常见引用就会看到变化,但如果更改了多个元素或附加项,则会生成一个副本。

在我看来,这是一个非常糟糕的设计。我这样想对吗?有什么原因让我不明白数组为什么应该这样做吗?

编辑:数组已经改变,现在有值语义。理智多了!


当前回答

在后来的Swift版本中,数组的行为有什么变化吗?我只是运行你的例子:

var a = [1, 2, 3]
var b = a
a[1] = 42
a
b

结果是[1,42,3]和[1,2,3]

其他回答

其行为与Array极其相似。. net中的Resize方法。要了解发生了什么,看看历史可能会有所帮助。C, c++, Java, c#和Swift中的token。

在C语言中,结构只不过是变量的聚合。应用。结构类型的变量将访问存储在结构中的变量。指向对象的指针不包含变量的聚合,而是标识它们。如果指针标识一个结构,->操作符可用于访问存储在该指针标识的结构中的变量。

在c++中,结构和类不仅可以聚合变量,还可以将代码附加到变量上。使用。调用一个方法将对一个变量要求该方法对变量本身的内容进行操作;在标识对象的变量上使用->将要求该方法对该变量标识的对象进行操作。

In Java, all custom variable types simply identify objects, and invoking a method upon a variable will tell the method what object is identified by the variable. Variables cannot hold any kind of composite data type directly, nor is there any means by which a method can access a variable upon which it is invoked. These restrictions, although semantically limiting, greatly simplify the runtime, and facilitate bytecode validation; such simplifications reduced the resource overhead of Java at a time when the market was sensitive to such issues, and thus helped it gain traction in the marketplace. They also meant that there was no need for a token equivalent to the . used in C or C++. Although Java could have used -> in the same way as C and C++, the creators opted to use single-character . since it was not needed for any other purpose.

In C# and other .NET languages, variables can either identify objects or hold composite data types directly. When used on a variable of a composite data type, . acts upon the contents of the variable; when used on a variable of reference type, . acts upon the object identified by it. For some kinds of operations, the semantic distinction isn't particularly important, but for others it is. The most problematical situations are those in which a composite data type's method which would modify the variable upon which it is invoked, is invoked on a read-only variable. If an attempt is made to invoke a method on a read-only value or variable, compilers will generally copy the variable, let the method act upon that, and discard the variable. This is generally safe with methods that only read the variable, but not safe with methods that write to it. Unfortunately, .does has not as yet have any means of indicating which methods can safely be used with such substitution and which can't.

In Swift, methods on aggregates can expressly indicate whether they will modify the variable upon which they are invoked, and the compiler will forbid the use of mutating methods upon read-only variables (rather than having them mutate temporary copies of the variable which will then get discarded). Because of this distinction, using the . token to call methods that modify the variables upon which they are invoked is much safer in Swift than in .NET. Unfortunately, the fact that the same . token is used for that purpose as to act upon an external object identified by a variable means the possibility for confusion remains.

如果有一台时光机,回到c#和/或Swift的创建时期,人们就可以通过让语言使用。和->令牌更接近c++的用法。可以使用聚合类型和引用类型的方法。作用于调用它们的变量,->作用于一个值(对于组合)或由此标识的东西(对于引用类型)。然而,这两种语言都不是这样设计的。

In C#, the normal practice for a method to modify a variable upon which it is invoked is to pass the variable as a ref parameter to a method. Thus calling Array.Resize(ref someArray, 23); when someArray identifies an array of 20 elements will cause someArray to identify a new array of 23 elements, without affecting the original array. The use of ref makes clear that the method should be expected to modify the variable upon which it is invoked. In many cases, it's advantageous to be able to modify variables without having to use static methods; Swift addresses that means by using . syntax. The disadvantage is that it loses clarify as to what methods act upon variables and what methods act upon values.

注意,数组的语义和语法在Xcode beta 3版本中发生了改变,所以这个问题不再适用。以下答案适用于beta 2:


这是出于性能考虑。基本上,他们尽量避免复制数组(并声称“类似c的性能”)。引用语言书:

对于数组,只有在执行可能修改数组长度的操作时才会发生复制。这包括追加、插入或删除项,或使用范围下标替换数组中的一段项。

我同意这有点令人困惑,但至少对它的工作原理有一个清晰而简单的描述。

该部分还包括有关如何确保数组被唯一引用、如何强制复制数组以及如何检查两个数组是否共享存储的信息。

为此我使用.copy()。

    var a = [1, 2, 3]
    var b = a.copy()
     a[1] = 42 

我发现:当且仅当操作有可能改变数组的长度时,该数组将是引用数组的可变副本。在上一个例子中,f[0..2]如果索引数量很多,操作就有可能改变它的长度(可能是不允许重复的),所以它会被复制。

var e = [1, 2, 3]
var f = e
e[0..2] = [4, 5]
e // 4,5,3
f // 1,2,3


var e1 = [1, 2, 3]
var f1 = e1

e1[0] = 4
e1[1] = 5

e1 //  - 4,5,3
f1 // - 4,5,3

在后来的Swift版本中,数组的行为有什么变化吗?我只是运行你的例子:

var a = [1, 2, 3]
var b = a
a[1] = 42
a
b

结果是[1,42,3]和[1,2,3]