Swift语言中的“&”符号代表什么?

76

我知道“&”可以用于位运算,但有时候我看到它在变量名前面。把 & 放在变量名前面是做什么用的?


2
请查看Swift函数文档中的输入输出参数 - Maic López Sáenz
5个回答

92

它作为一个inout,使变量成为一个in-out参数。In-out实际上意味着通过引用而不是值传递值。这不仅要求按引用接受值,还要按引用传递值,因此请使用 & - foo(&myVar)而不是foo(myVar)

正如您所看到的,在Swift中,您可以在错误处理中使用它,在这里您必须创建一个错误引用,并使用&将其传递给函数。如果发生错误,则函数将填充错误值,否则将变量返回为原始值。

为什么要使用它?有时函数已经返回其他值,只返回另一个值(如错误)会令人困惑,因此我们将其作为inout传递。其他时候,我们希望函数来填充这些值,以便我们不必迭代很多返回值,因为函数已经为我们完成了 - 还有其他可能的用途。


11
在Swift中,它也可用于以传统C语言“地址”的含义从Swift调用C API。这在Swift中采用UnsafePointer<Type>的形式。因此,例如,在Swift中,一个期望指针的C API可能需要一个UnsafePointer<CGFloat>参数,您可以使用&myCGFloat来调用它。 (https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html) - Pat Niemeyer
1
我不明白为什么需要这样做,因为消息签名已经指定了它是inout。为什么要做两次? - Alper
1
@Alper - 我认为让程序员标记参数(inout)和参数(&)作为引用的理由是为了让调用者清楚地知道参数是引用并且可能被改变,从而避免因变量意外更改而导致的错误。 - cristoper
我理解输入功能,但是return sum & 0xff是什么意思? - Moosa Baloch
2
@MoosaBaloch在这种情况下,&是按位与运算符。 - Justin Oroz

22

这意味着它是一个输入输出变量。您可以直接对该变量进行操作。它通过地址传递,而不是作为副本。

例如:

var temp = 10
func add(inout a: Int){
    a++
}
add(inout:&temp)
temp // 11

你的例子还有其他替代情况吗?我的意思是,如果你执行了 add(temp),那么会有什么不同?temp 是 10 吗? - mfaani
2
++ 已废弃 O:-) - Dave Kliman

20

在 Swift 语言中还有另一种未被提及的 & 符号的用法。看下面的例子:

protocol Foo {}
protocol Bar {}

func myMethod(myVar: Foo & Bar) {
    // Do something
}

这里的&语法指定了myVar符合Foo和Bar协议。

作为另一种用例,考虑以下情况:

func myMethod() -> UIViewController & UITableViewDataSource {
    // Do something
}

这里我们说的是该方法返回一个符合某个协议(UITableViewDataSource)的类实例(UIViewController)。在Swift 5.1的不透明类型中,这种方式已经有些过时了,但你仍然可能会在早期的Swift 5.1代码中偶尔看到这种语法。


这个问题特别涉及到变量前面的“&”符号,而不是协议之间的使用。 - Mercutio
2
这与问题无关,但这正是我正在寻找的,所以谢谢! - Thao Tran

13

如果在函数中在变量前加上&,那么这个变量就是一个输入输出变量。

@Icaro已经描述了它的含义,我将举一个例子来说明输入输出变量和输入变量之间的区别:

func majec(inout xValue:Int, var yValue:Int) {
    xValue = 100
    yValue = 200
}

var xValue = 33
var yValue = 33
majec(&xValue, yValue: yValue)
xValue //100
yValue //33

5
正如其他答案所述,您可以使用前缀&来将值传递给方法或函数调用的inout参数,这在《Swift编程语言》的函数 > 函数参数标签和参数名 > 引用传递参数中有所记录。但其中还有更多内容。
实际上,您可以将Swift的inout参数和向它们传递值类比于C或C++中的按地址传递或按引用传递。事实上,编译器会将许多inout参数的使用优化为大致相同的机制(特别是当您调用处理指针的导入的C或ObjC API时)。然而,这些只是优化措施——从语义层面上说,inout并不会传递地址,这使得编译器可以使此语言结构更加灵活和强大。
例如,下面是一个使用常见策略验证其属性访问权限的结构体:
struct Point {
    private var _x: Int
    var x: Int {
        get {
            print("get x: \(_x)")
            return _x
        }
        set {
            print("set x: \(newValue)")
            _x = newValue
        }
    }
    // ... same for y ...
    init(x: Int, y: Int) { self._x = x; self._y = y }
}

(在“真实”的代码中,对于x的getter和setter可以执行诸如强制最小/最大值之类的操作。或者x可以执行其他计算属性技巧,例如在幕后与SQL数据库通信。在这里,我们只是记录调用并获取/设置基础私有属性。)
现在,当我们将x传递给inout参数时会发生什么?
func plusOne(num: inout Int) {
    num += 1
}

var pt = Point(x: 0, y: 1)
plusOne(num: &pt.x)
// prints:
//   get x: 0
//   set x: 1

因此,即使x是一个计算属性,在使用inout参数通过“按引用”传递它时,与x是存储属性或局部变量相同,它的工作方式也与您期望的相同。
这意味着您可以通过“按引用”传递各种各样的东西,而在C/C++/ObjC中甚至无法考虑。例如,请考虑标准库swap函数,它接受任何两个...“事物”并交换它们的值:
var a = 1, b = 2
swap(&a, &b)
print(a, b) // -> 2 1

var dict = [ "Malcolm": "Captain", "Kaylee": "Mechanic" ]
swap(&dict["Malcolm"], &dict["Kaylee"])
print(dict) // -> ["Kaylee": "Captain", "Malcolm": "Mechanic"], fanfic ahoy

let window1 = NSWindow()
let window2 = NSWindow()
window1.title = "window 1"
window2.title = "window 2"
var windows = [window1, window2]
swap(&windows[0], &windows[1])
print(windows.map { $0.title }) // -> ["window 2", "window 1"]

inout的工作方式还可以让您做一些有趣的事情,比如在嵌套调用链上使用+=运算符:

window.frame.origin.x += 10

...这比分解CGRect并构造具有不同x坐标的新矩形要简单得多。


更精细的inout行为版本被称为“按值结果调用”,以及它可以优化到C风格的“通过地址传递”行为的方式,在The Swift Programming LanguageDeclarations > Functions > In-Out Parameters一章中有介绍。


哇,非常感谢您提供如此全面而深入的解释!我之前并不知道在 Swift 中 & 运算符有这些微妙的行为。 - Oren S
1
谈论“inout”给我带来的最大问题是它让我想要去街上买双层汉堡和奶昔。 - rickster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接