我正在阅读文档,我经常对语言的一些设计决策摇头。但真正让我困惑的是数组是如何处理的。
我冲到操场上试了试。你也可以试试。第一个例子:
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没有看到变化,即使长度没有改变。
总而言之,如果更改了一个元素,对数组的常见引用就会看到变化,但如果更改了多个元素或附加项,则会生成一个副本。
在我看来,这是一个非常糟糕的设计。我这样想对吗?有什么原因让我不明白数组为什么应该这样做吗?
编辑:数组已经改变,现在有值语义。理智多了!
其行为与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.