余晖落尽暮晚霞,黄昏迟暮远山寻
本站
当前位置:网站首页 > 编程知识 > 正文

Swift 泛型(swift 泛型协议)

xiyangw 2022-12-03 12:07 24 浏览 0 评论

Swift 提供了泛型让你写出灵活且可重用的函数和类型。

Swift 标准库是通过泛型代码构建出来的。

Swift 的数组和字典类型都是泛型集。

你可以创建一个Int数组,也可创建一个String数组,或者甚至于可以是任何其他 Swift 的类型数据数组。

以下实例是一个非泛型函数 exchange 用来交换两个 Int 值:

实例

// 定义一个交换两个变量的函数funcswapTwoInts(_a: inoutInt, _b: inoutInt){lettemporaryA = aa = bb = temporaryA}varnumb1 = 100varnumb2 = 200print("交换前数据: \(numb1) 和 \(numb2)")swapTwoInts(&numb1, &numb2)print("交换后数据: \(numb1) 和 \(numb2)")

以上程序执行输出结果为:

交换前数据: 100 和 200交换后数据: 200 和 100

以上实例只试用与交换整数 Int 类型的变量。如果你想要交换两个 String 值或者 Double 值,就得重新写个对应的函数,例如 swapTwoStrings(_:_:) 和 swapTwoDoubles(_:_:),如下所示:

String 和 Double 值交换函数

funcswapTwoStrings(_a: inoutString, _b: inoutString){lettemporaryA = aa = bb = temporaryA}funcswapTwoDoubles(_a: inoutDouble, _b: inoutDouble){lettemporaryA = aa = bb = temporaryA}

从以上代码来看,它们功能代码是相同的,只是类型上不一样,这时我们可以使用泛型,从而避免重复编写代码。

泛型使用了占位类型名(在这里用字母 T 来表示)来代替实际类型名(例如 Int、String 或 Double)。

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

swapTwoValues 后面跟着占位类型名(T),并用尖括号括起来(<T>)。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(_:_:) 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 T 的实际类型。

以下实例是一个泛型函数 exchange 用来交换两个 Int 和 String 值:

实例

// 定义一个交换两个变量的函数funcswapTwoValues<T>(_a: inoutT, _b: inoutT){lettemporaryA = aa = bb = temporaryA}varnumb1 = 100varnumb2 = 200print("交换前数据: \(numb1) 和 \(numb2)")swapTwoValues(&numb1, &numb2)print("交换后数据: \(numb1) 和 \(numb2)")varstr1 = "A"varstr2 = "B"print("交换前数据: \(str1) 和 \(str2)")swapTwoValues(&str1, &str2)print("交换后数据: \(str1) 和 \(str2)")

以上程序执行输出结果为:

交换前数据: 100 和 200交换后数据: 200 和 100交换前数据: A 和 B交换后数据: B 和 A

泛型类型

Swift 允许你定义你自己的泛型类型。

自定义类、结构体和枚举作用于任何类型,如同 Array 和 Dictionary 的用法。

接下来我们来编写一个名为 Stack (栈)的泛型集合类型,栈只允许在集合的末端添加新的元素(称之为入栈),且也只能从末端移除元素(称之为出栈)。

图片中从左到右解析如下:

  • 三个值在栈中。

  • 第四个值被压入到栈的顶部。

  • 现在有四个值在栈中,最近入栈的那个值在顶部。

  • 栈中最顶部的那个值被移除,或称之为出栈。

  • 移除掉一个值后,现在栈又只有三个值了。

以下实例是一个非泛型版本的栈,以 Int 型的栈为例:

Int 型的栈

structIntStack{varitems = [Int]()mutatingfuncpush(_item: Int){items.append(item)}mutatingfuncpop() -> Int{returnitems.removeLast()}}

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

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

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

泛型的栈

structStack<Element> {varitems = [Element]()mutatingfuncpush(_item: Element){items.append(item)}mutatingfuncpop() -> Element{returnitems.removeLast()}}varstackOfStrings = Stack<String>()print("字符串元素入栈: ")stackOfStrings.push("google")stackOfStrings.push("runoob")print(stackOfStrings.items); letdeletetos = stackOfStrings.pop()print("出栈元素: " + deletetos)varstackOfInts = Stack<Int>()print("整数元素入栈: ")stackOfInts.push(1)stackOfInts.push(2)print(stackOfInts.items);

实例执行结果为:

字符串元素入栈: ["google", "runoob"]出栈元素: runoob整数元素入栈: [1, 2]

Stack 基本上和 IntStack 相同,占位类型参数 Element 代替了实际的 Int 类型。

以上实例中 Element 在如下三个地方被用作占位符:

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

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

  • 指定 pop() 方法的返回值类型必须是 Element 类型。


扩展泛型类型

当你扩展一个泛型类型的时候(使用 extension 关键字),你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

下面的例子扩展了泛型类型 Stack,为其添加了一个名为 topItem 的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除:

泛型

structStack<Element> {varitems = [Element]()mutatingfuncpush(_item: Element){items.append(item)}mutatingfuncpop() -> Element{returnitems.removeLast()}}extensionStack{vartopItem: Element? {returnitems.isEmpty ? nil : items[items.count - 1]}}varstackOfStrings = Stack<String>()print("字符串元素入栈: ")stackOfStrings.push("google")stackOfStrings.push("runoob")iflettopItem = stackOfStrings.topItem{print("栈中的顶部元素是:\(topItem).")}print(stackOfStrings.items)

实例中 topItem 属性会返回一个 Element 类型的可选值。当栈为空的时候,topItem 会返回 nil;当栈不为空的时候,topItem 会返回 items 数组中的最后一个元素。

以上程序执行输出结果为:

字符串元素入栈: 栈中的顶部元素是:runoob.["google", "runoob"]

我们也可以通过扩展一个存在的类型来指定关联类型。

例如 Swift 的 Array 类型已经提供 append(_:) 方法,一个 count 属性,以及一个接受 Int 类型索引值的下标用以检索其元素。这三个功能都符合 Container 协议的要求,所以你只需简单地声明 Array 采纳该协议就可以扩展 Array。

以下实例创建一个空扩展即可:

extension Array: Container {}

类型约束

类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。

类型约束语法

你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
 // 这里是泛型函数的函数体部分}

上面这个函数有两个类型参数。第一个类型参数 T,有一个要求 T 必须是 SomeClass 子类的类型约束;第二个类型参数 U,有一个要求 U 必须符合 SomeProtocol 协议的类型约束。

实例

泛型

// 非泛型函数,查找指定字符串在数组中的索引funcfindIndex(ofStringvalueToFind: String, inarray: [String]) -> Int? {for(index, value)inarray.enumerated(){ifvalue == valueToFind{// 找到返回索引值returnindex}}returnnil}letstrings = ["google", "weibo", "taobao", "runoob", "facebook"]ifletfoundIndex = findIndex(ofString: "runoob", in: strings){print("runoob 的索引为 \(foundIndex)")}

索引下标从 0 开始。

以上程序执行输出结果为:

runoob 的索引为 3

关联类

Swift 中使用 associatedtype 关键字来设置关联类型实例。

下面例子定义了一个 Container 协议,该协议定义了一个关联类型 ItemType。

Container 协议只指定了三个任何遵从 Container 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。

// Container 协议protocol Container {
 associatedtype ItemType
 // 添加一个新元素到容器里
 mutating func append(_ item: ItemType)
 // 获取容器中元素的数
 var count: Int { get }
 // 通过索引值类型为 Int 的下标检索到容器中的每一个元素
 subscript(i: Int) -> ItemType { get }}// Stack 结构体遵从 Container 协议struct Stack<Element>: Container {
 // Stack<Element> 的原始实现部分
 var items = [Element]()
 mutating func push(_ item: Element) {
 items.append(item)
 }
 mutating func pop() -> Element {
 return items.removeLast()
 }
 // Container 协议的实现部分
 mutating func append(_ item: Element) {
 self.push(item)
 }
 var count: Int {
 return items.count }
 subscript(i: Int) -> Element {
 return items[i]
 }}var tos = Stack<String>()tos.push("google")tos.push("runoob")tos.push("taobao")// 元素列表print(tos.items)// 元素个数print( tos.count)

以上程序执行输出结果为:

["google", "runoob", "taobao"]3

Where 语句

类型约束能够确保类型符合泛型函数或类的定义约束。

你可以在参数列表中通过where语句定义参数的约束。

你可以写一个where语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。

实例

下面的例子定义了一个名为allItemsMatch的泛型函数,用来检查两个Container实例是否包含相同顺序的相同元素。

如果所有的元素能够匹配,那么返回 true,反之则返回 false。

泛型

// Container 协议protocolContainer{associatedtypeItemType// 添加一个新元素到容器里mutatingfuncappend(_item: ItemType)// 获取容器中元素的数varcount: Int{get}// 通过索引值类型为 Int 的下标检索到容器中的每一个元素subscript(i: Int) -> ItemType{get}}// // 遵循Container协议的泛型TOS类型structStack<Element>: Container{// Stack<Element> 的原始实现部分varitems = [Element]()mutatingfuncpush(_item: Element){items.append(item)}mutatingfuncpop() -> Element{returnitems.removeLast()}// Container 协议的实现部分mutatingfuncappend(_item: Element){self.push(item)}varcount: Int{returnitems.count}subscript(i: Int) -> Element{returnitems[i]}}// 扩展,将 Array 当作 Container 来使用extensionArray: Container{}funcallItemsMatch<C1: Container, C2: Container> (_someContainer: C1, _anotherContainer: C2) -> BoolwhereC1.ItemType == C2.ItemType, C1.ItemType: Equatable{// 检查两个容器含有相同数量的元素ifsomeContainer.count != anotherContainer.count{returnfalse}// 检查每一对元素是否相等foriin0..<someContainer.count{ifsomeContainer[i] != anotherContainer[i]{returnfalse}}// 所有元素都匹配,返回 truereturntrue}vartos = Stack<String>()tos.push("google")tos.push("runoob")tos.push("taobao")varaos = ["google", "runoob", "taobao"]ifallItemsMatch(tos, aos){print("匹配所有元素")}else{print("元素不匹配")}

以上程序执行输出结果为:

匹配所有元素

相关推荐

Vue的框架(了解)

前端MVC设计模式MVC设计模式,其实就是将前端实现某个业务的所有代码划分为三部分Model:模型,指数据模型,这个数据一般来自于服务器View:视图,指页面标签内容Controller:控制...

Vue.js实战 第五章练习一

练习要求:在原有表格基础上,新增一项是否选中该商品的功能,总价变为只计算选中商品的总价,同时提供一个全选的按钮。实现思路:按照vue数据和dom元素双向绑定的特性,定义allCheckStatus变量...

Vue基础到进阶教程之class和style绑定

关于class和style我们并不陌生,这个在学习css的时候就是家常便饭了,操作元素的class列表和内联样式是数据绑定的一个常见需求。因为它们都是属性,所以我们可以用v-bind处理它们,...

深入Vue 必学高阶组件 HOC「进阶篇」

作者:ssh转发连接:https://mp.weixin.qq.com/s/seKoLSIMtTd1sU4uDrgZCA前言高阶组件这个概念在React中一度非常流行,但是在Vue的社区里讨论...

周末大礼包,23道高质量中级前端面试题。金九银十,建议收藏

这套面试题考察的内容比较常见,涉及到JavaScript、ES6、CSS、Vue、简单算法,浏览器相关知识等。题目列表1.JavaScript的数据类型有哪些2.什么是同源策略3.跨域的方法...

vue3.0-摒弃Object.defineProperty,基于 Proxy 的观察者机制

写在前面:11月16日早上,Vue.js的作者尤大大在VueToronto的主题演讲中预演了Vue.js3.0的一些新特性,其中一个很重要的改变就是Vue3将使用ES6的Proxy作...

程序员都必掌握的前端教程之VUE基础教程(七)

阅读本文约需要10分钟,您可以先关注我们,避免下次无法找到。本篇文章成哥继续带大家来学习前端VUE教程,今天主要讲解VUE的表单处理等知识点。下面我们就一起来学习该块内容吧!01简介在日常开发中,我...

web前端开之网站搭建框架之vue详解

网站搭建框架之vueVue是web前端快速搭建网站的框架之一。它与jQuery有所不同,是以数据驱动web界面(以操作数据改变页面,而jQuery是以操作节点来改变页面),同时,vue还实现了数据的双...

vue3.0尝鲜-基于 Proxy 的观察者机制探索

Vue.js的作者尤大大在VueToronto的主题演讲中预演了Vue.js3.0的一些新特性,其中一个很重要的改变就是Vue3将使用ES6的Proxy作为其观察者机制,取代之前使用...

TypeScript 设计模式之观察者模式

一、模式介绍1.背景介绍在软件系统中经常碰到这类需求:当一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化。这是建立一种「对象与对象之间的依赖关系」,一个对象发生改变时将「自动通知其他...

vue面试3

1.单页面应用与多页面应用的去别2.简述一下Sass、Less,且说明区别?他们是动态的样式语言,是CSS预处理器,CSS上的一种抽象层。他们是一种特殊的语法/语言而编译成CSS。变量符不一样,les...

VUE v-bind 数据绑定

动态的绑定一个或多个attribute,也可以是组件的prop。缩写::或者.(当使用.prop修饰符)期望:any(带参数)|Object(不带参数)参数:attrOrP...

vue初学习之自定义选择框实现

v-model简单介绍在使用vue的过程中会经常用到input和textarea这类表单元素,vue对于这些元素的数据绑定和我们以前经常用的jQuery有些区别。vue使用v-model实现这些标签...

Vue实现拖拽穿梭框功能四种方式

一、使用原生js实现拖拽打开视频讲解更加详细Vue实现拖拽穿梭框功能的四种方式_哔哩哔哩_bilibili<html><head><meta...

Vue3.x setup 语法糖实现props双向绑定

1.背景为了封装一下Element-Plus的分页插件,需要实现父子组件之间的传值。2.父组件<scriptsetuplang="ts">letqueryPa...

取消回复欢迎 发表评论: