当谈到在Swift中改变值类型时,有些事情我不完全理解。
正如“Swift编程语言”iBook所述:默认情况下,不能从其实例方法中修改值类型的属性。
因此,为了实现这一点,我们可以在结构和枚举中使用mutating
关键字声明方法。
我不太清楚的是:你可以从结构体外部更改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)
有谁知道为什么结构不能从自己的上下文中更改其内容,而内容可以在其他地方轻松更改?
可变属性标记在存储(常量或变量)上,而不是类型上。你可以认为struct有可变和不可变两种模式。如果你将一个struct值分配给一个不可变的存储(我们称之为let
或Swift中的常量),该值将成为不可变模式,你不能改变值中的任何状态。(包括调用任何可变方法)
如果将值分配给可变存储(我们将其称为var
或Swift中的变量),您可以自由修改它们的状态,并且允许调用mutating方法。
此外,类没有这种不可变/可变模式。IMO,这是因为类通常用于表示可引用实体。可引用实体通常是可变的,因为很难以具有适当性能的不可变方式制作和管理实体的引用图。他们可能会在以后添加此功能,但至少现在不会。
对于Objective-C程序员来说,可变/不可变概念非常熟悉。在Objective-C中,每个概念都有两个独立的类,但在Swift中,您可以使用一个结构来做到这一点。一半工作。
对于C/C程序员来说,这也是非常熟悉的概念。这正是const
关键字在C/C中的作用。
此外,不可变值可以得到很好的优化。理论上,Swift编译器(或LLVM)可以对let
传递的值执行复制省略,就像在C中一样。如果你明智地使用不可变结构,它将优于重新计算的类。
正如@Joseph声称的那样,这并没有提供原因,我补充了一点。
结构有两种方法。平原方法和变异方法。平原方法意味着不可变(或不变异)。这种分离的存在只是为了支持不可变语义学。处于不可变模式的对象根本不应该改变其状态。
然后,不可变方法必须保证这种语义不变性。这意味着它不应该改变任何内部值。因此编译器不允许在不可变方法中对自身进行任何状态更改。相比之下,变异方法可以自由修改状态。
然后,你可能会有一个问题,为什么不可变是默认值?那是因为很难预测突变值的未来状态,而这通常是令人头疼和错误的主要来源。许多人同意解决方案是避免可变的东西,然后默认情况下不可变在C/C家族语言及其派生语言的愿望清单上已经有几十年了。
更多细节请看纯函数式风格。不管怎样,我们仍然需要可变的东西,因为不可变的东西有一些弱点,讨论它们似乎脱离了话题。
警告:外行人的条件。
这个解释在最基本的代码级别上并不完全正确。然而,它已经被一个真正在Swift上工作的人审查过,他说它作为一个基本解释已经足够好了。
所以我想试着简单直接地回答“为什么”这个问题。
准确地说:当我们可以在不修改任何关键字的情况下更改struct参数时,为什么我们必须将struct函数标记为mutating
?
所以,从大局来看,这与保持Swift快速发展的哲学有很大关系。
你可以把它想象成管理实际物理地址的问题。当你改变你的地址时,如果有很多人有你现在的地址,你必须通知他们你已经搬家了。但是如果没有人有你现在的地址,你可以随心所欲地搬家,没有人需要知道。
在这种情况下,Swift有点像邮局。如果许多拥有大量联系人的人经常四处走动,它的开销就非常高。它必须支付大量人员来处理所有这些通知,这个过程占用了大量时间和精力。这就是为什么Swift的理想状态是让镇上的每个人都拥有尽可能少的联系人。然后它不需要大量人员来处理地址更改,它可以更快更好地完成其他事情。
这也是为什么Swift人都在谈论值类型和引用类型。本质上,引用类型到处都是“联系人”,值类型通常不需要超过几个。值类型是“Swift”-er。
回到小图片:structs
。结构在Swift中很重要,因为它们可以做对象可以做的大部分事情,但它们是值类型。
让我们继续物理地址类比,想象一个位于某个ObjectVille
中的错误结构
。这个类比在这里有点混乱,但我认为它仍然有帮助。
因此,要对更改struct
上的变量进行建模,假设先生结构
有绿色头发,并得到一个转换为蓝色头发的命令。就像我说的,这个类比被打乱了,但发生的事情是,不是更改先生结构
的头发,老人搬出来,一个蓝色头发的新人搬进来,这个新人开始称自己为先生结构
。没有人需要收到地址更改通知,但如果有人看那个地址,他们会看到一个蓝色头发的人。
现在让我们建模当你调用struct
上的函数时会发生什么。在这种情况下,就像先生结构
得到了一个订单,比如changeYourHeadBlue()
。所以邮局将指令传递给先生结构
“去把你的头发改成蓝色,完成后告诉我。”
如果他遵循和以前一样的例行公事,如果他正在做他在直接更改变量时所做的事情,先生结构
会做的就是搬出自己的房子,找一个蓝头发的新人。但这就是问题所在。
命令是“去把你的头发改成蓝色,完成后告诉我”,但得到命令的是绿色的家伙。蓝色家伙搬进来后,仍然需要发送“工作完成”通知。但是蓝色家伙对此一无所知。
「这个比喻真的很可怕,绿发人技术上的情况是,他搬出去后,立即自杀。所以他也不能通知任何人任务完成了!」
为了避免这个问题,在这种情况下,斯威夫特必须直接去那个地址的房子,实际上改变现在居民的头发。这是一个完全不同的过程,不仅仅是派一个新人进来。
这就是为什么Swift希望我们使用mutating
关键字!
最终的结果对于任何引用结构体的东西都是一样的:房子的居民现在有了蓝色的头发。但是实现它的过程实际上是完全不同的。看起来它在做同样的事情,但是它在做一件非常不同的事情。它在做一件Swift结构体通常不会做的事情。
因此,为了给可怜的编译器一点帮助,而不是让它必须自己弄清楚一个函数是否会改变struct
,对于每一个struct函数,我们都被要求同情并使用mutating
关键字。
本质上,为了帮助Swift保持快速,我们都必须尽自己的一份力量。:)
结构体是字段的聚合;如果一个特定的结构体实例是可变的,它的字段将是可变的;如果一个实例是不可变的,它的字段将是不可变的。因此,必须为任何特定实例的字段可能是可变的或不可变的可能性准备好结构体类型。
为了使结构体方法改变底层结构体的字段,这些字段必须是可变的。如果在不可变结构上调用改变底层结构体字段的方法,它将尝试改变不可变字段。由于这样做没有好处,因此需要禁止这种调用。
为了实现这一点,Swift将结构方法分为两类:一类是修改底层结构的方法,因此只能在可变结构实例上调用;另一类是不修改底层结构的方法,因此应该在可变和不可变实例上都可以调用。后一种用法可能更频繁,因此是默认用法。