Generics(泛型)

//泛型 Generics

// “泛型代码让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。”
//“泛型是 Swift 最强大的特性之一,许多 Swift 标准库是通过泛型代码构建的”
//“泛型的使用贯穿了整本语言手册.例如,Swift 的 Array 和 Dictionary 都是泛型集合。你可以创建一个 Int 数组,也可创建一个 String 数组,甚至可以是任意其他 Swift 类型的数组。同样的,你也可以创建存储任意指定类型的字典

//1.泛型解决的问题

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

//“这个函数使用输入输出参数(inout)来交换 a 和 b 的值

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt),anotherInt is now \(anotherInt)")

//“诚然,swapTwoInts(::) 函数挺有用,但是它只能交换 Int 值,如果你想要交换两个 String 值或者 Double值,就不得不写更多的函数”
//“在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题”

//2. 泛型函数
//“泛型函数可以适用于任何类型

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,无论 T 代表什么类型。只有 swapTwoValues(::) 函数在调用时,才能根据所传入的实际类型决定 T 所代表的类型。”
//“另外一个不同之处在于这个泛型函数名(swapTwoValues(::))后面跟着占位类型名(T),并用尖括号括起来(<T>)。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(::) 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 T 的实际类型。”

var someString = "jack"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)

//“注意 上面定义的 swapTwoValues(::) 函数是受 swap(::) 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。”

//3. 类型参数
//“在上面的 swapTwoValues(::) 例子中,占位类型 T 是类型参数的一个例子。类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 <T>)。”
//“一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 swapTwoValues(::) 函数中的参数 a 和 b),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。”
//“你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。”

//4.命名类型参数
//“在大多数情况下,类型参数具有一个描述性名字,例如 Dictionary<Key, Value> 中的 Key 和 Value,以及 Array<Element> 中的 Element,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如 T、U、V,正如上面演示的 swapTwoValues(::) 函数中的 T 一样。”

//5. 泛型类型
//“除了泛型函数,Swift 还允许你定义泛型类型。这些自定义类、结构体和枚举可以适用于任何类型,类似于 Array 和 Dictionary”
//“这部分内容将向你展示如何编写一个名为 Stack (栈)的泛型集合类型”
//“栈是一系列值的有序集合,和 Array 类似,但它相比 Swift 的 Array 类型有更多的操作限制。数组允许在数组的任意位置插入新元素或是删除其中任意位置的元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。”

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

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

//6.泛型版本

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>)。”
//“由于 Stack 是泛型类型,因此可以用来创建 Swift 中任意有效类型的栈,就像 Array 和 Dictionary 那样。”
//“你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 Stack 实例”

var StackOfStings = Stack<String>()
StackOfStings.push(item: "aa")
StackOfStings.push(item: "bb")
StackOfStings.push(item: "cc")
StackOfStings.pop()

//7. 扩展一个泛型类型
//“当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用”

extension Stack {
    var topItem : Element?{
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

//“注意,这个扩展并没有定义一个类型参数列表。相反的,Stack 类型已有的类型参数名称 Element,被用在扩展中来表示计算型属性 topItem 的可选类型。”

if let topItem = StackOfStings.topItem {
    print("the top item on stack is \(topItem)")
}
//打印  the top item on stack is bb

//8. 类型约束
//“swapTwoValues(::) 函数和 Stack 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。”

//“例如,Swift 的 Dictionary 类型对字典的键的类型做了些限制。在字典的描述中,字典的键的类型必须是可哈希(hashable)的。也就是说,必须有一种方法能够唯一地表示它。Dictionary 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,Dictionary 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。”
//“为了实现这个要求,一个类型约束被强制加到 Dictionary 的键类型上,要求其键类型必须符合 Hashable 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 String、Int、Double 和 Bool)默认都是可哈希的。”

//8.1 类型约束语法
//“你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分”

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

//“第一个类型参数 T,有一个要求 T 必须是 SomeClass 子类的类型约束;第二个类型参数 U,有一个要求 U 必须符合 SomeProtocol 协议的类型约束。”

//8.2 类型约束实践

func findIndex(ofString valueToFind:String,in array:[String])->Int?{
    for (index,value) in array.enumerated() {
        if value == valueToFind{
            return index
        }
    }
    return nil
}
let strings = ["cat","dog","lima","jack"]
if let foundIndex = findIndex(ofString: "jack", in: strings) {
    print("the index of jack is \(foundIndex)")
}

//泛型版本

//func findIndexGenerics<T>(of valueToFind:T,in array:[T])->Int?{
//    for (index,value) in array.enumerated() {
//        if value == valueToFind{
//            return index
//        }
//    }
//    return nil
//}

//“上面所写的函数无法通过编译。问题出在相等性检查上,即 "if value == valueToFind"。不是所有的 Swift 类型都可以用等式符(==)进行比较,“比如说,如果你创建一个自定义的类或结构体来表示一个复杂的数据模型,那么 Swift 无法猜到对于这个类或结构体而言“相等”意味着什么。正因如此,这部分代码无法保证适用于每个可能的类型 T,当你试图编译这部分代码时会出现相应的错误。”
//“不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 Equatable 协议,该协议要求任何遵循该协议的类型必须实现等式符(==)及不等符(!=),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型 自动支持 Equatable 协议。”

func findIndexGenerices<T:Equatable>(array:[T],_ valuetoFind:T)->Int?{
    for (index,value) in array.enumerated() {
        if value == valuetoFind {
            return index
        }
    }
    return nil
}
let doubleIndex = findIndexGenerices(array: [3,14159,0.1,0.25], 9.3)
//doubleIndex的值为nil
let stingIndex = findIndexGenerices(array: ["mike","jordan","rose"], "rose")
//stingIndex类型为int? 值为2

//9. 关联类型
//“定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用”“关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 associatedtype 关键字来指定关联类型。”

protocol Container{
    associatedtype itemType
    mutating func append(item:itemType)
    var count : Int{get}
    subscript(i:Int)->itemType{get}
}
struct IntStackGenerics:Container {
    var items = [Int]()
    mutating func push(item:Int){
        items.append(item)
    }
    mutating func pop()->Int{
        return items.removeLast()
    }
    
    //Container协议实现部分
    typealias itemType = Int
    mutating func append(item: Int) {
        self.push(item: item)
    }
    var count: Int {
        return items.count
    }
    subscript(i:Int)->Int{
        return items[i]
    }
}

//“IntStack 结构体实现了 Container 协议的三个要求,其原有功能也不会和这些要求相冲突。
//此外,IntStack 在实现 Container 的要求时,指定 ItemType 为 Int 类型,即 typealias ItemType = Int,从而将 Container 协议中抽象的 ItemType 类型转换为具体的 Int 类型。”
//“由于 Swift 的类型推断,你实际上不用在 IntStack 的定义中声明 ItemType 为 Int。因为 IntStack 符合 Container 协议的所有要求,Swift 只需通过 append(_:) 方法的 item 参数类型和下标返回值的类型,就可以推断出 ItemType 的具体类型。事实上,如果你在上面的代码中删除了 typealias ItemType = Int 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 ItemType 应该是哪种类型。”

//也可以让泛型Stack结构体遵从Container协议
struct StackGenerics<Element>:Container{
    //StackCenerics<Element>的原始部分
    var items = [Element]()
    mutating func push(item:Element){
        items.append(item)
    }
    mutating func pop()->Element{
        return items.removeLast()
    }
    //Container协议实现部分
   mutating func append(item: StackGenerics<Element>.itemType) {
        self.push(item: item)
    }
    var count: Int{
        return items.count
    }
    subscript(i:Int)->Element{
        return items[i]
    }
}

//“这一次,占位类型参数 Element 被用作 append(_:) 方法的 item 参数和下标的返回类型。Swift 可以据此推断出 Element 的类型即是 ItemType 的类型。

//9.2 “通过扩展一个存在的类型来指定关联类型”
//“通过扩展添加协议一致性中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型的协议。”
//“Swift 的 Array 类型已经提供 append(:) 方法,一个 count 属性,以及一个接受 Int 类型索引值的下标用以检索其元素。这三个功能都符合 Container 协议的要求,也就意味着你只需简单地声明 Array 采纳该协议就可以扩展 Array,使其遵从 Container 协议。你可以通过一个空扩展来实现这点,正如通过扩展采纳协议中的描述:”
extension Array:Container{}
//“如同上面的泛型 Stack 结构体一样,Array 的 append(
:) 方法和下标确保了 Swift 可以推断出 ItemType 的类型。定义了这个扩展后,你可以将任意 Array 当作 Container 来使用。”

//10.泛型Where语句
//“类型约束让你能够为泛型函数或泛型类型的类型参数定义一些强制要求。”
//“为关联类型定义约束也是非常有用的。你可以在参数列表中通过 where 子句为关联类型定义约束”

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

func allItemsMatch<C1:Container,C2:Container>(_ someContainer:C1,_ anotherContainer:C2)->Bool where C1.itemType == C2.itemType,C1.itemType:Equatable{
    //检查两个容器含有相同数量的元素
    if someContainer.count != anotherContainer.count {
        return false
    }
    //检查每一对元素是否相等
    for i in 0..<someContainer.count{
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    return true
}
/*
 “C1 必须符合 Container 协议(写作 C1: Container)。
  C2 必须符合 Container 协议(写作 C2: Container)。
  C1 的 ItemType 必须和 C2 的 ItemType类型相同(写作 C1.ItemType == C2.ItemType)。
  C1 的 ItemType 必须符合 Equatable 协议(写作 C1.ItemType: Equatable)。
 */

//“第三个和第四个要求被定义为一个 where 子句,写在关键字 where 后面,它们也是泛型函数类型参数列表的一部分”

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 泛型代码可以确保你写出灵活的,可重用的函数和定义出任何你所确定好的需求的类型。你的可以写出避免重复的代码,并且用一...
    iOS_Developer阅读 790评论 0 0
  • 本章将会介绍 泛型所解决的问题泛型函数类型参数命名类型参数泛型类型扩展一个泛型类型类型约束关联类型泛型 Where...
    寒桥阅读 629评论 0 2
  • 泛型(Generics) 泛型代码允许你定义适用于任何类型的,符合你设置的要求的,灵活且可重用的 函数和类型。泛型...
    果啤阅读 663评论 0 0
  • 136.泛型 泛型代码让你可以写出灵活,可重用的函数和类型,它们可以使用任何类型,受你定义的需求的约束。你可以写出...
    无沣阅读 1,452评论 0 4
  • 一般只问return true/false, 问number 都可以用动态规划来做。 这题的误区是容易以为是一个递...
    98Future阅读 191评论 0 0