可变参数究竟是如何工作的?

How exactly does a variadic parameter work?

我是 Swift 新手,在理解可变参数到底是什么以及它为什么有用时遇到了一些困难。我目前正在关注在线 Swift 5.3 指南,这是为此类参数提供的示例。

func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers

显然,名为numbers 的可变参数的类型为Double...,这使得它可以在函数体中用作常量数组。为什么函数 return Double(numbers.count) 而不是 numbers.count?而不是创建一个可变参数,为什么不创建一个参数来接收函数外部的数组,就像这样?

func addition(numbers : [Int]) -> Int
{
    var total : Int = 0
    for number in numbers
    {
        total += number
    }
    return total
}

let totalBruhs : [Int] = [4, 5, 6, 7, 8, 69]
addition(numbers: totalBruhs)

另外,为什么每个函数只能有一个可变参数?

可变参数需要(好吧,不是需要,而是很好)存在于Swift中,因为它们存在于C中,而许多东西存在于Swift中桥接到 C。在 C 中,创建任意长度的快速数组并不像 Swift.

中那么简单

如果您是从头开始构建 Swift 并且不向后兼容 C,那么可能已经添加了它们,也可能没有。 (虽然我打赌是的,只是因为这么多 Swift 开发人员已经习惯了它们存在的语言。但是话又说回来,像 Zig 这样的语言有意摆脱了可变参数,所以我不知道。Zig 也演示了您不需要需要可变参数来桥接到 C,但它仍然很好。@Rob 下面的评论值得一读。他可能没有错.另外,很有见地)

但它们也很方便,因为您不需要添加 [...],这使得只有一个值时效果更好。特别是,考虑 print:

func print(_ items: Any..., separator: String = " ", terminator: String = "\n")

如果没有可变参数,您需要在每个 print 调用中放入 [...],否则您需要重载。 Variadic 并没有改变这里的世界,但它有点不错。当您考虑过载会造成的歧义时,它特别好。假设您没有可变参数,而是有两个重载:

func print(_ items: [Any]) { ... }
func print(_ item: Any) { print([item]) }

这实际上有点模棱两可,因为 Array 也是 Any 的一种。所以 print([1,2,3]) 会打印 [[1,2,3]]。我敢肯定有一些可能 work-arounds,但可变参数很好地解决了这个问题。

只能有一个,否则会有模棱两可的情况。

func f(_ xs: Int..., _ ys: Int...)

遇到这种情况f(1,2,3)应该怎么办? xs 是什么,ys 是什么?

您在此处显示的函数没有 return Double(numbers.count)。它将 numbers.count 转换为 Double,因此它可以分为另一个 Double。函数 returns total / Double(numbers.count).

And instead of creating a variadic parameter, why not just create a parameter that takes in an array that's outside of the function ... ?

我同意你的看法,将数组用于算术函数(如“mean”、“sum”等)感觉很直观

话虽如此,但在某些情况下,可变参数模式感觉很自然:

  1. 在某些情况下,您编写的函数在调用点使用数组可能不合逻辑或不直观。

    考虑一个 max 函数,它应该是 return 两个值中较大的一个。强加一个约束,即调用者 必须 创建这些值的数组,以便 return 两个值中较大的一个,这感觉不太正确。你真的想允许一个漂亮、简单的语法:

     let result = max(a, b)
    

    但与此同时,作为 API 开发人员,也没有理由将 max 实现限制为仅允许两个参数。也许调用者可能想使用三个。或者更多。作为 API 开发人员,我们设计 API 用于主要用例的自然调用点,但提供尽可能多的灵活性。所以可变函数参数既非常自然又非常灵活。

    这种模式有很多可能的例子,即任何自然感觉它应该接受两个参数的函数,但可能需要更多。考虑一个用于两个矩形的 union 函数,并且您需要边界矩形。同样,您不希望调用者必须为可能是两个矩形的简单并集创建一个数组。

  2. 另一个常见的例子是您可能有可变数量的参数但可能不处理数组。典型的例子是 printf 模式。或者另一个是你正在与某些 SQL 数据库交互并且可能将值绑定到 SQL 中的 ? 占位符等(以防止 SQL 注入攻击):

     let sql = "SELECT book_id, isbn FROM books WHERE title = ? AND author = ?"
     let resultSet = db.query(sql, title, author)
    

    同样,在这些情况下,建议调用者必须为这个异构值集合创建一个数组在调用点可能感觉不自然。

所以,问题不是“为什么我要在数组合乎逻辑且直观的地方使用可变参数?”而是“为什么我要在可能不使用的地方强制使用数组参数?”