当谈到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)
有谁知道为什么结构体不能在自己的上下文中更改内容,而内容却可以在其他地方轻松更改?
8条答案
按热度按时间9lowa7mx1#
可变性属性标记在存储器上(常量或变量),而不是类型上。你可以认为struct有两种模式:* mutable * 和 * immutable *.如果你把一个结构值赋给一个不可变的存储器(我们在Swift中称之为
let
或constant),那么这个值就变成了不可变模式,你不能改变这个值的任何状态(包括调用任何变异方法)。如果值被赋给一个可变存储(我们称之为
var
或Swift中的 * variable *),你可以自由地修改它们的状态,并且允许调用mutating方法。另外,类没有这种不可变/可变模式。请注意,这是因为类通常用来表示 * reference-able * 实体。而reference-able实体通常是可变的,因为很难以不可变的方式制作和管理实体的引用图并保持适当的性能。他们可能会在以后添加这个特性,但至少现在不会。
对于Objective-C程序员来说,可变/不可变的概念是非常熟悉的。在Objective-C中,每个概念有两个独立的类,但在Swift中,你可以用一个结构来完成这一点。
对于C/C ++程序员来说,这也是非常熟悉的概念,这正是
const
关键字在C/C 中的作用。同样,不可变值也可以得到很好的优化。理论上,Swift编译器(或LLVM)可以对
let
传递的值执行复制省略,就像在C中一样。如果明智地使用不可变结构,它将优于引用计数类。更新
正如@Joseph声称的那样,这并没有提供"为什么",我再补充一点。
结构有两种方法。* plain * 和 * mutating * 方法。* plain * 方法意味着 * immutable(或non-mutating)。这种分离的存在只是为了支持 * immutable * 语义。处于immutable模式的对象根本不应该改变它的状态。
然后,不可变方法必须保证这种"语义不变性"。这意味着它不应该改变任何内部值。因此编译器不允许在不可变方法中改变任何自身的状态。相反,可变方法可以自由地修改状态。
然后,你可能会问 * 为什么默认值是不可变的? 那是因为很难预测值的未来变化状态,这通常会成为令人头疼和bug的主要来源。许多人同意解决方案是避免可变的东西,然后 * 默认值不可变 * 在C/C ++家族语言及其衍生语言中几十年来一直排在愿望清单的首位。
更多的细节请看purely functional style。不管怎样,我们仍然需要可变的东西,因为不变的东西有一些弱点,讨论它们似乎离题了。
qgzx9mmu2#
这种解释在最本质的代码层面上并不完全正确,但是它已经被一个真正在Swift上工作的人审查过了,他说作为一个基本的解释已经足够好了。
所以我想试着简单直接地回答"为什么"的问题。
准确地说:* 为什么我们必须将结构函数标记为
mutating
,而我们可以在不使用任何修改关键字的情况下更改结构参数?*所以,从大的方面来说,这与保持斯威夫特快速的哲学有很大关系。
你可以把它想象成管理实际地址的问题。当你改变你的地址时,如果有很多人有你现在的地址,你必须通知他们你已经搬家了。但是如果没有人有你现在的地址,你可以搬到任何你想搬到的地方,没有人需要知道。
在这种情况下,Swift有点像邮局。如果有很多联系人的人经常搬家,它的管理费用就很高。它必须支付大量的员工来处理所有这些通知。而且这个过程需要花费大量的时间和精力。这就是为什么斯威夫特的理想状态是让镇上的每个人都尽可能少地接触。处理地址变更不需要很多员工,而且它可以更快更好地处理其他事情。
这也是为什么Swift的人都在为值类型和引用类型争论不休。本质上,引用类型到处都有"联系人",而值类型通常不需要超过两个。值类型是"Swift"-er。
回到小画面:结构体在Swift中很重要,因为它们可以做对象能做的大部分事情,但它们是值类型。
让我们继续物理地址的类比,想象一个
misterStruct
住在someObjectVille
中,这个类比在这里有点错误,但是我认为它仍然是有用的。为了模拟在
struct
上改变一个变量,假设misterStruct
有一个绿色的头发,然后得到一个命令,把它变成蓝色的头发,这个类比有点不靠谱,就像我说的,但是实际上不是改变misterStruct
的头发,而是老的那个人移出,一个 * 蓝色头发的新人 * 移入,这个新的人开始自称misterStruct
。没有人需要得到地址更改通知,但如果有人查看这个地址,他们会看到一个蓝头发的人。现在让我们来模拟一下当你在
struct
上调用一个函数时会发生什么,在这个例子中,就像misterStruct
得到了一个类似changeYourHairBlue()
的命令,所以邮局给misterStruct
发送了一条指令"去把你的头发换成蓝色,完成后告诉我"。如果他和以前一样,如果他做的是直接改变变量时做的事情,那么
misterStruct
会做的就是 * 搬出自己的房子 ,然后叫一个蓝头发的人进来,但这就是问题所在。命令是"去把你的头发换成蓝色,完成后告诉我",但接到命令的是那个 * 绿色 * 的家伙。在蓝色的家伙搬进来后,"工作完成"的通知仍然要被发回。 但蓝色的家伙对此一无所知。*
[To我真的把这个比喻打得很糟糕,从技术上讲,绿头发的家伙在搬出去后,* 他立即自杀了。* 所以 * 他 * 也不能通知任何人任务完成了 *! ]
为了避免这个问题,在这样的情况下,斯威夫特必须直接去那个地址的房子,并真正改变当前居民的头发。这是一个完全不同的过程,而不仅仅是派一个新的家伙。
这就是为什么Swift希望我们使用
mutating
关键字!对于任何必须引用该结构体的对象,最终结果看起来都是一样的:房子里的居民现在是蓝头发,但是实现它的过程实际上是完全不同的,看起来它在做同样的事情,但是它在做一件非常不同的事情,它在做一件 * Swift结构体通常不会做的事情 。
因此,为了给糟糕的编译器一点帮助,而不是让它自己去判断一个函数是否变异了
struct
, 对于每一个结构体函数, 我们被要求怜悯并使用mutating
关键字。从本质上讲,为了帮助斯威夫特保持敏捷,我们都必须尽自己的一份力量
2ledvvac3#
结构是字段的集合;如果特定结构示例是可变的,则其字段也将是可变的;如果一个示例是不可变的,那么它的字段也是不可变的。2因此,必须为任何特定示例的字段可能是可变的或不可变的而准备一个结构类型。
为了使结构方法能够改变底层结构的字段,这些字段必须是可变的。如果一个改变底层结构字段的方法被调用到一个不可变的结构上,它会试图改变不可变的字段。因为这样做不会有什么好处,所以需要禁止这样的调用。
为了实现这一点,Swift将结构方法分为两类:那些修改底层结构的,因此只能在可变结构示例上调用,以及那些不修改底层结构的,因此应该在可变和不可变示例上都可以调用。后一种用法可能更频繁,因此是默认用法。
相比之下,.NET目前(仍然!)没有提供区分修改结构的结构方法和不修改结构的方法的方法,相反,在不可变的结构示例上调用结构方法会导致编译器生成结构示例的一个可变副本,让方法做它想做的任何事情,并在方法完成时丢弃副本。这会迫使编译器浪费时间来复制结构,无论方法是否修改它。即使添加复制操作几乎不会将语义不正确的代码转变为语义正确的代码;它只会导致在一个方面(修改一个“不可变”值)语义错误的代码在另一个方面(允许代码认为它在修改一个结构,但放弃尝试的更改)错误。允许struct方法指示它们是否将修改底层结构可以消除对无用的复制操作的需要,还可以确保尝试的错误使用将被标记。
6jygbczu4#
Swift结构体可以示例化为常量(通过
let
)或变量(通过var
)考虑Swift的
Array
结构体(是的,它是一个结构体)。为什么append不能处理行星名称呢?因为append是用
mutating
关键字标记的,而且planetNames
是用let
声明的,所以所有这样标记的方法都是禁止的。在你的例子中,编译器可以通过在
init
之外赋值一个或多个属性来判断你正在修改结构体。如果你稍微修改一下代码,你会发现x
和y
并不总是可以在结构体之外访问。注意第一行的let
。plupiseo5#
考虑与C的类比,Swift中的结构体方法为
mutating
/not-mutating
类似于C中的方法为non-const
/const
,C中标记为const
的方法同样不能改变结构体。您可以从结构外部更改var,但不能从其自身的方法更改它。
在C中,你也可以“从结构体外部改变一个var”--但是 * 只有 * 当你有一个非
const
的结构体变量时。如果你有一个const
的结构体变量,你不能赋值给一个var,也不能调用一个非const
的方法。类似地,在Swift中,仅当结构变量不是常量时,才能更改结构的属性。如果有结构常量,则不能为属性赋值,也不能调用mutating
方法。smtd7mpg6#
当我开始学习Swift的时候,我也想知道同样的事情,这些答案中的每一个,虽然可能增加了一些见解,但它们本身都有点罗嗦和混乱。我认为你问题的答案实际上很简单...
在你的结构体内部定义的变异方法想要得到修改它自己的每一个示例的权限。如果其中一个示例被赋值给一个不可变的常量
let
呢?哦,为了保护你自己(并让编辑器和编译器知道您正在 * 尝试 * 做什么),当您想给予示例方法这种能力时,您必须是显式的。相比之下,从结构体 * 外部 * 设置属性是在该结构体的已知示例上操作的。如果它被赋值给常量,Xcode会在你键入方法调用时通知你。
这是我喜欢Swift的原因之一,因为我开始更多地使用它--当我输入错误时会收到警告。当然,这比排除晦涩的JavaScript错误要好得多!
ibrsph3r7#
Swift程序员开发Struct的方式是不能在Struct方法中修改它的属性。例如,检查下面给出的代码
在执行上面的代码时,我们得到了一个错误,因为我们试图给Struct City的属性填充赋值。默认情况下,Struct属性不能在它自己的方法中变化。这是苹果开发人员构建它的方式,因此Struct在默认情况下将具有静态性质。
在Struct中声明函数为mutating允许我们改变Struct中的属性。5,将上面的代码修改为这样,
现在我们可以在方法的作用域内将newpopulation的值赋给属性population。
如果我们用let代替var来处理Struct对象,那么我们就不能改变任何属性的值,这也是为什么当我们试图用let示例调用改变函数时会得到一个错误的原因,所以当你改变一个属性的值时,最好使用var.
很想听听你的意见和想法......
lmvvr0a88#
快速变异结构
还有一个变种
Swift types(https://stackoverflow.com/a/59219141/4770877)