Swift和mutating结构

时间:2014-06-04 11:00:50

标签: struct value-type swift

当谈到在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 个答案:

答案 0 :(得分:71)

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

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

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

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

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

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

更新

正如@Joseph声称这并没有提供为什么,我还要多补充一点。

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

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

然后,你可能会有一个为什么不可变是默认值的问题?这是因为它很难预测未来的变异值状态,而且通常成为头痛和错误的主要来源。很多人都认为解决方案是避免可变的东西,然后默认不可变在C / C ++系列语言及其推导中已经存在了数十年的愿望列表。

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

我希望这会有所帮助。

答案 1 :(得分:21)

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

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

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

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

答案 2 :(得分:10)

警告:外行人的条件未来。

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

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

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

所以,大局观,它与保持Swift swift的哲学有很大关系。

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

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

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

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

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

因此,为了模拟更改struct上的变量,假设misterStruct有绿头发,并获得切换到蓝色头发的订单。像我说的那样,这个比喻变得令人讨厌,但是发生的事情是,不是改变misterStruct的头发,而是老人搬出去,一个蓝色头发的新人进入,那个新人开始称自己为misterStruct。没有人需要更改地址通知,但如果有人查看该地址,他们会看到一个蓝头发的人。

现在让我们模拟在struct上调用函数时会发生什么。在这种情况下,就像misterStruct获得changeYourHairBlue()之类的订单。因此,邮局向misterStruct提供指示“将你的头发改为蓝色并告诉我你什么时候完成。”

如果他遵循与以前相同的惯例,如果他正在做他直接更改变量时所做的事情,那么misterStruct将会做什么搬出他自己的房子并打电话给他们一个蓝头发的新人。但这就是问题所在。

命令是“把你的头发改成蓝色并告诉我你什么时候完成”,但这是绿色那个得到那个订单的人。在蓝人搬进来之后,仍然需要发回“工作完成”通知。 但蓝衣人对此一无所知。

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

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

这就是Swift希望我们使用mutating关键字的原因!

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

所以给这个可怜的编译器一点帮助,而不是让它必须弄清楚一个函数是否自己改变了struct,对于每一个结构函数来说都是mutating关键字。

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

修改

嘿dude / dudette向我投降,我只是完全重写了我的答案。如果它与你的关系更好,你会删除downvote吗?

答案 3 :(得分:7)

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

为什么追加不适用于行星名称?因为追加标记为mutating关键字。由于planetNames是使用let声明的,因此标记的所有方法都是禁止的。

在您的示例中,编译器可以通过分配init之外的一个或多个属性来告诉您正在修改结构。如果您稍微更改了代码,您会发现xy并非始终可以在结构外部访问。注意第一行的let

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

答案 4 :(得分:5)

考虑用C ++进行类比。 Swift中mutating / not - mutating的结构方法类似于C ++中非const / const的方法。在C ++中标记为const的方法同样不能改变结构。

  

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

在C ++中,您还可以从结构外部更改var" - 如果你有一个非const结构变量,只能 。如果您有一个const结构变量,则无法分配给var,也无法调用非const方法。类似地,在Swift中,只有在struct变量不是常量时才能更改struct的属性。如果您有结构常量,则无法分配属性,也无法调用mutating方法。

答案 5 :(得分: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内部将函数声明为mutation允许我们更改Structs中的属性。 上面代码的第5行:

mutating changePopulation(newpopulation : Int)

现在,我们可以在方法范围内将 newpopulation 的值分配给属性 population

注意:

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

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

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

答案 6 :(得分:0)

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

在结构内部 中定义的变异方法希望获得权限,以修改将来将要创建的每个实例。如果这些实例之一被let分配给不可变常量怎么办?哦哦为了保护您自己(并使编辑器和编译器知道您正在尝试执行的操作),当您想赋予实例方法这种功能时,您必须明确。

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

当我开始使用Swift时,这就是我钟爱的Swift之一-在键入错误时会收到警告。排除疑难解答的JavaScript错误肯定可以解决问题!

答案 7 :(得分:0)

另一个变体

library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyjs)
mytitle <- paste0("Life, Death & Statins")
dbHeader <- dashboardHeaderPlus(
  titleWidth = "0px",
  tags$li(a(href = "http://https://www.uow.edu.au/", #   '',
            div(style = "margin-left:-15px;margin-bottom:-83px;margin-top:-15px;padding: 0px 1190px 0px 0px ; width: 290px;",
                img(src = 'YBS.png', height = "125px",width="232px")),
            div(style="display: inline;margin-top:-35px; padding: 0px 90px 0px 800px ;font-size: 58px ;color: black;font-family:Times-New Roman;font-weight: bold; width: 500px;",HTML(mytitle)),
            div(style="display: inline;margin-top:25px; padding: 0px 0px 0px 800px;vertical-align:top; width: 150px;", actionButton("well", "Welcome")),
            div(style="display: inline;margin-top:15px; padding: 0px 0px 0px 0px;vertical-align:top; width: 150px;", actionButton("info", "Information")),
            div(style="display: inline;padding: 0px 0px 0px 0px;vertical-align:top; width: 150px;", actionButton("conse", "Consent")),
            div(style="display: inline;padding: 0px 0px 0px 0px;vertical-align:top; width: 150px;", actionButton("pswd", "Password")),
            div(style="display: inline;padding: 0px 0px 0px 0px;vertical-align:top; width: 150px;", actionButton("rp", "Run Project")),
            div(style="display: inline;padding: 0px 0px 0px 0px;vertical-align:top; width: 150px;", actionButton("res", "Results"))
            
  ),
  class = "dropdown")
  
  
)

shinyApp(
  ui = dashboardPagePlus(
    header = dbHeader,
    sidebar = dashboardSidebar(width = "0px"
    ),
    body = dashboardBody(
      
      useShinyjs(),
      tags$script(HTML("$('body').addClass('fixed');")),
      
      tags$head(tags$style(".skin-blue .main-header .logo { padding: 0px;}"))
      
    )
    
  ),
  server<-shinyServer(function(input, output,session) { 
    hide(selector = "body > div > header > nav > a")
    
  }
  )
)

[Swift types]