Swift“可变”字符串真的可变,还是像Java字符串一样?

时间:2014-06-14 16:06:45

标签: string swift immutability

Swift编程语言中,在字符串部分字符串可变性一节中,它说:

  

您可以通过将特定String分配给变量(在这种情况下可以修改)来指示特定var variableString = "Horse" variableString += " and carriage" // variableString is now "Horse and carriage" let constantString = "Highlander" constantString += " and another Highlander" // this reports a compile-time error - a constant string cannot be modified” 是否可以修改(或变异),或者指定为常量(在其中)如果它无法修改):

并提供示例代码:

{{1}}

iBooks here或网页浏览器here中的图书。

在下一段中,它声称"字符串是值类型"。

我的问题:对我来说,这看起来不像是一个可变的字符串。它看起来像我以前在Java(或C#,Python和其他)中使用的:具有可变变量绑定的不可变字符串对象。换句话说,有一个对象"马"然后它创建了一个新的String对象"马和马车"并将其设置为相同的变量。而且由于无法区分对不可变对象的引用与值类型(对吗?)之间的区别,我想知道:为什么他们这样描述它?这些Swift字符串与它在Java中的方式有​​什么区别吗? (或C#,Python,Objective-C / NSString)

6 个答案:

答案 0 :(得分:13)

以某种方式,"可变"和"不可变的"只有在谈论参考类型时才有意义。如果您尝试将其扩展为值类型,则可以将所有值类型视为在功能上等同于" immutable"参考类型。

例如,考虑var类型Int。这可变吗?有些人可能会说,当然 - 你可以改变其可见的价值"通过分配(=)。但是,对var NSNumberNSString NSNumber也是如此 - 您可以通过分配它来更改其可见值。但NSStringArray被描述为不可变的类。

引用类型的实际情况是,为它们分配会导致变量(指针)指向新对象。旧的和新的对象本身都没有被改变,但是由于它指向不同的对象,你可以看到""一个新的价值。

当我们说一个班级是可变的时,我们的意思是什么?是它提供了一个API(方法或参考)来实际更改对象的内容。但我们怎么知道对象已经改变了? (而不是它是一个新的对象?)它是因为我们可以对同一个对象有另一个引用,并且通过另一个引用可以看到通过一个引用对对象的更改。但是这些属性(指向具有指向同一对象的多个指针的不同对象)本身仅适用于引用类型。根据定义,值类型不能具有这样的"共享" (除非"值"的一部分是参考类型,如Int),因此,"可变性的结果"价值类型不可能发生。

因此,如果你创建一个包含整数的不可变类,它在操作上等同于= - 在这两种情况下,更改变量值的唯一方法是赋值( Int)。因此mutating也应该同样被视为"不可变"。

Swift中的值类型稍微复杂一些,因为它们可以有方法,其中一些可以是mutating。因此,如果您可以在值类型上调用mutating方法,它是否可变?但是,如果我们考虑在值类型上调用{{1}}方法作为语法糖来为其分配一个全新值(无论方法是否会将其变异),我们都可以克服这个问题。

答案 1 :(得分:4)

在Swift中,Structures and Enumerations Are Value Types

  

事实上,Swift整数,浮点数,布尔值,字符串,数组和字典中的所有基本类型都是值类型,并在幕后实现为结构。

因此字符串是一种值类型,它在赋值时被复制并且不能有多个引用,但它的基础字符数据存储在可共享的写时复制缓冲区中。 API reference for the String struct说:

  

尽管Swift中的字符串具有值语义,但字符串使用写时复制策略将其数据存储在缓冲区中。然后,该缓冲区可以由字符串的不同副本共享。当多个字符串实例使用相同的缓冲区时,字符串的数据仅在变异时被懒惰地复制。因此,任何变异操作序列中的第一个可能花费O(n)时间和空间。

确实,varlet声明了对可见不可变的字符缓冲区的可变与不可变绑定。

var v1 = "Hi"      // mutable
var v2 = v1        // assign/pass by value, i.e. copies the String struct
v1.append("!")     // mutate v1; does not mutate v2
[v1, v2]           // ["Hi!", "Hi"]

let c1 = v1        // immutable
var v3 = c1        // a mutable copy
// c1.append("!")  // compile error: "Cannot use mutating member on immutable value: 'c1' is a 'let' constant"
v3 += "gh"         // mutates v3, allocating a new character buffer if needed
v3.append("?")     // mutates v3, allocating a new character buffer if needed
[c1, v3]           // ["Hi", "High?"]

这就像非最终版与final Java字符串变量有两个皱纹。

  1. 使用可变绑定,您可以调用变异方法。由于不能对值类型进行任何别名,因此无法判断变异方法是否实际修改了字符缓冲区或重新分配给String变量,但性能影响除外。
  2. 当只有一个String实例使用时,实现可以通过改变字符缓冲区来优化变异操作。

答案 2 :(得分:2)

实际上,Swift的Strings看起来就像Objective-C(不可变)NSString;我在你链接到的文档中找到了这个 -

  

Swift的String类型与Foundation的NSString类无缝桥接。如果您正在使用Cocoa或Cocoa Touch中的Foundation框架,除了本章中描述的String功能外,整个NSString API还可用于调用您创建的任何String值。您还可以将String值与任何需要NSString实例的API一起使用。

答案 3 :(得分:2)

Swift字符串是值,而不是对象。更改值时,它将变为不同的值。因此,在第一种情况下,使用var,您只需将新值分配给同一个变量。鉴于let保证在分配后没有任何其他值,因此它会产生编译错误。

所以为了回答你的问题,Swift字符串接受与Java相同的处理,但被认为是值而不是对象。

答案 4 :(得分:1)

首先,您使用创建新值实例的不可变方法。您需要使用mutatingextend方法来执行变异语义操作。

您在此处所做的是创建一个新的不可变字符串并将其绑定到现有名称。

var variableString = "Horse"
variableString += " and carriage"

这就是在没有任何额外名称绑定的情况下就地改变字符串。

var variableString = "Horse"
variableString.extend(" and carriage")

其次,不可变/可变分离的目的是提供更简单,更安全的编程模型。您可以在不可变数据结构上安全地做出更多假设,并且可以消除许多头痛案例。这有助于优化。如果没有不可变类型,则需要在将整个数据传递给函数时复制它们。否则,原始数据可以被函数变异,这种效果是不可预测的。然后这些函数需要注释为"此函数不会修改传入的数据。"。使用不可变类型,您可以安全地假设该功能无法修改数据,那么您不必复制它。默认情况下,Swift会隐式自动执行此操作。

是的,实际上,可变/不可变的区别仅仅是像Swift这样的高级语言中的界面差异。价值语义只是意味着它不支持身份比较。由于许多细节被抽象出来,内部实施可以是任何东西。 Swift代码注释澄清字符串是使用COW技巧,然后我相信两个不可变/可变接口实际上映射到大多数不可变的实现。无论界面选择如何,我相信你会得到相同的结果。但是,这仍然提供了我提到的不可变数据类型的好处。

然后,代码示例实际上做了同样的事情。唯一的区别是你不能改变在不可变模式下绑定到其名称的原始

答案 5 :(得分:0)

回答原始问题。 Swift中的字符串与Java或C#中的字符串不同(我不了解Python)。它们有两种不同之处。

1)Java和C#中的字符串是引用类型。 Swift(和C ++)中的字符串是值类型。

2)即使引用未声明为final或readonly,Java和C#中的字符串也始终是不可变的。 Swift(和C ++)中的字符串可以是可变的或不可变的,具体取决于声明方式(Swift中的let与var)。

在Java中声明final String str = "foo"时,您是在声明对不可变String对象的不可变引用。 String str = "foo"声明了对不可变String对象的可变引用。

在Swift中声明let str = "foo"时,您是在声明一个不变的字符串。 var str = "foo"声明一个可变字符串。