为什么没有泛型,您的Swift代码很快就会变成意大利面条
\n\n想象一下,您正在编写一个披萨配送应用程序。您需要处理订单、客户、骑手——每次都要为排序、搜索或过滤编写相同的代码,只改变数据类型?这听起来像是任何开发者的噩梦。正是在这里,Swift中的泛型登场了——这是一个强大的工具,允许您编写灵活、可重用的代码,同时不牺牲类型安全。
\n\n在本文中,我们不仅仅会快速浏览语法。我们将深入探讨Swift中的类型化和泛型如何协同工作,使您的代码不仅美观,而且可靠。您将学习如何消除重复代码,如何使用带有associatedtype的protocol,以及为什么where子句是您最好的朋友。
什么是泛型,为什么它们改变了游戏规则
\n\n泛型是一种机制,允许您编写适用于任何满足特定条件的数据类型的函数、结构体、类和枚举。您不必为Int、String和Double编写三个版本的函数,而是编写一个适用于任何类型的函数。
在Swift中,类型化是严格且静态的。这意味着编译器在编译时检查类型。泛型并不违反这一规则——它们使其更加灵活。编译器在调用函数时自行推断具体类型,但检查仍然存在。
\n\n第一个示例:没有泛型的函数
\n\n// 不好:为三种类型编写三个函数\nfunc swapInts(_ a: inout Int, _ b: inout Int) {\n let temp = a\n a = b\n b = temp\n}\n\nfunc swapStrings(_ a: inout String, _ b: inout String) {\n let temp = a\n a = b\n b = temp\n}\n\nfunc swapDoubles(_ a: inout Double, _ b: inout Double) {\n let temp = a\n a = b\n b = temp\n}\n\n现在,来看一个使用泛型的优雅解决方案:
\n\n// 好:一个函数适用于所有类型\nfunc swapValues<T>(_ a: inout T, _ b: inout T) {\n let temp = a\n a = b\n b = temp\n}\n\n// 使用:\nvar x = 10\nvar y = 20\nswapValues(&x, &y) // T = Int\n\nvar name1 = "Alice"\nvar name2 = "Bob"\nswapValues(&name1, &name2) // T = String\n\n看到魔法了吗?<T> 是一个类型占位符。编译器在调用时自行替换实际类型。这就是Swift中泛型编程的基础。
泛型类型:创建自己的通用栈
\n\nSwift中的泛型不仅适用于函数,也适用于整个类型。让我们创建一个Stack结构体——经典的LIFO(后进先出)数据结构示例。
struct Stack<Element> {\n private var items: [Element] = []\n \n mutating func push(_ item: Element) {\n items.append(item)\n }\n \n mutating func pop() -> Element? {\n return items.popLast()\n }\n \n func peek() -> Element? {\n return items.last\n }\n \n var count: Int {\n return items.count\n }\n}\n\n// 使用:\nvar intStack = Stack<Int>()\nintStack.push(1)\nintStack.push(2)\nintStack.push(3)\nprint(intStack.pop() ?? 0) // 3\n\nvar stringStack = Stack<String>()\nstringStack.push("Swift")\nstringStack.push("Generics")\nprint(stringStack.peek() ?? "") // "Generics"\n\n请注意:我们在声明结构体时在尖括号中指定了Element类型。在创建实例时,我们将其具体化——Stack<Int>或Stack<String>。编译器保证您不会意外地将字符串放入整数栈中。
带有关联类型的协议:协议的泛型
\n\n在Swift中,协议不能直接拥有泛型参数。取而代之的是使用