关于泛型 2个月前

编程语言
168
关于泛型

泛型 Where 语句

类型约束 让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。

对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 where 子句来实现。通过泛型 where 子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 where 关键字紧跟在类型参数列表后面来定义 where 子句,where 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。

下面的例子定义了一个名为 allItemsMatch 的泛型函数,用来检查两个 Container 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 true,否则返回 false

被检查的两个 Container 可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个 where 子句来表示:

funcallItemsMatch<C1:Container, C2:Container>
    (_ someContainer: C1, _ anotherContainer: C2) ->Bool
where C1.Item == C2.Item, C1.Item:Equatable {

// 检查两个容器含有相同数量的元素
if someContainer.count!= anotherContainer.count {
returnfalse
        }

// 检查每一对元素是否相等
for i in0..<someContainer.count {
if someContainer[i]!= anotherContainer[i] {
returnfalse
            }
        }

// 所有元素都匹配,返回 true
returntrue
}

这个函数接受 someContaineranotherContainer 两个参数。参数 someContainer 的类型为 C1,参数 anotherContainer 的类型为 C2C1C2 是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。

这个函数的类型参数列表还定义了对两个类型参数的要求:

  • C1 必须符合 Container 协议(写作 C1: Container)。
  • C2 必须符合 Container 协议(写作 C2: Container)。
  • C1Item 必须和 C2Item 类型相同(写作 C1.Item == C2.Item)。
  • C1Item 必须符合 Equatable 协议(写作 C1.Item: Equatable)。

前两个要求定义在函数的类型形式参数列表里,后两个要求定义在了函数的泛型 where 分句中。

这些要求意味着:

  • someContainer 是一个 C1 类型的容器。
  • anotherContainer 是一个 C2 类型的容器。
  • someContaineranotherContainer 包含相同类型的元素。
  • someContainer 中的元素可以通过不等于操作符(!=)来检查它们是否相同。

第三个和第四个要求结合起来意味着 anotherContainer 中的元素也可以通过 != 操作符来比较,因为它们和 someContainer 中的元素类型相同。

这些要求让 allItemsMatch(_:_:) 函数能够比较两个容器,即使它们的容器类型不同。

allItemsMatch(_:_:) 函数首先检查两个容器元素个数是否相同,如果元素个数不同,那么一定不匹配,函数就会返回 false

进行这项检查之后,通过 for-in 循环和半闭区间操作符(..<)来迭代每个元素,检查 someContainer 中的元素是否不等于 anotherContainer 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 false。

如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回 true

下面是 allItemsMatch(_:_:) 函数的示例:

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")

var arrayOfStrings = ["uno", "dos", "tres"]

ifallItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// 打印“All items match.”

上面的例子创建 Stack 实例来存储 String 值,然后将三个字符串压栈。这个例子还通过数组字面量创建了一个 Array 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 Container 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 allItemsMatch(_:_:) 函数。在上面的例子中,allItemsMatch(_:_:) 函数正确地显示了这两个容器中的所有元素都是相互匹配的。

具有泛型 Where 子句的扩展

你也可以使用泛型 where 子句作为扩展的一部分。基于以前的例子,下面的示例扩展了泛型 Stack 结构体,添加一个 isTop(_:) 方法。

extensionStackwhereElement:Equatable {
funcisTop(_item: Element) ->Bool {
guardlet topItem = items.lastelse {
returnfalse
        }
return topItem == item
    }
}

这个新的 isTop(_:) 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 where 子句,会有一个问题:在 isTop(_:) 里面使用了 == 运算符,但是 Stack 的定义没有要求它的元素是符合 Equatable 协议的,所以使用 == 运算符导致编译时错误。使用泛型 where 子句可以为扩展添加新的条件,因此只有当栈中的元素符合 Equatable 协议时,扩展才会添加 isTop(_:) 方法。

以下是 isTop(_:) 方法的调用方式:

if stackOfStrings.isTop("tres") {
print("Top element is tres.")
} else {
print("Top element is something else.")
}
// 打印“Top element is tres.”

如果尝试在其元素不符合 Equatable 协议的栈上调用 isTop(_:) 方法,则会收到编译时错误。

structNotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue =NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue)// 报错

你可以使用泛型 where 子句去扩展一个协议。基于以前的示例,下面的示例扩展了 Container 协议,添加一个 startsWith(_:) 方法。

extensionContainerwhere Item:Equatable {
funcstartsWith(_item: Item) ->Bool {
return count >=1&& self[0]== item
    }
}

这个 startsWith(_:) 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何符合 Container 协议的类型都可以使用这个新的 startsWith(_:) 方法,包括上面使用的栈和数组,只要容器的元素是符合 Equatable 协议的。

if[9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
// 打印“Starts with something else.”

上述示例中的泛型 where 子句要求 Item 遵循协议,但也可以编写一个泛型 where 子句去要求 Item 为特定类型。例如:

extensionContainerwhere Item ==Double {
funcaverage() ->Double {
var sum =0.0
for index in0..<count {
            sum += self[index]
        }
return sum /Double(count)
    }
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// 打印“648.9”

此示例将一个 average() 方法添加到 Item 类型为 Double 的容器中。此方法遍历容器中的元素将其累加,并除以容器的数量计算平均值。它将数量从 Int 转换为 Double 确保能够进行浮点除法。

就像可以在其他地方写泛型 where 子句一样,你可以在一个泛型 where 子句中包含多个条件作为扩展的一部分。用逗号分隔列表中的每个条件。

包含上下文关系的 where 分句

当你使用泛型时,可以为没有独立类型约束的声明添加 where 分句。例如,你可以使用 where 分句为泛型添加下标,或为扩展方法添加泛型约束。Container 结构体是个泛型,下面的例子通过 where 分句让新的方法声明其调用所需要满足的类型约束。

extensionContainer {
funcaverage() ->Doublewhere Item ==Int {
var sum =0.0
for index in0..<count {
            sum +=Double(self[index])
        }
return sum /Double(count)
    }
funcendsWith(_item: Item) ->Boolwhere Item:Equatable {
return count >=1&& self[count-1]== item
    }
}
let numbers = [1260, 1200, 98, 37]
print(numbers.average())
// 输出 "648.75"
print(numbers.endsWith(37))
// 输出 "true"

例子中,当 Item 是整型时为 Container 添加 average() 方法,当 Item 遵循 Equatable 时添加 endsWith(_:) 方法。两个方法都通过 where 分句对 Container 中定义的泛型 Item 进行了约束。

如果不使用包含上下文关系的 where 分句,需要写两个扩展,并为每个扩展分别加上 where 分句。下面的例子和上面的具有相同效果。

extensionContainerwhere Item ==Int {
funcaverage() ->Double {
var sum =0.0
for index in0..<count {
            sum +=Double(self[index])
        }
return sum /Double(count)
    }
}
extensionContainerwhere Item:Equatable {
funcendsWith(_item: Item) ->Bool {
return count >=1&& self[count-1]== item
    }
}

在包含上下文关系的 where 分句的例子中,由于每个方法的 where 分句各自声明了需要满足的条件,因此 average()endsWith(_:) 的实现能放在同一个扩展里。而将 where 分句放在扩展进行声明也能起到同样的效果,但每一个扩展只能有一个必备条件。

具有泛型 Where 子句的关联类型

你可以在关联类型后面加上具有泛型 where 的子句。例如,建立一个包含迭代器(Iterator)的容器,就像是标准库中使用的 Sequence 协议那样。你应该这么写:

protocolContainer {
associatedtype Item
mutatingfuncappend(_item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }

associatedtype Iterator:IteratorProtocolwhereIterator.Element== Item
funcmakeIterator() ->Iterator
}

迭代器(Iterator)的泛型 where 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。makeIterator() 则提供了容器的迭代器的访问接口。

一个协议继承了另一个协议,你通过在协议声明的时候,包含泛型 where 子句,来添加了一个约束到被继承协议的关联类型。例如,下面的代码声明了一个 ComparableContainer 协议,它要求所有的 Item 必须是 Comparable 的。

protocolComparableContainer:Container where Item:Comparable { }

泛型下标

下标可以是泛型,它们能够包含泛型 where 子句。你可以在 subscript 后用尖括号来写占位符类型,你还可以在下标代码块花括号前写 where 子句。例如:

extensionContainer {
subscript<Indices:Sequence>(indices: Indices) -> [Item]
whereIndices.Iterator.Element==Int {
var result: [Item] = []
for index in indices {
                result.append(self[index])
            }
return result
    }
}

这个 Container 协议的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组。这个泛型下标的约束如下:

  • 在尖括号中的泛型参数 Indices,必须是符合标准库中的 Sequence 协议的类型。
  • 下标使用的单一的参数,indices,必须是 Indices 的实例。
  • 泛型 where 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 Int 类型。这样就能确保在序列(Sequence)中的索引和容器(Container)里面的索引类型是一致的。

综合一下,这些约束意味着,传入到 indices 下标,是一个整型的序列。

image
EchoEcho官方
无论前方如何,请不要后悔与我相遇。
1377
发布数
439
关注者
2223433
累计阅读

热门教程文档

React
18小节
10.x
88小节
Java
12小节
Maven
5小节
C#
57小节