是通过值快速传递还是通过引用快速传递


100

我真的是Swift的新手,我刚刚读到类是通过引用传递的,并且复制了数组/字符串等。

通过引用进行传递是否与在Objective-C或Java中通过“ a”引用进行传递的方式相同,还是通过引用进行正确传递?


“通过引用传递与Objective-C或Java中的引用方式相同”无论是Objective-C还是Java都没有引用传递。
newacct 2014年

2
是。我知道。您不通过引用。您通过值传递引用。我以为那是在回答时就知道的。
gran_profaci 2014年

Java通过值传递,而不是引用传递。
6rchid

Answers:


167

Swift中的事物类型

规则是:

  • 类实例是引用类型(即,对类实例的引用实际上是一个指针

  • 函数是参考类型

  • 其他一切都是价值类型 ; “其他所有内容”仅表示结构实例和枚举实例,因为这就是Swift中的全部内容。例如,数组和字符串是结构实例。正如newacct指出的那样,您可以通过使用inout并获取地址传递对其中之一的引用(作为函数参数)。但是类型本身就是一个值类型。

引用类型对您意味着什么

引用类型对象在实践中很特殊,因为:

  • 仅仅分配或传递给函数可以产生对同一对象的多个引用

  • 即使对对象的引用是一个常量(let显式或隐式),对象本身也是可变的。

  • 从对该对象的所有引用可以看出,对该对象的突变会影响该对象。

这些可能是危险,因此请注意。另一方面,传递引用类型显然是有效的,因为仅复制和传递了一个指针,这很简单。

值类型对您意味着什么

显然,传递值类型是“安全的”,let这意味着它所说的是:您不能通过let引用对结构实例或枚举实例进行突变。另一方面,通过单独复制值来实现安全性,不是吗?这是否会使传递值类型变得昂贵?

好吧,是的,不是。它并不像您想象的那样糟糕。正如Nate Cook所说,传递值类型不一定意味着要进行复制,因为let(显式或隐式)可以保证不变性,因此无需复制任何内容。甚至突入var参考并不意味着事情被复制,只有他们可以是如果需要的话(因为有一个突变)。该文档特别建议您不要扭曲您的内裤。


6
“类实例通过引用传递。函数通过引用传递” 如果参数不是inout类型,则为按值传递。某项是否通过引用与类型正交。
newacct 2014年

4
@newacct好吧,从严格意义上来说,您当然是对的!严格来说,应该说一切都是按值传递,但枚举实例和结构实例是值类型,而类实例和函数是引用类型。见,例如,developer.apple.com/swift/blog/?id=10 -也看到developer.apple.com/library/ios/documentation/Swift/Conceptual/...不过,我觉得我说的话与一般的协定词义的意思。
马特2014年

6
正确,值类型/引用类型不应与按值传递/按引用传递混淆,因为值类型可以按值或按引用传递,并且引用类型也可以按值或按引用传递。
newacct 2014年

1
@newacct非常有用的讨论;我正在重写我的摘要,以免引起误解。
马特2014年

43

当参数不是时,它总是 按值传递inout

如果参数为,则始终 通过引用传递inout。但是,&由于在传递给参数时需要在参数上显式使用运算符inout,因此此操作有些复杂,因此它可能不适合直接传递变量的传统“按引用传递”定义。


3
这样的回答,与内特库克的组合,我(从C ++来)回避的事实是清晰的,即使是“引用类型”将不会在功能范围之外进行修改,除非你明确指定它(使用inout
Gobe

10
inout实际上不是通过引用传递的,而是通过复制复制的。它仅保证在函数调用后将修改后的值分配给原始参数。进出参数
Dalija Prasnikar

尽管所有的事情都是通过价值传递,这是事实。可以在函数内部修改引用类型属性,作为对同一实例的副本引用。
MrAn3

42

默认情况下,Swift中的所有内容都通过“副本”传递,因此,当您传递值类型时,您将获得该值的副本,而当您传递引用类型时,您将获得该引用的副本,其中包括所有隐含的内容。(也就是说,引用的副本仍指向与原始引用相同的实例。)

我在上面的“副本”周围使用了吓人的引号,因为Swift做了很多优化。只要有可能,就不会复制,直到出现突变或突变的可能性为止。由于默认情况下参数是不可变的,因此这意味着大多数情况下实际上不会发生任何复制。


对我来说,这是最好的答案,因为它阐明了例如可以在函数内部修改实例属性,即使参数是副本(按值传递)也可以修改,因为它指向相同的引用。
MrAn3

9

这是一个供参考传递的小代码示例。除非您有充分的理由,否则请避免这样做。

func ComputeSomeValues(_ value1: inout String, _ value2: inout Int){
    value1 = "my great computation 1";
    value2 = 123456;
}

这样称呼它

var val1: String = "";
var val2: Int = -1;
ComputeSomeValues(&val1, &val2);

为什么要避免这样做?
头脑,

1
@Brainless,因为它为代码增加了不必要的复杂性。最好输入参数并返回单个结果。必须这样做,通常表明设计不佳。另一种表达方式是,传入的引用变量中的隐藏副作用对调用者而言并不透明。
克里斯·阿梅林克斯

这没有通过引用。inout是一个复制,复制输出运算符。它将首先复制对象,然后在函数返回后覆盖原始对象。尽管看起来似乎相同,但存在细微的差异。
Hannes Hertach

7

苹果斯威夫特开发者博客有一个叫后的价值和引用类型,提供有关这个题目的明确和详细的讨论。

报价:

Swift中的类型分为两类之一:第一,“值类型”,其中每个实例保留其数据的唯一副本,通常定义为struct,enum或tuple。第二种是“引用类型”,其中实例共享数据的单个副本,并且该类型通常定义为类。

Swift博客文章继续用示例解释差异,并建议何时使用它们。


1
这不能回答问题。问题是关于值传递与引用传递,这与值类型与引用类型完全正交。
约尔格W¯¯米塔格

2

默认情况下,类通过引用传递,其他类则通过值传递。您可以使用inout关键字通过引用传递。


这是不正确的。inout是一个复制,复制输出运算符。它将首先复制对象,然后在函数返回后覆盖原始对象。尽管看起来似乎相同,但存在细微的差异。
Hannes Hertach

2

当您使用带有infix运算符(例如+ =)的inout时,可以忽略&address符号。我猜编译器假定通过引用传递?

extension Dictionary {
    static func += (left: inout Dictionary, right: Dictionary) {
        for (key, value) in right {
            left[key] = value
        }
    }
}

origDictionary + = newDictionaryToAdd

很好的是,这个字典“ add”也只写了一个原始引用,非常适合锁定!


2

类和结构

结构和类之间最重要的区别之一是,结构在代码中传递时始终会被复制,而类是通过引用传递的。

关闭

如果将闭包分配给类实例的属性,并且闭包通过引用该实例或其成员来捕获该实例,则将在闭包和实例之间创建一个强大的引用周期。Swift使用捕获列表来打破这些强大的参考周期

ARC(自动参考计数)

引用计数仅适用于类的实例。结构和枚举是值类型,而不是引用类型,并且不通过引用存储和传递。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.