【问题标题】:How exactly does a variadic parameter work?可变参数究竟是如何工作的?
【发布时间】:2020-08-09 21:38:28
【问题描述】:

我是 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... 的类型,这允许它在函数体中作为常量数组使用。为什么函数返回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)

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

【问题讨论】:

  • “为什么函数返回Double(numbers.count) 而不仅仅是numbers.count?” ... 它不是。它返回值的总和(这是一个双精度数)除以有多少数字。但是number.count 是一个整数,你不能将Double 除以一个整数。因此,在除法运算中使用它之前,您必须将整数计数转换为双精度数。这就是return 表达式中Double(numbers.count) 的意义所在。
  • FWIW,我不会使用可变参数来计算数字序列的平均值。再说一次,我也不会在 Double 的数组上这样做。我可能会在Sequence 上执行此操作并使用FloatingPoint 协议。例如,gist.github.com/robertmryan/92edae4b0ca41455ad26253770a7fc5c

标签: swift variadic-functions


【解决方案1】:

可变参数需要(嗯,不是需要,但很好)存在于 Swift 中,因为它们存在于 C 中,而且 Swift 中的许多东西都连接到 C。在 C 中,创建一个快速的任意数组长度并不像 Swift 中那么简单。

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

但它们也很方便,因为您不需要添加 [...],这样当只有一个值时会更好。特别是,考虑类似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]]。我确信有一些可能的解决方法,但可变参数可以很好地解决这个问题。

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

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

f(1,2,3) 在这种情况下应该怎么做?什么是xs,什么是ys

您在此处显示的函数不会返回 Double(numbers.count)。它将numbers.count 转换为Double,因此可以将其划分为另一个Double。函数返回total / Double(numbers.count)

【讨论】:

  • @Rob_Napier 啊,所以这些参数的存在部分是因为Swift和C的关系,也是为了让程序更简单一点。我从你的回答中假设可变参数不是那么重要吗?
  • 我不确定我是否会将 C 互操作性拖入这个问题,特别是因为语言之间可变参数函数的互操作并不是人们所希望的。 (参见Using Imported C Functions in Swift。)我可以理解 Zig 的设计选择,但我认为在很多情况下可变参数模式是逻辑模式,而 Swift 对它们的原生支持似乎很合理。我认为我们不需要诉诸 C 互操作性来激发动力。
  • @Rob 你的反驳很好。更新了答案以将人们指向它。
【解决方案2】:

与其创建可变参数,为什么不直接创建一个接受函数外部数组的参数...?

我同意你的观点,将数组用于“mean”、“sum”等算术函数感觉很直观。

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

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

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

     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)
    

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

所以,问题不是“我为什么要在数组符合逻辑和直观的情况下使用可变参数?”而是“为什么我要强制使用可能不使用的数组参数?”

【讨论】:

    猜你喜欢
    • 2018-06-19
    • 1970-01-01
    • 2011-06-26
    • 2021-08-15
    • 2012-06-08
    • 2011-10-11
    • 2013-07-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多