如何在Swift中使用泛型

泛型允许您声明一个变量,该变量在执行时可以分配给我们定义的一组类型。

在Swift中,数组可以保存任何类型的数据。 如果需要整数,字符串或浮点数的数组,则可以使用Swift标准库创建一个数组。 声明数组时应定义的类型。 数组是使用泛型的常见示例。 如果要实现自己的集合,则肯定要使用泛型。

让我们探究泛型及其允许我们做的伟大的事情。

1.通用功能

我们首先创建一个简单的泛型函数。 我们的目标是创建一个函数来检查是否有两个对象属于同一类型。 如果它们是同一类型,那么我们将使第二个对象的值等于第一个对象的值。 如果它们不是同一类型,那么我们将打印“不同类型”。 这是在Swift中实现这种功能的尝试。

func sameType (one: Int, inout two: Int) -> Void {
    // This will always be true
    if(one.dynamicType == two.dynamicType) {
        two = one
    }
    else {
        print("not same type")
    }
}

在没有仿制药的世界中,我们遇到了一个重大问题。 在函数的定义中,我们需要指定每个参数的类型。 结果,如果我们希望函数能够与每种可能的类型一起使用,就必须为每种可能的类型组合编写具有不同参数的函数定义。 那不是一个可行的选择。

func sameType (one: Int, inout two: String) -> Void {
    // This would always be false
    if(one.dynamicType == two.dynamicType) {
        two = one
    }
    else {
        print("not same type")
    }
}

我们可以通过使用泛型来避免此问题。 看看下面的示例,其中我们利用了泛型。

func sameType<T,E>(one: T, inout two: E) -> Void {
    if(one.dynamicType == two.dynamicType) {
        two = one
    }
    else {
        print("not same type")
    }
}

在这里,我们看到了使用泛型的语法。 通用类型由TE 。 通过在函数名称后面的函数定义中添加<T,E>来指定类型。 可以将TE用作我们使用函数的任何类型的占位符。

但是,此功能存在一个主要问题。 它不会编译。 编译器将引发错误,指示T不可转换为E 泛型假定由于TE具有不同的标签,因此它们也将是不同的类型。 很好,我们仍然可以通过两个函数定义来实现我们的目标。

func sameType<T,E>(one: T, inout two: E) -> Void {
    print("not same type")
}

func sameType<T>(one: T, inout two: T) -> Void {
    two = one
}

函数的参数有两种情况:

  • 如果它们是同一类型,则调用第二种实现。 然后将值two赋给one
  • 如果它们属于不同类型,则将调用第一个实现,并将字符串“ not same type”打印到控制台。

我们已经将函数定义从可能无限数量的参数类型组合减少到只有两个。 现在,我们的函数可以将类型的任何组合用作参数。

var s = "apple"
var p = 1

sameType(2,two: &p)
print(p)
sameType("apple", two: &p)

// Output:
1
"not same type"

泛型编程也可以应用于类和结构。 让我们看看它是如何工作的。

2.通用类和结构

考虑一下我们想要创建自己的数据类型(二叉树)的情况。 如果我们使用不使用泛型的传统方法,那么我们将制作只能容纳一种数据类型的二叉树。 幸运的是,我们有泛型。

一棵二叉树由以下节点组成:

  • 两个孩子或分支,这是其他节点
  • 通用数据
  • 父节点,通常不被该节点引用
二叉树

每个二叉树都有一个没有父节点的头节点。 通常将两个子项区分为左节点和右节点。

左子节点中的任何数据都必须小于父节点。 右子级中的任何数据都必须大于父节点。

class BTree <T: Comparable> {

    var data: T? = nil
    var left: BTree<T>? = nil
    var right: BTree<T>? = nil
    
    func insert(newData: T) {
        if (self.data > newData) {
            // Insert into left subtree
        }
        else if (self.data < newData) {
            // Insert into right subtree
        }
        else if (self.data == nil) {
            self.data = newData
            return
        }
        
    }
    
}

BTree类的声明还声明了泛型T ,该泛型TComparable协议约束。 我们将稍后讨论协议和约束。

我们树的数据项被指定为T类型。 如insert(_:)方法的声明所指定的,任何插入的元素也必须是T类型。 对于泛型类,在声明对象时指定类型。

var tree: BTree<int>

在这个例子中,我们创建一个整数的二叉树。 制作泛型类非常简单。 我们要做的就是在声明中包含泛型,并在必要时在主体中引用它。

3.协议和约束

在许多情况下,我们必须操纵数组以实现编程目标。 这可能是排序,搜索等。我们将研究泛型如何帮助我们进行搜索。

我们使用通用函数进行搜索的主要原因是我们希望能够搜索数组,而不管其持有的对象是什么类型。

func find <T> (array: [T], item : T) ->Int? {
    var index = 0
    while(index < array.count) {
        if(item == array[index]) {
            return index
        }
        index++
    }
    return nil;
}

在上面的示例中, find(array:item:)函数接受通用类型T的数组,并在该数组中搜索与也是T类型的item的匹配item

不过有一个问题。 如果尝试编译以上示例,则编译器将引发另一个错误。 编译器告诉我们,二进制运算符==不能应用于两个T操作数。 如果考虑一下,原因很明显。 我们不能保证泛型T支持==运算符。 幸运的是,Swift涵盖了这一点。 请看下面的更新示例。

func find <T: Equatable> (array: [T], item : T) ->Int? {
    var index = 0
    while(index < array.count) {
        if(item == array[index]) {
            return index
        }
        index++
    }
    return nil;
}

如果我们指定泛型类型必须符合Equatable协议,则编译器会给我们通过。 换句话说,我们对T可以代表什么类型施加约束。 要将约束添加到泛型,请在尖括号之间列出协议。

但是,某种东西变得Equatable是什么意思? 它仅表示它支持比较运算符==

Equatable不是我们可以使用的唯一协议。 Swift还有其他协议,例如Hashable Comparable 。 我们在二叉树示例中看到了Comparable 。 如果类型符合Comparable协议,则表示支持<>运算符。 希望您可以使用自己喜欢的任何协议并将其作为约束,这很显然。

4.定义协议

让我们用一个游戏示例来演示实际的约束和协议。 在任何游戏中,我们都会有许多需要随时间更新的对象。 此更新可能针对对象的位置,健康状况等。现在,让我们以对象健康状况为例。

在游戏的实现中,我们有许多健康状况不同的对象,它们可能是敌人,盟友,中立者等。它们将不是同一类,因为我们所有不同的对象都可能具有不同的功能。

我们想创建一个称为check(_:)的函数   检查给定对象的健康状况并更新其当前状态。 根据对象的状态,我们可以更改其运行状况。 我们希望此功能对所有对象起作用,无论它们的类型如何。 这意味着我们需要进行check(_:) 通用功能。 这样,我们可以遍历不同的对象并在每个对象上调用check(_:)

所有这些对象都必须具有一个代表其运行状况的变量和一个用于更改其活动状态的函数。 让我们为此声明一个协议,并将其命名为Healthy

protocol Healthy {
    mutating func setAlive(status: Bool)
    var health: Int { get }
}

协议定义了需要实现符合协议类型的属性和方法。 例如,该协议要求符合Healthy协议的任何类型都必须实现变异setAlive(_:)函数。 该协议还需要一个名为health的属性。

现在,让我们重新访问之前声明的check(_:)函数。 我们在声明中指定一个约束,即类型T必须符合Healthy协议。

func check<T:Healthy>(inout object: T) {
    if (object.health <= 0) {
        object.setAlive(false)
    }
}

我们检查对象的health属性。 如果它小于或等于零,我们在对象上调用setAlive(_:) ,并传入false 。 因为需要T来符合Healthy协议,所以我们知道可以在传递给check(_:)函数的任何对象上调用setAlive(_:) check(_:)函数。

5.关联类型

如果您想进一步控制协议,则可以使用关联的类型。 让我们回顾一下二叉树的例子。 我们想创建一个函数来对二叉树进行操作。 我们需要某种方法来确保输入参数满足我们定义为二叉树的条件。 为了解决这个问题,我们可以创建一个BinaryTree协议。

protocol BinaryTree {
    typealias dataType
    mutating func insert(data: dataType)
    func index(i: Int) -> dataType
    var data: dataType { get }
}

这使用关联的类型typealias dataTypedataType与泛型类似。 较早的T ,其行为类似于dataType 。 我们指定一个二叉树必须实现功能insert(_:)  index(_:) insert(_:)接受一个dataType类型的参数 index(_:)返回一个dataType对象。 我们还指定二叉树必须具有类型为dataType的属性data

由于我们有关联的类型,我们知道我们的二叉树将是一致的。 我们可以假设,类型传递给insert(_:) ,给出index(_:) 以及所持data是每个相同。 如果类型不尽相同,就会遇到问题。

6.凡条款

Swift还允许您将where子句与泛型一起使用。 让我们看看它是如何工作的。 子句允许我们使用泛型来完成两件事:

  • 我们可以强制协议中的关联类型或变量为同一类型。
  • 我们可以将协议分配给关联的类型。

为了说明这一点,让我们实现一个操作二叉树的函数。 目标是找到两个二叉树之间的最大值。

为了简单起见,我们将在BinaryTree协议中添加一个名为inorder()的函数。 顺序三种流行的深度优先遍历类型之一 。 它是递归移动的树节点(左子树,当前节点,右子树)的顺序。

protocol BinaryTree {
    typealias dataType
    mutating func insert(data: dataType)
    func index(i: Int) -> dataType
    var data: dataType { get }
    // NEW
    func inorder() -> [dataType]
}

我们期望inorder()函数返回关联类型的对象数组。 我们还实现了功能twoMax(treeOne:treeTwo:) 该函数接受两个二进制树。

func twoMax<B: BinaryTree, T: BinaryTree where B.dataType == T.dataType, B.dataType: Comparable, T.dataType: Comparable> (inout treeOne: B, inout treeTwo: T) -> B.dataType {
        var inorderOne = treeOne.inorder()
        var inorderTwo = treeTwo.inorder()
        
        if (inorderOne[inorderOne.count] > inorderTwo[inorderTwo.count]) {
            return inorderOne[inorderOne.count]
        } else {
            return inorderTwo[inorderTwo.count]
        }
}

由于where子句,我们的声明很长。 第一个要求B.dataType == T.dataType指出两个二进制树的关联类型应该相同。 这意味着它们的data对象应为同一类型。

第二组需求B.dataType: Comparable, T.dataType: Comparable ,指出两者的关联类型必须符合Comparable协议。 这样,我们可以检查执行比较时的最大值。

有趣的是,由于二叉树的性质,我们知道有序的最后一个元素将是该树中的最大元素。 这是因为在二叉树中,最右边的节点是最大的。 我们只需要查看这两个元素即可确定最大值。

我们有三种情况:

  1. 如果树一包含最大值,那么其有序的最后一个元素将是最大的,我们将在第一个if语句中将其返回。
  2. 如果树二包含最大值,那么其有序的最后一个元素将是最大的,我们将在第一个if语句的else子句中将其返回。
  3. 如果它们的最大值相等,则我们返回树二的顺序中的最后一个元素,这仍然是两者的最大值。

结论

在本教程中,我们专注于Swift中的泛型。 我们已经了解了泛型的价值,并探讨了如何在函数,类和结构中使用泛型。 我们还在协议中使用了泛型,并探讨了关联的类型和where子句。

有了对泛型的良好理解,您现在就可以创建更多通用的代码,并且能够更好地处理棘手的编码问题。

翻译自: https://code.tutsplus.com/tutorials/how-to-use-generics-in-swift--cms-24919

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值