3.3 类型
17 分钟阅读
原文链接: https://docs.swift.org/latest/documentation/the-swift-programming-language/types/
3.3 类型
使用内置的具名类型和复合类型。
在 Swift 中,类型分为两种:具名类型和复合类型。具名类型是定义时可以赋予特定名字的类型,包括类、结构体、枚举和协议。例如,名为 MyClass 的用户自定义类的实例,类型就是 MyClass。除了用户自定义的具名类型,Swift 标准库还定义了许多常用的具名类型,包括表示数组、字典和可选值的类型。
在其他语言中通常被视为基本类型或原始类型的数据类型——例如表示数字、字符和字符串的类型——其实都是具名类型,是用结构体在 Swift 标准库中定义和实现的。由于它们是具名类型,你可以用扩展声明扩展它们的行为以满足程序的需要,详见扩展和扩展声明。
复合类型是没有名字、由 Swift 语言本身定义的类型。复合类型有两种:函数类型和元组类型。复合类型可以包含具名类型和其他复合类型。例如元组类型 (Int, (Int, Int)) 包含两个元素:第一个是具名类型 Int,第二个是另一个复合类型 (Int, Int)。
你可以给具名类型或复合类型加圆括号,但给类型加圆括号没有任何实际效果。例如 (Int) 等价于 Int。
本章讨论 Swift 语言本身定义的类型,并介绍 Swift 的类型推断行为。
Grammar of a type:
type → function-type
type → array-type
type → dictionary-type
type → type-identifier
type → tuple-type
type → optional-type
type → implicitly-unwrapped-optional-type
type → protocol-composition-type
type → opaque-type
type → boxed-protocol-type
type → metatype-type
type → any-type
type → self-type
type →(type)
类型标注
类型标注显式指定变量或表达式的类型。类型标注以冒号(:)开始,以一个类型结束,如下面的例子所示:
| |
第一个例子里,表达式 someTuple 被指定为具有元组类型 (Double, Double);第二个例子里,函数 someFunction 的参数 a 被指定为类型 Int。
类型标注可以在类型之前包含一个可选的类型特性列表。
Grammar of a type annotation:
type-annotation →
:attributes? type
类型标识符
类型标识符指向一个具名类型,或者指向某个具名类型或复合类型的类型别名。
大多数情况下,类型标识符直接指向与标识符同名的具名类型。例如 Int 是直接指向具名类型 Int 的类型标识符,而类型标识符 Dictionary<String, Int> 直接指向具名类型 Dictionary<String, Int>。
有两种情况类型标识符不指向同名的类型。第一种情况是类型标识符指向某个具名类型或复合类型的类型别名:例如在下面的例子里,类型标注中的 Point 指向元组类型 (Int, Int)。
| |
第二种情况是类型标识符用点(.)语法指向在其他模块中声明、或者嵌套在其他类型中的具名类型。例如下面代码中的类型标识符引用了在 ExampleModule 模块中声明的具名类型 MyType。
| |
Grammar of a type identifier:
type-identifier → type-name generic-argument-clause? | type-name generic-argument-clause?
.type-identifier
type-name → identifier
元组类型
元组类型是由类型组成的逗号分隔列表,并括在圆括号中。
你可以把元组类型用作函数的返回类型,让函数返回一个包含多个值的元组。你也可以给元组类型的元素命名,并用这些名字引用各个元素的值。元素名由一个标识符紧跟一个冒号(:)构成。同时演示这两个特性的例子参见返回多个值的函数。
当元组类型的元素有名字时,这个名字就是类型的一部分。
| |
所有元组类型都包含两个或更多类型,只有 Void 例外,它是空元组类型 () 的类型别名。
Grammar of a tuple type:
tuple-type →
()|(tuple-type-element,tuple-type-element-list)
tuple-type-element-list → tuple-type-element | tuple-type-element,tuple-type-element-list
tuple-type-element → element-name type-annotation | type
element-name → identifier
函数类型
函数类型表示函数、方法或闭包的类型,由以箭头(->)分隔的参数类型和返回类型构成:
| |
参数类型是由类型组成的逗号分隔列表。由于返回类型可以是元组类型,函数类型支持返回多个值的函数和方法。
函数类型 () -> T(其中 T 是任意类型)的参数可以应用 autoclosure 特性,在调用点隐式创建闭包。这提供了一种语法上的便利,让你在调用该函数时不必写出显式闭包就能延迟表达式的求值。自动闭包函数类型参数的例子参见自动闭包。
函数类型可以在它的参数类型中包含可变参数。在语法上,可变参数由基础类型名紧跟三个点(...)构成,例如 Int...。可变参数被视为一个包含基础类型名元素组成的数组,例如可变参数 Int... 被视为 [Int]。使用可变参数的例子参见可变参数。
要指定输入输出参数,就在参数类型前加上 inout 关键字。你不能用 inout 关键字标记可变参数或返回类型。输入输出参数在输入输出参数中讨论。
如果函数类型只有一个参数,而该参数的类型是元组类型,那么写函数类型时这个元组类型必须加圆括号。例如 ((Int, Int)) -> Void 是"接受一个 (Int, Int) 元组类型参数、不返回任何值"的函数类型;相比之下,不加圆括号的 (Int, Int) -> Void 是"接受两个 Int 参数、不返回任何值"的函数类型。同样,由于 Void 是 () 的类型别名,函数类型 (Void) -> Void 与 (()) -> () 相同——都是接受单个空元组实参的函数;它们与 () -> ()(不接受任何实参的函数)并不相同。
函数和方法中的实参名不是对应函数类型的一部分。例如:
| |
由于实参标签不属于函数类型,写函数类型时要省略它们。
| |
如果函数类型包含多于一个箭头(->),这些函数类型从右向左结合。例如函数类型 (Int) -> (Int) -> Int 被理解为 (Int) -> ((Int) -> Int)——也就是一个接受 Int、返回另一个"接受并返回 Int“的函数的函数。
可能抛出或重新抛出错误的函数,其函数类型必须包含 throws 关键字。你可以在 throws 之后的圆括号里写一个类型,指定该函数抛出的错误类型;抛出的错误类型必须遵循 Error 协议。只写 throws 而不指定类型,等同于写 throws(any Error);省略 throws 则等同于写 throws(Never)。函数抛出的错误类型可以是任何遵循 Error 的类型,包括泛型类型、装箱协议类型和不透明类型。
函数抛出的错误类型是函数类型的一部分,而错误类型之间的子类型关系意味着相应的函数类型之间也存在子类型关系。例如,如果你声明一个自定义的 MyError 类型,某些函数类型之间从父类型到子类型的关系如下:
- 抛出任何错误的函数,标记为
throws(any Error) - 抛出特定错误的函数,标记为
throws(MyError) - 不抛出错误的函数,标记为
throws(Never)
这些子类型关系带来如下结果:
- 你可以在使用抛出函数的地方使用不抛出错误的函数。
- 你可以在使用抛出函数的地方使用抛出具体错误类型的函数。
- 你可以在使用"抛出更一般错误类型的函数"的地方,使用抛出更具体错误类型的函数。
如果你在函数类型中用关联类型或泛型类型形参作为抛出的错误类型,那么该关联类型或泛型类型形参会被隐式要求遵循 Error 协议。
抛出错误和重新抛出错误的函数在抛出错误的函数和方法和重新抛出错误的函数和方法中介绍。
异步函数的函数类型必须用 async 关键字标记。async 关键字是函数类型的一部分,同步函数是异步函数的子类型,因此你可以在使用异步函数的地方使用同步函数。关于异步函数的内容,参见异步函数和方法。
非逃逸闭包的限制
作为非逃逸函数的参数,不能被存放在类型为 Any 的属性、变量或常量中,因为这可能让该值逃逸。
作为非逃逸函数的参数,不能作为实参传给另一个非逃逸函数参数。这一限制帮助 Swift 在编译期(而不是运行时)完成更多内存访问冲突检查。例如:
| |
在上面的代码中,takesTwoFunctions(first:second:) 的两个参数都是函数。两个参数都没有标记 @escaping,因此它们都是非逃逸的。
上面例子中标记为"错误"的四次函数调用会造成编译器错误:由于 first 和 second 参数是非逃逸函数,它们不能作为实参传给另一个非逃逸函数参数。相比之下,标记为"OK"的两次函数调用不会造成编译器错误,因为这些调用没有违反该限制:external 不是 takesTwoFunctions(first:second:) 的参数之一。
如果你需要避开这一限制,就把其中一个参数标记为逃逸,或者用 withoutActuallyEscaping(_:do:) 函数临时把某个非逃逸函数参数转换成逃逸函数。关于避免内存访问冲突的内容,参见内存安全。
Grammar of a function type:
function-type → attributes? function-type-argument-clause
async? throws-clause?->typefunction-type-argument-clause →
()
function-type-argument-clause →(function-type-argument-list...?)function-type-argument-list → function-type-argument | function-type-argument
,function-type-argument-list
function-type-argument → attributes? parameter-modifier? type | argument-label type-annotation
argument-label → identifierthrows-clause →
throws|throws(type)
数组类型
Swift 语言为 Swift 标准库中的 Array<Element> 类型提供了下列语法糖:
| |
换句话说,下面两个声明是等价的:
| |
两种情况下,常量 someArray 都声明为字符串数组。数组元素可以通过下标访问:在方括号中指定合法的下标值,someArray[0] 就指向下标 0 处的元素 "Alex"。
你可以通过嵌套方括号来创建多维数组,其中元素基础类型的名字写在最内层的方括号中。例如,你可以用三对方括号创建一个三维整数数组:
| |
访问多维数组中的元素时,最左侧的下标指向最外层数组中该下标处的元素,往右下一个下标指向再嵌套一层的数组中该下标处的元素,依此类推。这意味着在上面的例子里,array3D[0] 指向 [[1, 2], [3, 4]],array3D[0][1] 指向 [3, 4],而 array3D[0][1][1] 指向值 4。
关于 Swift 标准库 Array 类型的详细讨论,参见数组。
Grammar of an array type:
array-type →
[type]
字典类型
Swift 语言为 Swift 标准库中的 Dictionary<Key, Value> 类型提供了下列语法糖:
| |
换句话说,下面两个声明是等价的:
| |
两种情况下,常量 someDictionary 都声明为以字符串为键、以整数为值的字典。
字典的值可以通过下标访问:在方括号中指定相应的键,someDictionary["Alex"] 就指向键 "Alex" 关联的值。下标返回字典值类型的可选值;如果指定的键不在字典中,下标返回 nil。
字典的键类型必须遵循 Swift 标准库中的 Hashable 协议。
关于 Swift 标准库 Dictionary 类型的详细讨论,参见字典。
Grammar of a dictionary type:
dictionary-type →
[type:type]
可选类型
Swift 语言把后置的 ? 定义为 Swift 标准库中具名类型 Optional<Wrapped> 的语法糖。换句话说,下面两个声明是等价的:
| |
两种情况下,变量 optionalInteger 都声明为可选整数类型。注意类型与 ? 之间不能出现空白。
类型 Optional<Wrapped> 是一个有两个成员 none 和 some(Wrapped) 的枚举,用来表示可能存在也可能不存在的值。任何类型都可以被显式声明为(或被隐式转换为)可选类型。如果你在声明可选变量或属性时没有提供初始值,它的值自动默认为 nil。
如果可选类型的实例包含值,你可以用后置运算符 ! 访问该值,如下所示:
| |
用 ! 运算符解包一个值为 nil 的可选值会导致运行时错误。
你也可以用可选链和可选绑定,有条件地对可选表达式执行操作。如果值是 nil,就不执行任何操作,因此也不会产生运行时错误。
更多内容以及使用可选类型的示例,参见可选类型。
Grammar of an optional type:
optional-type → type
?
隐式解包可选类型
Swift 语言把后置的 ! 定义为 Swift 标准库中具名类型 Optional<Wrapped> 的语法糖,并附加"访问时自动解包"的行为。如果你试图使用一个值为 nil 的隐式解包可选值,就会得到运行时错误。除隐式解包这一行为外,下面两个声明是等价的:
| |
注意类型与 ! 之间不能出现空白。
由于隐式解包会改变包含该类型的声明的含义,嵌套在元组类型或泛型类型内部的可选类型——例如字典或数组的元素类型——不能被标记为隐式解包。例如:
| |
由于隐式解包可选值与可选值具有相同的 Optional<Wrapped> 类型,你可以在代码中所有能使用可选值的地方使用隐式解包可选值。例如,你可以把隐式解包可选值赋给可选类型的变量、常量和属性,反之亦然。
与可选值一样,如果你在声明隐式解包可选变量或属性时没有提供初始值,它的值自动默认为 nil。
用可选链有条件地对隐式解包可选表达式执行操作:如果值是 nil,就不执行任何操作,因此也不会产生运行时错误。
关于隐式解包可选类型的更多内容,参见隐式解包可选值。
Grammar of an implicitly unwrapped optional type:
implicitly-unwrapped-optional-type → type
!
协议组合类型
协议组合类型定义这样一种类型:它遵循指定协议列表中的每一个协议,或者是给定类的子类并且遵循指定协议列表中的每一个协议。协议组合类型只能用在类型标注、泛型形参子句和泛型 where 子句中指定类型时。
协议组合类型的形式如下:
| |
协议组合类型让你能够指定一个值,其类型满足多个协议的要求,而无需显式定义一个新的具名协议、让它继承你希望该类型遵循的各个协议。例如,你可以使用协议组合类型 ProtocolA & ProtocolB & ProtocolC,而不必声明一个继承 ProtocolA、ProtocolB 和 ProtocolC 的新协议;同样,你可以用 SuperClass & ProtocolA 代替声明一个新的、继承 SuperClass 并遵循 ProtocolA 的协议。
协议组合列表中的每一项都是下列之一;列表中最多只能包含一个类:
- 类的名字
- 协议的名字
- 某个类型别名,其底层类型是协议组合类型、协议或类
当协议组合类型包含类型别名时,同一个协议可能在定义中出现多次——重复项会被忽略。例如下面代码中 PQR 的定义等价于 P & Q & R。
| |
Grammar of a protocol composition type:
protocol-composition-type → type-identifier
&protocol-composition-continuation
protocol-composition-continuation → type-identifier | protocol-composition-type
不透明类型
不透明类型定义一个遵循某个协议或协议组合的类型,而不指明其底层的具象类型。
不透明类型出现在函数或下标的返回类型位置、函数/下标/构造器的参数类型位置,或者属性的类型位置。不透明类型不能作为元组类型或泛型类型的一部分出现,例如数组的元素类型或可选值的被包装类型。
不透明类型的形式如下:
| |
其中约束是类类型、协议类型、协议组合类型或 Any。只有当某个值所属的类型遵循所列协议或协议组合、或者继承所列的类时,该值才能被用作该不透明类型的实例。与不透明值交互的代码只能以约束所定义的接口允许的方式使用该值。
在编译期,类型为不透明类型的值具有一个具体的具象类型,Swift 可以利用该底层类型进行优化。不过,不透明类型构成一道边界,关于该底层类型的信息不能跨越这道边界。
协议声明不能包含不透明类型。类不能把不透明类型用作非 final 方法的返回类型。
用不透明类型作为返回类型的函数,必须返回值共享同一底层类型的结果。返回类型可以包含属于该函数泛型类型形参的类型,例如函数 someFunction<T>() 可以返回 T 类型或 Dictionary<String, T> 类型的值。
为参数写不透明类型,是使用泛型类型而不为泛型类型形参指定名字的语法糖。这个隐式泛型类型形参带有约束,要求它遵循不透明类型中所列的协议。如果你写了多个不透明类型,每个都会产生自己的泛型类型形参。例如,下面两个声明是等价的:
| |
在第二个声明中,由于泛型类型形参 T1 和 T2 有名字,你可以在代码的其他地方引用这些类型;相比之下,第一个声明中的泛型类型形参没有名字,无法在其他代码中引用。
你不能在可变参数的类型中使用不透明类型。
你不能把不透明类型用作所返回函数类型的参数,也不能用在本身是函数类型的参数类型中作为参数,因为在这些位置,函数的调用方必须构造出那个未知类型的值。
| |
Grammar of an opaque type:
opaque-type →
sometype
装箱协议类型
装箱协议类型定义一个遵循某个协议或协议组合的类型,同时允许该遵循类型在程序运行期间发生变化。
装箱协议类型的形式如下:
| |
其中约束是协议类型、协议组合类型、协议类型的元类型,或者协议组合类型的元类型。
在运行时,装箱协议类型的实例可以包含任何满足该约束的类型的值。这种行为与不透明类型的工作方式形成对比:不透明类型在编译期就有某个特定的遵循类型。使用装箱协议类型时所用的额外一层间接引用称为装箱。装箱通常需要为存储单独分配内存、访问时还要多一层间接引用,因此会在运行时带来性能开销。
把 any 应用于 Any 或 AnyObject 类型没有任何效果,因为这些类型本身就是装箱协议类型。
Grammar of a boxed protocol type:
boxed-protocol-type →
anytype
元类型
元类型指向任何类型的类型,包括类类型、结构体类型、枚举类型和协议类型。
类、结构体或枚举类型的元类型是该类型的名字后跟 .Type;协议类型的元类型——而不是运行时遵循该协议的具象类型——是协议名后跟 .Protocol。例如类类型 SomeClass 的元类型是 SomeClass.Type,协议 SomeProtocol 的元类型是 SomeProtocol.Protocol。
你可以用后置的 self 表达式把类型当作值来访问。例如 SomeClass.self 返回 SomeClass 本身,而不是 SomeClass 的实例;SomeProtocol.self 返回 SomeProtocol 本身,而不是运行时某个遵循 SomeProtocol 的类型的实例。你可以把类型的实例传给 type(of:) 函数,以值的形式访问该实例的动态运行时类型,如下例所示:
| |
更多内容参见 Swift 标准库中的 type(of:)。
用构造器表达式可以从某个类型的元类型值构造该类型的实例。对于类实例,所调用的构造器必须用 required 关键字标记,或者整个类用 final 关键字标记。
| |
Grammar of a metatype type:
metatype-type → type
.Type| type.Protocol
Any 类型
Any 类型可以包含来自其他所有类型的值。Any 可以用作下列任一类型实例的具象类型:
- 类、结构体或枚举
- 元类型,例如
Int.self - 元素可以是任意类型的元组
- 闭包或函数类型
| |
当把 Any 用作某个实例的具象类型时,你需要先把该实例转换成已知类型,才能访问它的属性或方法。具象类型为 Any 的实例会保留它们原有的动态类型,可以用类型转换运算符之一——as、as? 或 as!——转换成该类型。例如,下面用 as? 有条件地把异构数组中的第一个元素向下转换成 String:
| |
关于类型转换的更多内容,参见类型转换。
AnyObject 协议与 Any 类型类似:所有类都隐式遵循 AnyObject。与由语言定义的 Any 不同,AnyObject 由 Swift 标准库定义。更多内容参见类专用协议和 AnyObject。
Grammar of an Any type:
any-type →
Any
Self 类型
Self 类型并不是某个特定的类型,而是让你方便地引用当前类型,而不必重复或知道该类型的名字。
在协议声明或协议成员声明中,Self 类型指向最终遵循该协议的类型。
在结构体、类或枚举声明中,Self 类型指向该声明引入的类型。在某个类型成员的声明内部,Self 类型指向该类型。在类声明的成员中,Self 只能出现在下列位置:
- 作为方法的返回类型
- 作为只读下标的返回类型
- 作为只读计算属性的类型
- 在方法体内部
例如,下面的代码展示了一个返回类型为 Self 的实例方法 f。
| |
上面例子的最后一部分表明,Self 指向 z 的值的运行时类型 Subclass,而不是变量本身的编译期类型 Superclass。
在嵌套类型声明内部,Self 类型指向最内层类型声明所引入的类型。
Self 类型与 Swift 标准库中 type(of:) 函数指向同一类型。写 Self.someStaticMember 访问当前类型的成员,与写 type(of: self).someStaticMember 等价。
Grammar of a Self type:
self-type →
Self
类型继承子句
类型继承子句用来指定某个具名类型继承哪个类、遵循哪些协议。类型继承子句以冒号(:)开始,后跟类型标识符列表。
类类型可以继承单个父类,并遵循任意多个协议。定义类时,父类的名字必须出现在类型标识符列表的最前面,后面跟着该类必须遵循的任意多个协议;如果该类不继承其他类,列表可以以协议开头。关于类继承的详细讨论和多个示例,参见继承。
其他具名类型只能继承或遵循一组协议。协议类型可以继承任意多个其他协议:当协议类型继承其他协议时,那些协议的要求集合会被汇总在一起,任何继承当前协议的类型都必须遵循所有这些要求。在这个列表中的协议名前写波浪号(~),可以抑制对该协议的隐式遵循,详见对协议的隐式遵循。
枚举定义中的类型继承子句可以是一组协议,或者——对于为成员分配原始值的枚举——是一个指定这些原始值类型的单个具名类型。关于用类型继承子句指定原始值类型的枚举定义示例,参见原始值。
Grammar of a type inheritance clause:
type-inheritance-clause →
:type-inheritance-list
type-inheritance-list → attributes?~? type-identifier | attributes?~? type-identifier,type-inheritance-list
类型推断
Swift 大量使用类型推断,让你可以在代码中省略许多变量和表达式的类型或类型的一部分。例如,不必写 var x: Int = 0,你可以写 var x = 0,完全省略类型——编译器会正确推断出 x 表示的值的类型是 Int。类似地,当完整类型可以从上下文推断出来时,你也可以省略类型的一部分:例如写 let dict: Dictionary = ["A": 1] 时,编译器推断 dict 的类型是 Dictionary<String, Int>。
在上面两个例子里,类型信息都是从表达式树的叶子向上传递到根。也就是说,var x: Int = 0 中 x 的类型是这样推断的:先检查 0 的类型,再把这一类型信息向根(变量 x)传递。
在 Swift 中,类型信息也可以沿相反方向流动——从根向下传到叶子。例如在下面的例子里,常量 eFloat 上的显式类型标注(: Float)使数字字面量 2.71828 被推断为 Float 而不是 Double。
| |
Swift 的类型推断在单个表达式或语句的层面上进行,这意味着,要推断出某个表达式中被省略的类型或类型的一部分,所需的所有信息都必须能通过对该表达式或其某个子表达式做类型检查而获得。