3

我试图了解值和引用类型之间的区别。现在我想使用苹果指南中的功能:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
  let temporaryA = a
  a = b
  b = temporaryA
}

如果我想使用这是函数,我将编写这段代码

swapTwoInts{&firstIntStruct, &secondIntStruct}

我知道我们必须放入这个函数引用类型,但是 Int 是一个值类型,因此我们使用 &。
另一方面,当我尝试在交换函数中将 Int 更改为我的类时,我还必须在类的实例之前编写 &。

如果它已经是参考,为什么我必须这样做?

4

3 回答 3

5

假设我们编写了您正在谈论的假设函数:

class C {}

func swapTwoC(_ lhs: C, rhs: C) {
    let originalLHS = lhs
    lhs = rhs
    rhs = originalLHS
}

直接的问题是,lhs并且rhs是不可变的。要改变它们,我们需要制作可变副本:

func swapTwoC(_ lhs: C, rhs: C) {
    var lhs = lhs; var rhs = rhs
    let originalLHS = lhs
    lhs = rhs
    rhs = originalLHS
}

但是现在的问题是我们正在改变我们的副本,而不是我们的调用者给我们的原始引用。

更根本的是,问题在于,当您将引用(到类的实例,我们称之为对象)传递给函数时,引用本身会被复制(它的行为类似于值类型)。如果那个函数改变了引用的值,它只会改变它自己的本地副本,正如我们所看到的。

当您有一个inout C,并且您传入 时&myObject,您实际上传入的是对您的引用的引用myObject。当函数参数被复制时,复制的是这个“ref to a ref”。然后,该函数可以使用该“ref to a ref”为myObject调用者拥有的引用分配一个新值

于 2018-05-09T19:44:19.730 回答
3

我想用例子来解释它。正如@Alexander 提到的,对于带有Intas 参数的函数:

1.传值

它会改变副本,而不是调用者的原始引用。

更根本的问题是,当您将引用(到类的实例,我们称之为对象)传递给函数时,引用本身会被复制(它的行为类似于值类型)。如果那个函数改变了引用的值,它只会改变它自己的本地副本,正如我们所看到的。

在此处输入图像描述

你可以看到

func swapTwoInts(_ a: Int, _ b: Int) { }

如果改变了p 和 q的值,其中self.x 和 self.y没有改变。因为这个函数传递的是x 和 y的值而不是它们的引用。

2. 通过引用传递:

func swapTwoInts(_ a: inout Int, _ b: inout Int) { }

它传递了self.x 和 self.y的引用,这就是为什么你不必像在以前的类型中使用 p 和 q 那样再次改变它们。var x因为它会用and改变对象var y

您可以看到,a 和 b 在日志中具有引用值,并且更改a 和 b也更改了self.x 和 self.y,因为 a 和 b 具有相同的 x 和 y 的引用(地址)。

在此处输入图像描述

于 2018-05-09T20:23:52.667 回答
1

因此,有一些较低级别的内存组件正在发挥作用,以充分理解这一点。

1)当您创建一个值或引用类型时,您在堆栈上有一个新变量。该变量要么是值类型的实际数据,要么是引用类型的数据指针。

2)当你调用一个函数时,它会创建堆栈的一个新部分,并在堆栈上创建新变量(它们是letswift 中的实例)来复制传入的变量。因此,对于值类型,它会进行深度复制并供参考类型它复制指针。

所以这意味着当你使用inout你说的时候,获取这个变量的内存地址并更新它包含的数据。所以你可以给一个值类型新的数据或一个引用类型一个新的指针地址,它会在交换函数的范围之外改变。它使它成为var(与传入的相同)而不是let正常的。

于 2018-05-09T19:51:50.720 回答