Swift5.1—泛型

简介: Swift5.1—泛型

泛型代码让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,而是用一种清晰抽象的方式来表达代码的意图。


泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。实际上,即使你没有意识到,你也一直在语言指南中使用泛型。例如,Swift 的 Array 和 Dictionary 都是泛型集合。你可以创建一个 Int 类型数组,也可创建一个 String 类型数组,甚至可以是任意其他 Swift 类型的数组。同样,你也可以创建一个存储任意指定类型的字典,并对该类型没有限制。


泛型解决的问题



下面是一个标准的非泛型函数 swapTwoInts(_:_:),用来交换两个 Int 值:


func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}


这个函数使用输入输出参数(inout)来交换 ab 的值,具体请参考 输入输出参数


swapTwoInts(_:_:) 函数将 b 的原始值换成了 a,将 a 的原始值换成了 b,你可以调用这个函数来交换两个 Int 类型变量:


var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 打印“someInt is now 107, and anotherInt is now 3”


swapTwoInts(_:_:) 函数很实用,但它只能作用于 Int 类型。如果你想交换两个 String 类型值,或者 Double 类型值,你必须编写对应的函数,类似下面 swapTwoStrings(_:_:)swapTwoDoubles(_:_:) 函数:


func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}


你可能注意到了,swapTwoInts(_:_:‘)swapTwoStrings(_:_:)swapTwoDoubles(_:_:) 函数体是一样的,唯一的区别是它们接受的参数类型(IntStringDouble)。


在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)


注意

在上面三个函数中,ab 类型必须相同。如果 ab 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 String 类型的变量和一个 Double 类型的变量互换值。试图这样做将导致编译错误。


泛型函数



泛型函数可适用于任意类型,下面是函数 swapTwoInts(::) 的泛型版本,命名为 swapTwoValues(::):


func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}


swapTwoValues(::) 和 swapTwoInts(::) 函数体内容相同,它们只在第一行不同,如下所示:


func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)


泛型版本的函数使用占位符类型名(这里叫做 T ),而不是 实际类型名(例如 Int、String 或 Double),占位符类型名并不关心 T 具体的类型,但它要求 a 和b 必须是相同的类型,T 的实际类型由每次调用 swapTwoValues(::) 来决定。


泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(swapTwoValues(::))后面跟着占位类型名(T),并用尖括号括起来()。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(::) 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 T的实际类型。


swapTwoValues(::) 函数现在可以像 swapTwoInts(::) 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。swapTwoValues(::) 函数被调用时,T 所代表的类型都会由传入的值的类型推断出来。


在下面的两个例子中,T 分别代表 Int 和 String:


var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt 现在是 107,anotherInt 现在是 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在是“world”,anotherString 现在是“hello”


注意

上面定义的 swapTwoValues(::) 函数是受 swap(::) 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 swapTwoValues(::) 函数的功能,你可以使用已存在的 swap(::) 函数。


类型参数



上面 swapTwoValues(_:_:) 例子中,占位类型 T 是一个类型参数的例子,类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 )。


一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 swapTwoValues(_:_:) 函数中的参数 ab),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 swapTwoValues(_:_:) 例子中,当函数第一次被调用时,TInt 替换,第二次调用时,被 String 替换。)


你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。


命名类型参数



大多情况下,类型参数具有描述性的名称,例如字典 Dictionary 中的 KeyValue 及数组 Array 中的 Element,这能告诉阅读代码的人这些参数类型与泛型类型或函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字符来表示,例如 TUV,例如上面演示函数 swapTwoValues(_:_:) 中的 T


注意

请始终使用大写字母开头的驼峰命名法(例如 TMyTypeParameter)来为类型参数命名,以表明它们是占位类型,而不是一个值。


泛型类型



除了泛型函数,Swift 还允许自定义泛型类型。这些自定义类、结构体和枚举可以适用于任意类型,类似于 ArrayDictionary


本节将向你展示如何编写一个名为 Stack(栈)的泛型集合类型。栈是值的有序集合,和数组类似,但比数组有更严格的操作限制。数组允许在其中任意位置插入或是删除元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。


注意

栈的概念已被 UINavigationController 类用来构造视图控制器的导航结构。你通过调用 UINavigationControllerpushViewController(_:animated:) 方法来添加新的视图控制器到导航栈,通过 popViewControllerAnimated(_:) 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。


下图展示了入栈(push)和出栈(pop)的行为:


2466108-7d6d91404e30f2d8.webp.jpg


  1. 现在有三个值在栈中。
  2. 第四个值被压入到栈的顶部。
  3. 现在栈中有四个值,最近入栈的那个值在顶部。
  4. 栈中最顶部的那个值被移除出栈。
  5. 一个值移除出栈后,现在栈又只有三个值了。


下面展示如何编写一个非泛型版本的栈,以 Int 型的栈为例:


struct IntStack {
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
}


这个结构体在栈中使用一个名为 items 的数组属性来存储值。栈提供了两个方法:push(_:)pop(),用来向栈中压入值以及从栈中移除值。这些方法被标记为 mutating,因为它们需要修改结构体的 items 数组。


上面的 IntStack 结构体只能用于 Int 类型。不过,可以定义一个泛型 Stack 结构体,从而能够处理任意类型的值。


下面是相同代码的泛型版本:


struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}


注意,Stack 基本上和 IntStack 相同,只是用占位类型参数 Element 代替了实际的 Int 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(<Element>)。


Element 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 Element 来引用。在这个例子中,Element 在如下三个地方被用作占位符:

  • 创建 items 属性,使用 Element 类型的空数组对其进行初始化。

  • 指定 push(_:) 方法的唯一参数 item 的类型必须是 Element 类型。
  • 指定 pop() 方法的返回值类型必须是 Element 类型。

由于 Stack 是泛型类型,因此可以用来创建适用于 Swift 中任意有效类型的栈,就像 ArrayDictionary 那样。


你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 Stack 实例。例如,要创建一个 String 类型的栈,可以写成 Stack()


var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 栈中现在有 4 个字符串


下图展示了 stackOfStrings 如何将这四个值压栈:


2466108-021f7b5320239aa1.webp.jpg


移除并返回栈顶部的值“cuatro”,即出栈:


let fromTheTop = stackOfStrings.pop()
// fromTheTop 的值为“cuatro”,现在栈中还有 3 个字符串


下图展示了如何将顶部的值出栈:


2466108-6e95420655a5c71c.webp.jpg


注:

inout修饰的参数是不能有默认值的,有范围的参数集合也不能被修饰,另外,一个参数一旦被inout修饰,就不能再被var和let修饰了。

目录
相关文章
|
8月前
|
存储 安全 Swift
Swift 语言:什么是 Swift 的泛型(Generics)?
Swift 语言:什么是 Swift 的泛型(Generics)?
90 0
|
2月前
|
安全 开发工具 Swift
Swift 是苹果公司开发的现代编程语言,具备高效、安全、简洁的特点,支持类型推断、闭包、泛型等特性,广泛应用于苹果各平台及服务器端开发
Swift 是苹果公司开发的现代编程语言,具备高效、安全、简洁的特点,支持类型推断、闭包、泛型等特性,广泛应用于苹果各平台及服务器端开发。基础语法涵盖变量、常量、数据类型、运算符、控制流等,高级特性包括函数、闭包、类、结构体、协议和泛型。
33 2
|
2月前
|
Swift 索引 容器
Swift 泛型-Where 语句
Swift 泛型-Where 语句
27 2
|
2月前
|
Swift 索引 容器
Swift 泛型-关联类
Swift 泛型-关联类
26 1
|
2月前
|
Swift 索引 容器
Swift 泛型-扩展泛型类型
Swift 泛型-扩展泛型类型
35 2
|
2月前
|
存储 Swift
Swift 泛型
Swift 泛型
42 2
|
2月前
|
Swift 索引
Swift 泛型-类型约束
Swift 泛型-类型约束
31 1
|
6月前
|
存储 安全 Swift
Swift高级特性:泛型与协议
【7月更文挑战第10天】Swift高级特性:泛型与协议增强代码复用与类型安全。泛型允许编写通用代码,如`swap`函数和泛型`Stack`结构体,支持类型约束如`Comparable`。协议定义行为蓝图,类型遵循协议需实现其要求。通过两者结合,构建高效灵活的代码结构。
|
8月前
|
安全 算法 Swift
【Swift开发专栏】Swift中的泛型编程
【4月更文挑战第30天】Swift的泛型允许开发人员创建可重用的组件,操作多种数据类型,无需针对每种类型编写特定代码。本文分为三部分:1) 泛型基本概念,包括类型参数、泛型函数和类型;2) Swift中的泛型实现,如类型推断、关联类型和泛型约束;3) 泛型编程最佳实践,如明确使用场景、选择有意义的类型参数名称和避免过度泛化。通过理解并应用这些概念,开发者能编写出更灵活、安全和高效的代码。
70 2
|
Swift C++ 索引
33 Swift泛型的发展历史与泛型的概述
Swift泛型的发展历史与泛型的概述
71 0