为什么在Swift中需要使用下划线?

224
这里说,“注意: 下划线_表示“我不关心该值”,但我从JavaScript转过来,不理解这是什么意思。

我只有在参数前使用下划线才能打印出这些函数:

func divmod(_ a: Int, _ b:Int) -> (Int, Int) {
    return (a / b, a % b)
}

print(divmod(7, 3))
print(divmod(5, 2))
print(divmod(12,4))

为避免任何错误,我必须这样写,而没有下划线:

func divmod(a: Int, b:Int) -> (Int, Int) {
    return (a / b, a % b)
}

print(divmod(a: 7, b: 3))
print(divmod(a: 5, b: 2))
print(divmod(a: 12,b: 4))

我不理解下划线的用法。何时、如何以及为什么要使用这些下划线?

4个回答

465

不同的使用情况有一些微妙之处,但通常下划线的意思是“忽略这个”。


在声明一个新函数时,下划线告诉Swift在调用时该参数不应该有标签——这就是你所看到的情况。完整的函数声明如下:

func myFunc(label name: Int) // call it like myFunc(label: 3)

"label"是一个参数标签,当你调用函数时必须存在。(自Swift 3以来,默认情况下所有参数都需要标签。)"name"是你在函数内部使用的该参数的变量名。一个更短的形式如下:

func myFunc(name: Int) // call it like myFunc(name: 3)

这是一种快捷方式,让你可以同时使用相同的单词作为外部参数标签和内部参数名称。它等同于func myFunc(name name: Int)

如果您希望您的函数在调用时没有参数标签,您可以使用下划线_使标签为空/被忽略。(在这种情况下,如果要使用该参数,您必须提供内部名称。)

func myFunc(_ name: Int) // call it like myFunc(3)

在赋值语句中,下划线表示“不赋值给任何东西”。如果你想调用一个返回结果但不关心返回值的函数,可以使用它。
_ = someFunction()

或者,就像您链接的文章中所示,忽略返回元组中的一个元素:
let (x, _) = someFunctionThatReturnsXandY()

当您编写一个实现某个已定义函数类型的闭包时,可以使用下划线来忽略某些参数。
PHPhotoLibrary.performChanges( { /* some changes */ },
    completionHandler: { success, _ in // don't care about error
        if success { print("yay") }
    })

同样地,当声明一个采用协议或重写超类方法的函数时,您可以使用_来忽略参数名称。由于协议/超类可能还定义该参数没有标签,因此您甚至可能会得到两个连续的下划线。
class MyView: NSView {
    override func mouseDown(with _: NSEvent) {
        // don't care about event, do same thing for every mouse down
    }
    override func draw(_ _: NSRect) {
        // don't care about dirty rect, always redraw the whole view
    }
}

与前两种样式有些相关:当使用绑定本地变量/常量的流程控制结构时,您可以使用_来忽略它。例如,如果您想迭代一个序列而不需要访问其成员:

for _ in 1...20 { // or 0..<20
    // do something 20 times
}

如果您在switch语句中绑定元组,下划线可以作为通配符使用,就像这个例子一样(从The Swift Programming Language中缩短而来):

switch somePoint { // somePoint is an (Int, Int) tuple
case (0, 0):
    print("(0, 0) is at the origin")
case (_, 0):
    print("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
    print("(0, \(somePoint.1)) is on the y-axis")
default:
    print("(\(somePoint.0), \(somePoint.1)) isn't on an axis")
}

最后提及一件与主题不太相关的事情,但是考虑到它似乎会吸引人们来到这里(正如评论中所指出):标识符中的下划线 (例如var _foofunc do_the_thing()struct Stuff_ 在 Swift 中没有特殊含义,但在程序员中有一些用途。

名称中的下划线是一种样式选择,但在 Swift 社区中并不受欢迎,因为 Swift 有关于类型使用 UpperCamelCase 和其他所有符号都使用 lowerCamelCase 的强烈约定。

在符号名称前缀或后缀上加下划线是一种样式约定,历史上被用来区分仅供内部使用的私有符号和导出的 API。然而,Swift 已经有了访问修饰符,因此这种约定通常在 Swift 中被视为非惯用语。

一些以双下划线前缀 (func __foo()) 的符号隐藏在 Apple 的 SDK 深处:这些是通过 NS_REFINED_FOR_SWIFT 属性从 (Obj)C 导入到 Swift 中的符号。当他们想要将一个 "(Obj)C API" 转换为 "更像 Swift 的 API" 时,Apple 会使用这个方法。例如,将类型不明确的方法转换为通用方法。他们需要使用导入的 API 来使改进后的 Swift 版本工作,因此他们使用 __ 来保持其可用性,同时又隐藏它以避免出现在大多数工具和文档中。


@rickster,方法开头下划线的意义是什么,比如这个-> func _copyContents(initializing ptr: UnsafeMutableBufferPointer<Element>) -> (Iterator,UnsafeMutableBufferPointer<Element>.Index) } 来自Sequence协议。 - dev gr
@devgr:完全不相关,建议您发布一个单独的问题。 - rickster
当然,我会发布一个单独的问题。 - dev gr
谢谢你提供了“不要给函数返回值赋值”的建议。这正是我一直在寻找的。 - absin

35

除了接受的答案外,_的一个用例是在代码中需要编写长数字时使用。

更易读的数字

这不是一个易于人类阅读的数字:

let x = 1000000000000

✅ 您可以在数字中添加_,使其更易读:

let x = 1_000_000_000_000

下划线接口

在Swift中,你可能会遇到以_开头的接口。虽然它们已被声明为public,但是以下划线开头的接口不被认为是此包的公共接口。我们可以在任何版本中删除或更改它们,包括小型发布版本。

例如,@_exportedImport或更多例子请查看官方Swift Collections存储库

⚠️ 简单来说就是不要使用它们,否则需要手动跟踪更改。


7
自从Swift 3以来,在函数调用上指定参数名称已成为强制性的要求——即使是第一个参数。因此,由于这可能会对使用Swift 2编写的代码造成巨大问题,您可以在声明时使用下划线来避免在调用时编写参数名称。所以在这种情况下,它是在说“不关心外部参数名”。其中,外部参数名是您在函数之外(即在调用时)调用参数的名称。这些外部参数名被称为参数标签。http://ericasadun.com/2016/02/09/the-trouble-with-argument-labels-some-thoughts/...看一下参数给出了两个名称?第一个就是下划线去的位置。希望这些有所帮助,如果还有疑问,请随时提出。

5
func divmod(_ a: Int, _ b:Int) -> (Int, Int) {
    return (a / b, a % b)
}

func divmod(a: Int, b:Int) -> (Int, Int) {
    return (a / b, a % b)
}
_ 是参数名称的占位符。在您的示例中,您使用不同的方式调用它们,在第二个函数中,您需要编写参数名称 a: 1
Swift 的函数命名约定是 funcName(param1:param2:),并且需要使用 _ 作为占位符来创建函数的名称。
在第一个名称中,名称为
divmod(_:_:)

第二个是:
divmod(a:b:)

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