首页 文章

Swift和mutating结构

提问于
浏览
83

当谈到在Swift中改变值类型时,有些事情我并不完全理解 .

正如"The Swift Programming Language" iBook所述: By default, the properties of a value type cannot be modified from within its instance methods.

因此,为了使这成为可能,我们可以在结构和枚举中使用 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)

有没有人知道结构不能从他们自己的上下文中改变他们的内容的原因,而内容可以很容易地在其他地方改变?

5 回答

  • 21

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

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

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

    对于Objective-C程序员来说,可变/不可变的概念非常熟悉 . 在Objective-C中,我们为每个概念分别创建了两个类,但在Swift中,您可以使用一个结构 . 半工作 .

    对于C / C程序员来说,这也是非常熟悉的概念 . 这正是 const 关键字在C / C中的作用 .

    此外,可以非常好地优化不可变值 . 从理论上讲,Swift编译器(或LLVM)可以对 let 传递的值执行copy-elision,就像C一样 . 如果你明智地使用不可变结构,它将胜过refcounted类 .

    更新

    正如@Joseph声称这不能提供原因,我补充一点 .

    结构有两种方法 . 简单和变异的方法 . 普通方法意味着不可变(或非变异) . 这种分离仅用于支持不可变的语义 . 不可变模式下的对象根本不应改变其状态 .

    然后,不可变方法必须保证这种语义不变性 . 这意味着它不应该改变任何内部 Value . 因此编译器不允许在不可变方法中对其自身进行任何状态更改 . 相反,变异方法可以自由地修改状态 .

    然后,您可能有一个问题,为什么不可变是默认值?很难预测未来变异 Value 的状态,这通常会成为头痛和错误的主要来源 . 许多人一致认为解决方案是避免可变的东西,然后默认情况下,不可变的数据库在C / C系列语言及其推导中已有数十年的愿望 .

    有关详细信息,请参阅purely functional style . 无论如何,我们仍然需要可变的东西,因为不可变的东西有一些弱点,讨论这个似乎是不可能的 .

    我希望这有帮助 .

  • 70

    结构是字段的集合;如果一个特定的结构实例是可变的,它的字段将是可变的;如果实例是不可变的,则其字段将是不可变的 . 因此,必须为任何特定实例的字段可能是可变的或不可变的可能性准备结构类型 .

    为了使结构方法改变底层结构的字段,这些字段必须是可变的 . 如果在不可变结构上调用一个改变底层结构字段的方法,它将尝试改变不可变字段 . 由于没有任何好处,因此需要禁止这种调用 .

    为了实现这一点,Swift将结构方法分为两类:修改底层结构的方法,因此只能在可变结构实例上调用,以及那些不修改底层结构的方法,因此可以在可变和不可变实例上调用 . 后一种用法可能更频繁,因此是默认值 .

    相比之下,.NET目前(仍然!)没有提供区分修改结构的结构方法和不修改结构的结构方法的方法 . 相反,在不可变结构实例上调用结构方法将导致编译器生成结构实例的可变副本,让方法执行它想要的任何操作,并在方法完成时丢弃副本 . 这会强制编译器浪费时间复制结构,无论方法是否修改它,即使添加复制操作几乎不会将语义错误的代码转换为语义正确的代码;它只会导致在某种程度上语义错误的代码(修改“不可变”值)以不同的方式出错(允许代码认为它正在修改结构,但丢弃尝试的更改) . 允许struct方法指示它们是否将修改底层结构可以消除对无用复制操作的需要,并且还确保将标记尝试的错误使用 .

  • 7

    Caution: layman's terms ahead.

    在最基本的代码级别上,这种解释并不严格 . 然而,一个真正在Swift工作的人已经对它进行了评估,他说这是一个很好的基本解释 .

    所以我想尝试简单直接地回答"why"的问题 .

    确切地说:当我们可以在没有任何修改关键字的情况下更改struct参数时,为什么我们必须将struct函数标记为 mutating

    所以,大局观,它与保持Swift迅速的理念有很大关系 .

    您可以将其视为管理实际物理地址的问题 . 当您更改地址时,如果有很多人拥有您当前的地址,您必须通知所有人您已移动的地址 . 但如果没有人拥有你目前的地址,你可以随心所欲地移动,没有人需要知道 .

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

    这也是为什么Swift-people人都在讨论 Value 类型与参考类型的原因 . 从本质上讲,引用类型遍布整个地方的“联系人”,而值类型通常不需要超过一对 . 值类型是“Swift”-er .

    所以回到小图: structs . 结构在Swift中是一个大问题,因为它们可以完成对象可以做的大部分事情,但它们是值类型 .

    让我们通过想象一个生活在 someObjectVille 中的 misterStruct 来继续物理地址类比 . 这个类比在这里有点令人兴奋,但我认为它仍然有用 .

    因此,为了模拟更改 struct 上的变量,让我们说 misterStruct 有绿头发,并获得一个命令切换到蓝色头发 . 正如我所说的那样,这个比喻变得令人讨厌,但有些事情发生了,不是改变了头发,老人走了出来,一个蓝色头发的新人进来了,那个新人开始称自己为 misterStruct . 没有人需要更改地址通知,但如果有人查看该地址,他们会看到一个蓝头发的人 .

    现在让我们模拟在 struct 上调用函数时会发生什么 . 在这种情况下,就像 misterStruct 获得一个如 changeYourHairBlue() 的订单 . 所以邮局将指令发送给 misterStruct "go change your hair to blue and tell me when you're done."

    如果他在做变量直接改变时做了什么,那么 misterStruct 会做什么就是离开自己的家,召唤一个蓝头发的新人 . 但这就是问题所在 .

    订单是"go change your hair to blue and tell me when you're done,"但它是获得该订单的绿色家伙 . 在蓝人搬进来之后,仍然需要发回通知 . 但蓝衣人对此一无所知 .

    [为了真正想起这个比喻一些可怕的东西,绿头发的家伙在技术上发生的事情是,他搬出去后,他立即自杀了 . 所以他不能通知任何人任务完成!]

    为了避免这个问题,在这种情况下,Swift必须直接进入该地址的房子,并实际改变当前居民的头发 . 这是一个完全不同的过程,而不仅仅是送一个新人 .

    这就是斯威夫特想要的原因我们使用 mutating 关键字!

    最终结果看起来与任何必须引用结构的东西相同:房子的居民现在有蓝色的头发 . 但实现它的过程实际上是完全不同的 . 它看起来像是在做一件非常不同的事情 . 它正在做一件Swift一般结构化的事情 .

    因此,为了给可怜的编译器提供一些帮助,而不是让它必须弄清楚函数是否自己变换 struct ,对于每个单独的结构函数,我们被要求怜悯并使用 mutating 关键字 .

    从本质上讲,为了帮助斯威夫特保持迅速,我们都必须尽自己的一份力量 . :)

    EDIT:

    嘿dude / dudette谁向我投降,我只是完全改写了我的回答 . 如果它与你坐得更好,你会删除downvote吗?

  • 5

    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 之外的一个或多个属性来告诉您正在修改结构 . 如果你稍微改变你的代码,你会发现 xy 并不总是在结构外部可访问 . 注意第一行 let .

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

    考虑一下C的类比 . Swift中的struct方法 mutating / not- mutating 类似于C中的方法非 const / const . 在C中标记为 const 的方法同样不能改变结构 .

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

    在C中,您也可以"change a var from outside the struct" - 但仅当您有非 const struct变量时 . 如果您有一个 const struct变量,则无法分配给var,也无法调用非 const 方法 . 类似地,在Swift中,只有在struct变量不是常量时才能更改struct的属性 . 如果您有结构常量,则无法分配属性,也无法调用 mutating 方法 .

相关问题