【问题标题】:Swift can change struct declared with let if using an index but not if using a loop如果使用索引,Swift 可以更改用 let 声明的结构,但如果使用循环则不能
【发布时间】:2019-05-16 18:18:19
【问题描述】:

在下面的代码中,一个名为Card 的结构被分配了let。然后,一旦分配,我将这张卡放入一个数组中。现在,在func resetCards 中,我想将数组中的每张卡片设置回其原始状态。但是,如果我对数组中的每张卡使用 for 循环,我会收到错误消息 "cannot assign property to constant",这是我所期望的。但是,如果我执行以下操作:cards[0].variable = false,则不会出现错误,并且可以更改结构变量。为什么如果我使用 for card in cards 循环遍历数组,即使使用 var 声明属性,我也无法更改结构的属性,但是如果我使用数组索引访问结构,例如for index in cards.indices可以吗?

class Concentration {
  var cards = [Card]()

  init(numberOfPairsOfCards: Int) {
    for _ in 0..<numberOfPairsOfCards {
      let card = Card()
      cards += [card, card]
    }

  func resetCards() {
    indexOfOneAndOnlyFaceUpCard = nil
    for card in cards {
      card.variable = true // this doesn't work
      cards[0].variable = true // this works
    }
  }
}

【问题讨论】:

  • 你希望‘cards += [card, card]’这行代码做什么?
  • 基本上我是在创造游戏集中度。因此,对于我创建的每张新卡片,我还需要创建一个相同的副本并将其添加到卡片数组中。该数组包含所有卡片,并且每张卡片必须有两张且只有两张。
  • 这不会添加卡片的副本,列表将包含指向同一实例的指针。
  • @nick 它会创建一个副本,因为结构是值类型而不是引用类型。所以cards += [card, card] 创建了两个不相互引用的唯一副本,即更改一个不会更改另一个。但是,如果 Card 是一个类而不是一个结构,那么你是正确的。

标签: swift


【解决方案1】:

结构是值类型,它们在分配给变量时被复制。当你遍历一个值类型数组时:

for card in cards {

然后card 包含每个元素的副本。任何更改都不会保存到原始数组中。

您可以遍历索引并直接访问数组值:

for offset in cards.indices {
  cards[offset].variable = true
}

但是,通常我们使用map 来创建一个全新的数组:

cards = cards.map {
   var card = $0 // both `$0` and `card` are copies of the original
   card.variable = true 
   return card
}

【讨论】:

    【解决方案2】:

    要回答为什么会出现编译错误的问题: 您需要将其声明为var 而不是let,这是在for-in 循环中省略单词时假定的

    for var card in cards {
        card.variable = true
    }
    

    从长远来看,此答案对您没有帮助,因为您只是更改了卡片结构的本地副本。你在专注中保留的卡片数组仍然没有改变

    【讨论】:

      【解决方案3】:

      在放入数组之前如何“声明”结构并不重要。让我们谈谈如何从数组中访问事物。

      我假设以下测试情况:

      struct Card {
          var property : String
      }
      var cards = [Card(property:"hello")]
      

      我们试着说

      for card in cards {
          card.property = "goodbye"
      }
      

      但我们不能,因为card 是用let 隐式声明的,它的属性不能被改变。因此,让我们尝试通过 var 重新分配来解决这个问题:

      for card in cards {
          var card = card
          card.property = "goodbye"
      }
      

      现在我们的代码编译并运行了,但你猜怎么着?数组本身不受影响!这是因为card 是位于数组中的结构的副本;参数传递和赋值都会复制。实际上,我们可以通过在前面坚持 var 引用来浓缩这一点:

      for var card in cards {
          card.property = "goodbye"
      }
      

      但我们一无所获; card 仍然是一个副本,因此数组内容仍然不受影响。

      现在让我们通过索引来尝试一下,就像您在实验中所做的那样:

      for ix in cards.indices {
          cards[ix].property = "goodbye"
      }
      

      宾果!它编译并运行更改cards 数组的内容。那是因为我们正在直接访问数组中的每张卡片。就好像我们说过:

      for ix in cards.indices {
          var card = cards[ix]
          card.property = "goodbye"
          cards[ix] = card
      }
      

      是的,我们仍在制作副本,但我们正在将该副本重新分配回数组中的相同位置。索引访问是这样做的简写。

      但是,我们实际上仍在提取副本,对其进行变异,然后重新插入。我们可以尝试通过使用inout 来解决这个问题,但需要进行一些更精细的规划,如下所示:

      func mutate(card: inout Card) {
          card.property = "goodbye" // legal!
      }
      for ix in cards.indices {
          mutate(card: &cards[ix])
      }
      

      如您所见,我们现在可以设置card.property,因为inout 的参数隐含为var。然而,具有讽刺意味的是,我们仍在进行复制和替换,因为结构是一种值类型——它不能真正地被改变,即使通过赋值var 引用给人一种我们正在这样做的错觉。

      【讨论】:

      • 解释得很漂亮。谢谢你。就一个问题。当您说“使用 let 隐式声明卡”时,这是否意味着所有 for 循环都隐式使用 let,例如for let card in cards 还是依赖于别的东西?
      • 欲了解更多信息,请参阅我的apeth.com/swiftBook/ch04.html#SECreferenceTypes 以及我的答案:stackoverflow.com/a/27366050/341994
      • 就是你说的意思。 for...in 暗示 let。你实际上可以通过说for var来解决这个问题;我会将其添加到我的答案中。
      • 这个答案如何解释'cards += [card,card]'这一行?
      猜你喜欢
      • 2023-02-17
      • 2018-04-27
      • 1970-01-01
      • 2023-03-15
      • 1970-01-01
      • 2011-08-25
      • 2018-09-18
      • 1970-01-01
      • 2021-02-05
      相关资源
      最近更新 更多