快速和变异结构


96

对于Swift中的值类型突变,我有些不了解。

正如iBook的“ Swift编程语言”所言: 默认情况下,不能从其实例方法中修改值类型的属性。

因此,为了使之成为可能,我们可以mutating在structs和enums中使用关键字声明方法。

对我来说还不完全清楚的事情是:您可以从结构外部更改var,但是不能从其自己的方法更改它。这对我来说似乎违反直觉,因为在面向对象的语言中,您通常尝试封装变量,以便只能从内部进行更改。对于结构,这似乎是另一回事。详细来说,这是一个代码片段:

struct Point {
    var x = 0, y = 0
    mutating func moveToX(x: Int, andY y:Int) { //Needs to be a mutating method in order to work
        self.x = x
        self.y = y
    }
}

var p = Point(x: 1, y: 2)
p.x = 3 //Works from outside the struct!
p.moveToX(5, andY: 5) 

有谁知道为什么结构不能从自己的上下文内部更改其内容而在其他地方可以轻松更改其内容的原因?

Answers:


80

可变性属性标记在存储(常量或变量)上,而不是类型上。您可以认为struct有两种模式:可变不可变。如果将结构值分配给不可变存储(在Swift中称为const let常量),则该值将变为不可变模式,并且您无法更改值的任何状态。(包括调用任何变异方法)

如果将值分配给可变存储(我们在Swift中称其为变量var变量),则可以随意修改它们的状态,并允许调用mutating方法。

另外,类没有这种不可变/可变模式。IMO,这是因为类通常用于表示可引用的实体。可引用实体通常是可变的,因为很难以不可变的方式以适当的性能制作和管理实体的引用图。他们可能会在以后添加此功能,但至少现在不会。

对于Objective-C程序员,可变/不可变的概念非常熟悉。在Objective-C中,每个概念都有两个分离的类,但是在Swift中,您可以使用一个结构来实现。辛苦了

对于C / C ++程序员来说,这也是一个非常熟悉的概念。这正是constC / C ++中的关键字。

同样,可以很好地优化不可变值。从理论上讲,Swift编译器(或LLVM)可以对传递的值执行复制删除let,就像在C ++中一样。如果明智地使用不可变结构,它将胜过引用过的类。

更新资料

正如@Joseph声称的那样,这并没有提供原因,所以我要补充一点。

结构有两种方法。简单变异方法。普通方法意味着不可变(或不可变)。存在这种分隔仅是为了支持不可变的语义。处于不可变模式的对象根本不应更改其状态。

然后,不变方法必须保证这种语义不变性。这意味着它不应更改任何内部值。因此,编译器不允许使用不可变方法更改自身的任何状态。相反,变异方法可以自由修改状态。

然后,您可能会问为什么默认值是不可变的?那是因为很难预测变量值的未来状态,并且通常成为头痛和漏洞的主要来源。许多人同意,该解决方案是避免使用易变的东西,然后默认情况下,几十年来使用C / C ++家族语言及其衍生版本时,不变的东西一直是人们的最爱。

有关更多详细信息,请参见纯功能样式。无论如何,我们仍然需要可变的东西,因为不可变的东西有一些弱点,关于它们的讨论似乎不在主题之列。

我希望这有帮助。


2
因此,基本上,我可以这样解释:结构事先不知道它是可变的还是不可变的,因此除非有不同说明,否则它假定不可变。看起来确实有道理。这样对吗?
Mike Seghers 2014年

1
@MikeSeghers我认为这是有道理的。
eonil 2014年

3
虽然到目前为止这是两个答案中最好的,但我认为它不能回答为什么,默认情况下,无法从其实例方法中修改值类型的属性。您暗示它是因为结构默认为不可变的,但是我不认为这是真的
Joseph Knight

4
@JosephKnight并不是默认的结构是不可变的。结构的方法默认为不可变的。以相反的假设将其视为C ++。在C ++方法中,默认为非常量this,const如果要使其具有“ const this”,并且必须在常量实例上允许使用,则必须向该方法显式添加a (如果实例为const,则不能使用常规方法)。Swift使用相反的默认值执行相同的操作:默认情况下,方法具有“ const this”,如果常量实例必须禁止使用该方法,则将其标记为其他方法。
模拟文件

3
@golddove是的,你不能。你不应该 您始终需要创建或导出该值的新版本,并且您的程序需要设计为有意采用这种方式。存在防止这种意外突变的功能。同样,这是功能样式编程的核心概念之一。
2014年

25

结构是字段的集合;如果特定结构实例是可变的,则其字段将是可变的;如果实例是不可变的,则其字段将是不可变的。因此,必须准备一种结构类型,以使任何特定实例的字段都是可变的或不可变的。

为了使结构方法能够改变基础结构的字段,这些字段必须是可变的。如果在不可变结构上调用使基础结构的字段发生变化的方法,则它将尝试使不可变字段发生变化。由于没有任何好处,因此必须禁止这种调用。

为了实现这一点,Swift将结构方法分为两类:修改基础结构的方法,因此只能在可变结构实例上调用;那些不修改基础结构的方法,因此应在可变实例和不可变实例上都可以调用。后者的用法可能更频繁,因此是默认用法。

相比之下,.NET当前(仍然!)无法区分将修改结构的结构方法与未修改结构的方法。取而代之的是,在不可变的结构实例上调用结构方法将使编译器生成该结构实例的可变副本,让该方法对它执行任何操作,并在该方法完成后丢弃该副本。这具有迫使编译器浪费时间复制结构而不管方法是否对其进行修改的作用,即使添加复制操作几乎永远不会将语义不正确的代码转换为语义正确的代码。只会导致以一种方式(修改“不可变”值)在语义上错误的代码以另一种方式(允许代码认为其在修改结构)是错误的,但放弃尝试的更改)。允许struct方法指示它们是否将修改基础结构可以消除对无用复制操作的需求,并且还可以确保对尝试的错误用法进行标记。


1
与.NET的比较以及没有mutating关键字的示例使我很清楚。mutating关键字会带来任何好处的原因。
Binarian

19

小心:外行的条件在前面。

在最严格的代码级别上,这种解释并不十分正确。但是,已经有一个实际从事Swift的人对它进行了审查,他说这足以作为基本的解释。

因此,我想尝试简单直接地回答“为什么”的问题。

准确地说:当我们可以在不修改任何关键字的情况下更改结构参数时,为什么还要将结构函数标记为mutating

因此,总的来说,这与保持Swift 迅速发展的哲学息息相关。

您可能会认为它类似于管理实际物理地址的问题。更改地址时,如果有很多人拥有您的当前住所,则您必须通知所有住所。但是,如果没有人知道您的当前地址,那么您可以将其移至所需的任何地方,而无需知道。

在这种情况下,Swift有点像邮局。如果很多人有很多联系,四处走动,这将带来很大的开销。它必须支付大量人员来处理所有这些通知,并且该过程占用大量时间和精力。这就是为什么Swift的理想状态是让镇上的每个人都尽可能少地联系。然后,它不需要大量人员来处理地址更改,并且它可以更快,更好地完成其他所有工作。

这也是为什么Swift-Folks都热衷于值类型与引用类型的原因。从本质上讲,引用类型遍布整个地方的“联系”,而值类型通常不需要几个。值类型为“ Swift” -er。

所以回到小图片:structs。在Swift中,结构很重要,因为结构可以执行对象可以执行的大多数操作,但是它们是值类型。

让我们通过想象一个misterStruct存在于中的物理地址来继续类比someObjectVille。这里的类比有点奇怪,但我认为它仍然很有帮助。

因此,要为在上更改变量的模型建模struct,假设它misterStruct有绿色的头发,并获得切换为蓝色头发的命令。就像我说的那样,这个比喻有点眨眼,但是发生的事情是,misterStruct老人不再换头发,而是搬走了,而一个留着蓝色头发新人进入了,那个新人开始自称misterStruct。无需更改地址通知,但如果有人查看该地址,他们会看到一个蓝头发的家伙。

现在,让我们对在上调用函数时的情况进行建模struct。在这种情况下,就像misterStruct获得了诸如这样的订单changeYourHairBlue()。因此,邮局发出的指示是misterStruct“将头发变成蓝色,并在完成后告诉我”。

如果他遵循与以前相同的例程,并且如果他正在做的操作(直接更改变量时),那么misterStruct要做的就是搬出自己的房子,并召集一个有着蓝色头发的新人。但这就是问题所在。

命令是“把头发变成蓝色,然后告诉我完成的时间”,但是得到命令的是绿色人。蓝色家伙进入后,仍然必须发送回“作业完成”通知。但是这个蓝家伙对此一无所知。

[为了使这个比喻真正令人震惊,从技术上讲,绿头发的家伙搬出去后立即自杀。因此,也无法通知任何人任务已完成]

为了避免这个问题,在这种情况下,Swift必须直接进入该地址处的房子,并实际上要更改当前居民的头发。这与发送新人完全不同。

这就是为什么Swift希望我们使用mutating关键字!

最终结果对于必须引用该结构的任何事物而言都是相同的:房屋的居民现在已经染成蓝色。但是实现它的过程实际上是完全不同的。看起来它在做同一件事,但是在做另一件事。它所做的事情通常Swift结构永远不会做的。

因此,要给性能不佳的编译器一些帮助,而不必让它不得不找出某个函数是否对每个struct函数都struct自行进行了突变我们就必须感到遗憾并使用该mutating关键字。

本质上,要帮助Swift保持敏捷,我们都必须尽自己的一份力量。:)

编辑:

嘿小伙子/ dudette否决了我,我只是完全重写了我的回答。如果情况更好,您是否会删除弃权票?


1
这是<f-word-here>如此惊人的写法!所以,很容易理解。我已经在互联网上搜寻了,这就是答案(好吧,无论如何都不必阅读源代码)。非常感谢您抽出宝贵的时间来编写它。
Vaibhav

1
我只是想确认自己理解正确:可以说,当在Swift中直接更改Struct实例的属性时,该Struct实例将被“终止”,而一个具有更改后的属性的新实例将被“创建”。在幕后?当我们在实例中使用一种变异方法来更改该实例的属性时,这种终止和重新初始化过程不会发生吗?
特德

@Teo,我希望我能给出明确的答案,但是我能说的最好的一点是,我将这个类比推给了一个实际的Swift开发人员,他们说“基本上是正确的”,这意味着从技术意义上讲还有更多。但是,有了这种理解-不仅限于此-从广义上讲,这就是类推的意思。
榨汁

8

Swift结构可以实例化为常量(通过let)或变量(通过var

考虑Swift的Array结构(是的,这是一个结构)。

var petNames: [String] = ["Ruff", "Garfield", "Nemo"]
petNames.append("Harvey") // ["Ruff", "Garfield", "Nemo", "Harvey"]

let planetNames: [String] = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
planetNames.append("Pluto") //Error, sorry Pluto. No can do

为什么附加项对行星名称无效?因为append用mutating关键字标记。并且由于planetNames使用声明了let,因此标记的所有方法都是禁止的。

在您的示例中,编译器可以通过在之外分配其一个或多个属性来告诉您正在修改该结构init。如果稍微更改代码,您会看到,x并且y在结构外部并不总是可以访问。注意let第一行上的。

let p = Point(x: 1, y: 2)
p.x = 3 //error
p.moveToX(5, andY: 5) //error

5

考虑与C ++的类比。Swift中mutating/ not-的struct方法mutating类似于C ++中non const/的方法constconst同样,用C ++ 标记的方法不能使该结构变异。

您可以从结构外部更改var,但不能从其自己的方法更改它。

在C ++中,您也可以“从struct外部更改var”,但前提是您具有非conststruct变量。如果具有conststruct变量,则不能分配给var,也不能调用非const方法。同样,在Swift中,仅当struct变量不是常量时,才可以更改struct的属性。如果具有结构常量,则不能分配给属性,也不能调用mutating方法。


1

当我开始学习Swift时,我想知道同样的事情,这些答案中的每一个,虽然可能会增加一些见解,但它们本身都有些罗and和令人困惑。我认为您问题的答案实际上非常简单……

您的结构体中定义的mutation方法需要获得许可,以修改将来将要创建的每个实例。如果这些实例之一被分配给一个不变常量,该let怎么办?哦哦 为了保护你自己从(并让编辑器和编译器知道你在做什么尝试做),你不得不当你想给一个实例方法这种权力是明确的。

相比之下,从结构外部设置属性的操作是在该结构的已知实例上进行的。如果将其分配给常量,则Xcode将在您在方法调用中键入内容后立即通知您。

当我开始使用它时,这就是我钟爱Swift的一件事-在键入错误时会收到警告。确保排除疑难解答的JavaScript错误!


1

SWIFT:在结构中使用变异函数

Swift程序员以无法在Struct方法中修改其属性的方式开发Structs。例如,检查下面给出的代码

struct City
{
  var population : Int 
  func changePopulation(newpopulation : Int)
  {
      population = newpopulation //error: cannot modify property "popultion"
  }
}
  var mycity = City(population : 1000)
  mycity.changePopulation(newpopulation : 2000)

在执行上述代码时,由于尝试将新值分配给Struct City的属性人口,我们会收到错误消息。默认情况下,Structs属性不能在其自己的方法内进行突变。这是Apple开发人员构建它的方式,因此默认情况下Structs具有静态性质。

我们该如何解决?有什么选择?

突变关键字:

将函数声明为Struct内部的变量使我们能够更改Structs中的属性。上面代码的第5行:

mutating changePopulation(newpopulation : Int)

现在,我们可以在价值分配newpopulation财产人口的方法的范围之内。

注意 :

let mycity = City(1000)     
mycity.changePopulation(newpopulation : 2000)   //error: cannot modify property "popultion"

如果我们对Struct对象使用let而不是var,那么我们将无法对任何属性的值进行突变。这也是为什么在尝试使用let实例调用变异函数时会出错的原因。因此,每当更改属性值时,最好使用var。

很想听听您的意见和想法.....

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.