本文迁移自 SwiftGG 中文版《The Swift Programming Language 6.2.3》的“泛型形参和实参”,原文由 Apple Inc.、Swift 项目作者及 SwiftGG 译者贡献,依据 Apache License 2.0(含 Runtime Library Exception)发布。本站仅做 Markdown、链接与版式适配。

将参数声明抽象化以脱离具体类型。

本节描述泛型类型、泛型函数和泛型构造器的形参和实参。在声明泛型类型、函数或构造器时,需要指定泛型可处理的类型形参。这些类型形参相当于占位符,当实例化泛型类型或调用泛型函数、初始化器时,会被具体的类型实参替换。

关于 Swift 语言中的“泛型”,参阅 泛型.

泛型形参子句

泛型形参子句 指定了泛型类型或函数的类型形参,以及对这些形参的任何相关约束和要求。泛型形参用尖括号(<>)括起来,格式如下:

Swift
<<#generic parameter list#>>

多个泛型形参用逗号分开,每个形参的形式如下:

Swift
<#type parameter#>: <#constraint#>

泛型形参由一个类型形参和一个可选的约束组成。类型形参只是一个占位符(如 TUVKeyValue等),用来表示类型。在声明类型、函数或构造器时(包括函数或构造器的签名),你可以访问这些类型形参及其任何关联的类型。

约束指定了类型形参要继承自特定类,或遵循某个协议或协议组合。例如:在下面的泛型函数中,泛型形参 T: Comparable 表示任何替代类型形参 T 的类型实参都必须遵循 Comparable 协议。

Swift
func simpleMax<T: Comparable>(_ x: T, _ y: T) -> T {
    if x < y {
        return y
    }
    return x
}

IntDouble,因为都遵循 Comparable 协议,所以该函数可以接受这两种类型的参数。与泛型类型不同,调用泛型函数或构造器时,你不需要指定泛型实参子句。泛型实参的类型会根据传给函数或构造器的参数类型推断出来。

Swift
simpleMax(17, 42) // T is inferred to be Int
simpleMax(3.14159, 2.71828) // T is inferred to be Double

泛型参数列表中的最后一个泛型参数后面可以跟一个可选的逗号。

整数泛型参数

整数泛型参数作为整数值而不是类型的占位符。它具有以下形式:

Swift
let <#type parameter#>: <#type>

类型必须是 Swift 标准库中的 Int 类型,或者解析为 Int 的类型别名或泛型类型。

你为整数泛型参数提供的值必须是整数字面量,或者是来自封闭泛型上下文的另一个整数泛型参数。例如:

Swift
struct SomeStruct<let x: Int> { }
let a: SomeStruct<2>  // OK: integer literal

struct AnotherStruct<let x: Int, T, each U> {
    let b: SomeStruct<x>  // OK: another integer generic parameter

    static let c = 42
    let d: SomeStruct<c>  // Error: Can't use a constant.

    let e: SomeStruct<T>  // Error: Can't use a generic type parameter.
    let f: SomeStruct<U>  // Error: Can't use a parameter pack.
}

类型上的整数泛型参数的值可以作为该类型的静态常量成员访问,具有与类型本身相同的可见性。函数上的整数泛型参数的值可以从函数内部作为常量访问。在表达式中使用时,这些常量的类型为 Int

Swift
print(a.x)  // Prints "4"

整数泛型参数的值可以从初始化类型或调用函数时使用的参数类型中推断出来。

Swift
struct AnotherStruct<let y: Int> {
    var s: SomeStruct<y>
}
func someFunction<let z: Int>(s: SomeStruct<z>) {
    print(z)
}

let s1 = SomeStruct<12>()
let s2 = AnotherStruct(s: s1)  // AnotherStruct.y is inferred to be 12.
someFunction(s: s1)  // Prints "12"

泛型 Where 子句

你可以通过在类型或函数体的起始大括号前包含一个泛型where子句,来对类型形参及其相关类型指定额外的要求。泛型 where子句由where关键字组成,后跟一个或多个要求,多个要求用逗号进行分隔。

Swift
where <#requirements#>

泛型 where 子句中的要求指明类型形参要继承自某个类或遵循某个协议或协议组合。虽然 where 子句为表达类型形参的简单约束提供了语法糖(比如,<T: Comparable> 等同于 where T: Comparable 等),但它可以用于为类型形参及其关联类型提供更复杂的约束。比如,你可以指定类型形参的关联类型遵循某个协议:<S: Sequence> where S.Iterator.Element: Equatable 指定了S遵循 Sequence协议,并且S的关联类型S.Iterator.Element遵循Equatable协议。此约束确保序列中的每个元素都是符合Equatable协议的。整数泛型参数不能有协议或超类要求。整数泛型参数不能有协议或超类要求。

还可以使用 == 运算符来指定两个类型必须相同。例如, <S1: Sequence, S2: Sequence> where S1.Iterator.Element == S2.Iterator.Element,表示 S1 和 S2 都遵循 Sequence 协议,并且两个序列元素的类型必须相同。对于整数泛型参数,== 运算符指定对其值的要求。你可以要求两个整数泛型参数具有相同的值,或者可以为整数泛型参数要求特定的整数值。

任何替代类型形参的类型实参都必须满足对该类型形参指定的所有约束与要求。

where子句可以出现在包含类型形参的声明中,或作为声明的一部分,被嵌套另一个在含有类型形参的声明中。被嵌套的泛型 where子句依然可以指向包围它的声明中的类型形参,然而此时 where子句指定的要求仅用于它被声明的地方。

如果外层的声明也有一个 where 子句,那么两个子句的要求会合并。在下面的示例中,startsWithZero()仅在 Element 同时遵循someProtocolNumeric协议时可用。

Swift
extension Collection where Element: SomeProtocol {
    func startsWithZero() -> Bool where Element: Numeric {
        return first == .zero
    }
}

重载泛型函数或构造器,泛型形参子句中的类型形参必须有不同的约束或要求。当调用重载的泛型函数或构造器时,编译器会使用这些约束来决定调用哪个重载的函数或构造器。

更多关于泛型 where 从句的内容和关于泛型函数声明的例子,参阅 泛型 Where 语句.

泛型形参子句的语法格式:

泛型形参子句< 泛型形参列表 ,? >
泛型形参列表泛型形参 | 泛型形参 , 泛型形参列表
泛型形参类型名称
泛型形参类型名称 : 类型标识符
泛型形参类型名称 : 协议合成类型
泛型形参let 类型名称 : 类型 \

泛型 where 子句where 约束列表
要求列表要求 | 要求 , 要求列表
要求一致性要求 | 同类型要求

一致性要求类型标识符 : 类型标识符
一致性要求类型标识符 : 协议合成类型
同类型要求类型标识符 == 类型
同类型要求类型标识符 == 有符号整数字面量

泛型实参子句

泛型实参 指定了泛型类型的类型实参。泛型实参用尖括号(<>)包围,格式如下

Swift
<<#generic argument list#>>

多个泛型实参用逗号分开。类型实参是实际具体类型的名称,用来替代泛型类型的泛型参数子句中相应的类型参数——或者对于整数泛型参数,是替换该整数泛型参数的整数值。从而得到该泛型类型的特定版本。下面的示例展示了 Swift 标准库中的泛型字典类型的简化版本:

Swift
struct Dictionary<Key: Hashable, Value>: Collection, ExpressibleByDictionaryLiteral {
    /* ... */
}

泛型Dictionary的特定版本Dictionary<String, Int>是通过具体的类型实参(StringInt)来替换泛型形参(Key: Hashable 和 Value)而形成的。每个类型实参必须满足它替换的泛型形参的所有约束,包括在泛型where子句中指定的额外要求。在上述例子中,Key类型形参要遵循Hashable协议,因此传入的实参String也要遵循Hashable协议。

还可以用本身就是泛型类型的特定版本的类型实参替代类型形参(假设已满足合适的约束和关联类型要求)。例如,可以将 Array<Element>中的类型形参 Element 替换 Array 泛型专用版本 Array<Int> ,以形成一个元素本身是整数数组的二维数组。

Swift
let arrayOfArrays: Array<Array<Int>> = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

正如在泛型形参子句中提到的,在指定泛型函数或构造器的类型实参时,不能使用泛型实参子句。

泛型实参列表中的最后一个类型实参后面可以跟一个可选的逗号。

泛型实参的语法格式:

generic-argument-clause< generic-argument-list ,? >
generic-argument-listgeneric-argument | generic-argument , generic-argument-list
generic-argumenttype | signed-integer-literal


← 模式