Swift和mutating结构
有什么我不完全理解什么时候在Swift中改变值types。
正如“Swift编程语言”所述,iBook声明: 默认情况下,值types的属性不能从其实例方法中修改。
所以为了使这成为可能,我们可以在结构和枚举中使用mutating
关键字来声明方法。
对我来说不完全清楚的事情是这样的:你可以从一个结构体外改变一个var,但你不能从它自己的方法改变它。 这对我来说似乎是违反直觉的,因为在面向对象的语言中,你通常试图封装variables,所以只能从内部改变它们。 结构似乎是相反的。 详细说明,下面是一个代码片段:
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) px = 3 //Works from outside the struct! p.moveToX(5, andY: 5)
有谁知道结构之所以不能从内部改变内容的原因,而内容可以很容易在其他地方改变?
可变性属性在存储(常量或variables)上标记,而不是types。 你可以认为结构有两种模式: 可变和不可变 。 如果你给一个不可变的存储分配一个结构值(我们在Swift中把它叫做let
或者constant ),那么这个值就成为不可变的模式,你不能改变这个值的任何状态。 (包括调用任何变异的方法)
如果该值被分配给一个可变的存储区(我们在Swift中称其为var
或variables ),则可以自由修改它们的状态,并允许调用mutating方法。
另外,类没有这个不可变/可变的模式。 国际海事组织,这是因为类通常用来代表参考的实体。 而且可引用的实体通常是可变的,因为以适当的性能以不可变的方式创build和pipe理实体的引用图是非常困难的。 他们可能会稍后添加此function,但至less现在不会。
对于Objective-C程序员来说,可变/不可变概念是非常熟悉的。 在Objective-C中,每个概念都有两个分离的类,但是在Swift中,可以用一个结构来完成。 一半工作。
对于C / C ++程序员来说,这也是非常熟悉的概念。 这正是const
关键字在C / C ++中所做的。
此外,不可变的值可以非常好地优化。 理论上,Swift编译器(或LLVM)可以对let
传递的值执行copy-elision,就像C ++一样。 如果你明智地使用不可变的结构,它将胜过refcounted类。
更新
正如@约瑟夫声称,这并不能说明为什么 ,我多加一点。
结构有两种方法。 简单和突变的方法。 普通方法意味着不可变(或不可变) 。 这种分离只是为了支持不可变的语义。 一个处于不可变模式的对象根本不应该改变它的状态。
那么,不可变的方法必须保证这种语义不变性 。 这意味着它不应该改变任何内部价值。 所以编译器不允许在一个不可变的方法中改变自己的状态。 相比之下,变异方法可以自由修改状态。
然后,你可能会有一个问题, 为什么不可变的是默认的? 这是因为很难预测价值变异的未来状态,而这通常成为头痛和缺陷的主要来源。 许多人都认为,解决scheme是避免可变的东西,然后在C / C ++家族语言及其派生语言中, 默认情况下不可变的数十年来一直在愿望清单之上。
请参阅纯function样式了解更多详情。 无论如何,我们仍然需要可变的东西,因为不可变的东西有一些弱点,讨论这似乎是脱离了主题。
我希望这有帮助。
结构是领域的集合; 如果一个特定的结构实例是可变的,它的字段将是可变的; 如果一个实例是不可变的,它的字段将是不可变的。 因此,必须准备一个结构types,以便任何特定实例的字段可以是可变的或不可变的。
为了使结构方法改变底层结构的字段,这些字段必须是可变的。 如果在不可变结构上调用基础结构的字段变异的方法,它会尝试对不可变字段进行变异。 由于没有什么好的可能来,这种调用需要被禁止。
为此,Swift将结构方法分为两类:修改底层结构的类,因此只能在可变结构实例上调用,而不能修改底层结构,因此可以在可变和不可变实例上调用。 后者的用法可能是更频繁的,因此是默认的。
相比之下,.NET目前(仍然!)没有提供区分结构方法的方法,而结构方法可以修改那些没有的结构方法。 相反,在一个不可变的结构实例上调用一个结构方法会导致编译器创build一个结构实例的可变副本,让该方法做它想要的,并在方法完成时丢弃该副本。 这样做的效果是强制编译器浪费时间复制结构,而不pipe该方法是否修改它,即使添加复制操作几乎不会将语义上不正确的代码转换为语义上正确的代码; 它只会导致以某种方式在语义上错误的代码(修改“不可变的”值)以不同的方式出错(允许代码认为它正在修改结构,但放弃尝试的更改)。 允许struct方法指示它们是否将修改底层结构可以消除对无用的复制操作的需要,并且还确保试图错误的使用将被标记。
Swift结构可以被实例化为常量(通过let
)或variables(通过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
并不总是可以在struct之外访问的。 注意第一行。
let p = Point(x: 1, y: 2) px = 3 //error p.moveToX(5, andY: 5) //error
考虑一下C ++的类比。 Swift中的一个struct方法是mutating
/非mutating
,类似于C ++中的非const
/ const
。 C ++中标记为const
的方法同样不能改变结构。
你可以从一个结构体外改变一个var,但是你不能从它自己的方法改变它。
在C ++中,你也可以“从结构体外部改变一个var” – 但只有当你有一个非const
结构体variables。 如果你有一个const
结构variables,你不能分配给一个var,你也不能调用一个非const
方法。 同样,在Swift中,只有当structvariables不是常量时,才能更改结构的属性。 如果你有一个结构常量,你不能分配给一个属性,也不能调用一个mutating
方法。
小心:未来的外行的条件。
在最基本的代码级别上,这种解释并不严格正确。 然而,一个实际上在Swift上工作的人已经对它进行了审查,他说这已经足够作为一个基本的解释了。
所以我想试着直接回答“为什么”这个问题。
准确地说,当我们可以在不修改关键字的情况下更改结构参数时,为什么我们必须将struct函数标记为mutating
?
所以,大局,与保持Swift 迅速的哲学有很大关系。
你可能会想到pipe理实际物理地址的问题。 当你改变你的地址时,如果有很多人拥有你现在的地址,你必须通知他们你已经移动了。 但是如果没有人有你现在的地址,你可以随意移动,而且没有人需要知道。
在这种情况下,斯威夫特有点像邮局。 如果很多有很多联系人的人移动了很多,那么开销就会很高。 它必须支付大量的人员处理所有这些通知,这个过程需要花费大量的时间和精力。 这就是为什么斯威夫特理想的状态是让城里的每个人尽可能less地接触。 然后,它不需要一个大的工作人员来处理地址变更,它可以做的更快,更好。
这也是为什么斯威夫特人都在谈论价值types与参考types的差异。 从本质上讲,参考types在各地都有“联系”,价值types通常不需要超过一对。 值types是“Swift”-er。
所以回到小图: structs
。 结构在Swift中是很重要的,因为它们可以完成对象可以做的大部分事情,但是它们是值types的。
让我们继续物理地址比喻,通过想象一个生活在某个对象的misterStruct
。 这个比喻在这里得到了一点点的提高,但我认为它仍然是有帮助的。
因此,要build模更改struct
上的variables,让我们说misterStruct
有绿色的头发,并得到一个命令切换到蓝色的头发。 就像我说的那样,这个比喻被证实了,但是发生的一件事情是,不是改变misterStruct
的头发,老人搬出去,一个新的蓝发人进来,新人开始称自己为misterStruct
。 没有人需要更改地址通知,但如果有人看这个地址,他们会看到一个蓝发的人。
现在让我们模拟在一个struct
上调用函数时会发生什么。 在这种情况下,它就像misterStruct
获取诸如changeYourHairBlue()
之类的命令。 所以邮局把指令交给misterStruct
“把你的头发变成蓝色,告诉你什么时候完成”。
如果他像以前一样遵循相同的程序,如果他直接改变variables的时候做了他所做的事情,那么misterStruct
会做的就是搬出自己的房子,然后打电话给一个新发蓝发的人。 但这是问题。
顺序是“把你的头发变成蓝色,当你完成时告诉我”,但是那个绿色的人得到了这个订单。 蓝人进来之后,还要发回一份“工作完成”的通知。 但是这个蓝色的男人对此一无所知。
(真要把这个比喻搞得很糟糕,绿发家伙在技术上发生的事情就是搬出去后立即自杀了。 所以他不能通知任何人任务完成! ]
为了避免这个问题,在这样的情况下,斯威夫特必须直接去那个地址的房子, 实际上改变当前居民的头发 。 这是一个完全不同的过程,而不是发送一个新的人。
这就是为什么Swift希望我们使用mutating
关键字!
最终的结果看起来与任何必须引用结构的东西是一样的:房子的居民现在有蓝色的头发。 但是实现它的过程实际上是完全不同的。 它看起来像是在做同样的事情,但它正在做一个非常不同的事情。 Swift的结构总的来说是做不到的。
所以,为了给这个可怜的编译器一点帮助,也不必弄清楚一个函数是否会自变形地为每一个结构函数赋值,我们被要求遗憾地使用mutating
关键字。
实质上,为了帮助Swift保持快速,我们都必须尽我们的一份力量。 🙂
编辑:
嘿,伙计/笨蛋谁低估了我,我刚刚重写了我的答案。 如果你和你坐得更好,你会删除downvote吗?