Swift语法3.03(类型Types)
类型
在Swift中,有两种类型:命名型类型和复合型类型。命名型类型是在定义时可以给定的特定名字的类型。命名型类型包括类,结构体,枚举和协议。例如,自定义的类MyClass
的实例拥有类型MyClass
。除了自定义类型之外,Swift标准库定义了许多常用的命名型类型,包括那些表示数组、字典和可选值的类型。
那些通常被其它语言认为是基本或初级的数据型类型-例如表示数字、字符和字符串的类型-实际上就是命名型类型,在Swift标准库中用结构体来定义和实现。因为它们是命名型类型,你可以通过使用扩展增加它们的行为来符合你程序的需求,按 Extensions 和 Extensin Declaration 里讨论的那样。
复合型类型是没有名字的类型,定义在Swift语言本身中。有两种复合型类型:函数型类型和元组型类型。一个复合型类型可以包含命名型类型和其他复合型类型。例如,元组型类型 (Int,(Int,Int))
包含两个元素:第一个是命名型类型Int
,第二个是另一个复合型类型 (Int,Int)
。
本节讨论Swift语言本身定义的类型并描述Swift中的类型推断行为。
GRAMMAR OF A TYPE
type → array-type | dictionary-type | function-type | type-identifier | tuple-type | optional-type | implicitly-unwrapped-optional-type | protocol-composition-type | metatype-type
类型注释
类型注释显示的指定一个变量或表达式的类型。类型注释以冒号(:
)开始以类型结束。
let someTuple: (Double, Double) = (3.14159, 2.71828) func someFunction(a: Int){ /* ... */ }
在第一个例子中,表达式someTuple
+的类型被指定为元组类型(Double, Double)
。在第二个例子中,函数someFunction
的参数a的类型被显示指定为类型Int
。
类型注释可以包含一个可选的类型特性的列表在类型之前。
GRAMMAR OF A TYPE ANNOTATION
type-annotation → : attributes opt inout opt type
类型标识符
类型标识符指的是命名型类型或者是命名型或复合型类型的别名。
大多数情况下,类型标识符指的是与类型标识符同名的命名型类型。例如Int类型标识符指的是命名型类型Int
,类型标识符Dictionary<String, Int>
指的是命名型类型Dictionary<String, Int>
。
有两种情况类型标识符不是指的是和类型标识符同名的类型。情况一,类型标识指的是某个命名型或复合型类型的类型别名。例如,在下面的例子中,类型注释中Point
的用法指的是元组类型(Int, Int)
:
typealias Point = (Int, Int) let origin: Point = (0, 0)
情况二,类型标识符使用dot( . )语法来表示声明在其它模块中或嵌套在其它类型中的命名型类型。例如,下面代码中的类型标识符指的是在ExampleModule
模块中声明的命名型类型MyType
:
var someValue: ExampleModule.MyType
GRAMMAR OF A TYPE IDENTIFIER
type-identifier → type-namegeneric-argument-clause opt | type-namegeneric-argument-clause opt .type-identifier
type-name → identifier
元组类型
元组类型是是使用逗号分隔的0个或多个类型的列表,用括号括起来。你可以使用元组类型作为函数的返回值使函数返回一个包含多个值的元组。你也能命名元组的元素并且使用那些名字来表示每个元素的值。一个元素的名字由一个标识符与紧随其后的冒号(:)组成。例如在函数和多返回值中所展现的这些特点。参阅 Functions with Multiple Return Values.
Void
是空元组类型()
的别名。如果括号中只有一个元素,类型就是那个元素的类型。例如,类型(Int)
就是Int
,不是(Int)
。因此,你可以命名一个元组中的元素当元组有2个或多个元组时。
GRAMMAR OF A TUPLE TYPE
tuple-type → (tuple-type-body opt)
tuple-type-body → tuple-type-element-list... opt
tuple-type-element-list → tuple-type-element | tuple-type-element,
tuple-type-element-list tuple-type-element → attributes opt inout opt type | element-nametype-annotation
element-name → identifier
函数类型
函数类型表示一个函数、方法或闭包的类型,由参数类型和返回值类型组成,中间用箭头(->
)隔开:
(parameter type) -> (return type)
因为参数类型和返回值类型可以是元组类型,函数类型支持多参数与多返回值的函数与方法。
函数类型的参数() - > T
(其中T
是任何类型)可以应用autoclosure
属性明确创建闭包在其调用点。这提供了语法上的方便的方式来推迟表达式的赋值,而不需要在调用函数写一个明确的闭包。例如自动闭包函数类型参数,参见Autoclosures。
函数类型可以有可变参数在它的参数类型中。在语法上,一个可变参数由一个紧跟三个点(...)的基本类型名组成,如Int... .
一个可变参数被视为包含基本类型元素的数组。例如,一个可变参数Int...
被视为[Int]
。使用可变参数的示例,请参阅可变参数。
为了指定输入输出参数,把inout
关键字缀于在参数类型之前。你不能用inout
关键字标记可变参数或返回值类型。输入输出参数请参阅 In-Out Parameters。
如果函数类型包括不止一个箭头(->
),函数类型从右到左进行分组。例如,函数类型(Int) -> (Int) -> (Int)
被理解为(Int) -> ((Int) -> (Int))
——也就是说,一个函数的参数为Int 类型,其返回类型是一个参数类型为Int返回类型为Int 的函数类型
函数类型如果能抛出错误必须使用throws
关键字来标记,而且函数类型如果能重抛错误则必须使用rethrows
关键字来标记。throws
关键字是函数类型的一部分,不抛出函数是抛出函数的子类型。所以,你可以使用不抛出函数或抛出函数在同样的位置。抛出和重抛函数的参考 抛出函数与方法和重抛函数与方法。抛出和冲抛出函数在 Throwing Functions and Methods和Rethrowing Functions and Methods中有描述。
GRAMMAR OF A FUNCTION TYPE
function-type → (type) ** throws** opt -> type
function-type → (type) rethrows->type
数组类型
Swift语言为Swift标准库中Array<Element>
类型提供下列语法糖:
[ type ]
换句话说,下面两个声明是等价的:
let someArray: [String] = ["Alex", "Brian", "Dave"] let someArray: Array<String> = ["Alex", "Brian", "Dave"]
上面两种情况中,常量someArray
被声明为字符串数组。数组的元素可以被访问通过下标在方括号中指定一个有效的索引值: someArray[0] 是指第0个元素"Alex"
。
你可以创建多维数组通过嵌套多对方括号,元素的基本类型名包含在最里面的方括号。例如,下面例子中用三对方括号创建三维整数数组。
var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
访问一个多维数组的元素时,最左边的下标索引值指的是最外层数组的相应位置元素。接下来的下标索引值指的是第一层嵌套的
的相应位置元素。依次类推。这意味着在上面的例子中,array3D[0]
指的是[[1, 2], [3, 4]]
,array3D[0][1]
是指[3, 4]
,array3D[0][1][1]
则是指值4
。
关于Swift标准库中Array
类型的详细讨论,参阅Arrays。
GRAMMAR OF AN ARRAY TYPE
array-type → [type]
字典类型
Swift语言为Swift标准库中的Dictionary<Key,Value>
类型提供下列语法糖:
[ key type: value type ]
换句话说,下面两个声明是等价的:
let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39]
let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]
上面两种情况中,常量someDictionary
被声明为键为String类型,值为Int类型的字典
字典中的值可以通过下标指定方括号中的相应键来访问:someDictionary["Alex"]
指的是和键Alex
关联的值。下标返回一个字典中键的类型的可选。如果被指定的键在字典中不存在的话,下标返回nil
。
字典中键的类型必须遵守Swift标准库中的Hashable
协议。
关于Swift标准库中Dictionary
类型的详细讨论可,参阅Dictionaries。
GRAMMAR OF A DICTIONARY TYPE
dictionary-type → [type:type]
可选类型
Swift为命名类型Optional<Wrapped>
定义后缀?
作为语法糖 ,其定义在Swift标准库中。换句话说,下列两种声明是等价的:
var optionalInteger: Int? var optionalInteger: Optional<Int>
在上述两种情况下,变量optionalInteger
都被声明为可选整型类型。注意在类型和?
之间没有空格。
类型Optional<Wrapped>
是有两种情况的枚举,none
和Some(Wrapped)
,其代表可能没有值或可能有值。任意类型都可以被显式的声明(或隐式的转换)为可选类型。如果你在声明可选的变量或属性时没有提供初始值,它的值则会自动赋值nil 。
如果一个可选类型的实例包含一个值,那么你就可以使用后缀操作符!
来获取该值,如下描述:
optionalInteger = 42 optionalInteger! // 42
使用!
操作符获解析一个值为nil的可选会导致运行时错误。
你也可以使用可选链和可选绑定来有条件的的执行对可选表达式的操作。如果值为nil
,不会执行任何操作并且因此没有产生运行时错误。
关于更多的信息以及查看如何使用可选类型的栗子,参阅Optionals。
GRAMMAR OF AN OPTIONAL TYPE
optional-type → type?
隐式解析可选类型
Swift为命名类型Optional<Wrapped>
定义后缀!
作为语法糖 ,其定义在Swift标准库中,以及当它被访问时它自动解析的附加行为。如果你试图使用一个值为nil
的隐式解析,你会得到一个运行时错误。除了隐式解析的行为之外,下面两个声明是等价的:
var implicitlyUnwrappedString: String! var explicitlyUnwrappedString: Optional<String>
注意类型与!
之间没有空格。
因为隐式解析可选改变了包含该类型的声明的含义,嵌套在元组类型或泛型类型中的可选类型-如字典或数组中元素的类型-不能被标记为隐式解析。例如:
let tupleOfImplicitlyUnwrappedElements: (Int!, Int!) // Error let implicitlyUnwrappedTuple: (Int, Int)! // OK
let arrayOfImplicitlyUnwrappedElements: [Int!] // Error let implicitlyUnwrappedArray: [Int]! // OK
因为隐式解析可选有相同的Optional<Wrapped>
类型作为可选值,你可以使用隐式解析可选在代码中所有你使用可选的相同地方。例如,你可以把隐式解析可选赋值给变量,常量,以及可选属性,反之亦然。
有了可选,在声明隐式解析可选的变量或属性时你不用提供初始值,它的值会自动赋值为nil
。
使用可选链有条件的对隐式解析可选的表达式进行操作。如果值为nil
,就不执行任何操作,因此也不会产生运行错误。
关于隐式解析可选的更多信息,参阅Implicitly Unwrapped Optionals。
GRAMMAR OF AN IMPLICITLY UNWRAPPED OPTIONAL TYPE
implicitly-unwrapped-optional-type → type!
协议组合类型
协议组合类型表述的是一个符合指定的协议列表中的每个协议的类型。协议组合类型可能被用在类型注解与泛型参数中。
协议组合类型有下列形式:
protocol<Protocol 1, Procotol 2>
协议组合类型允许你指定一个值,该值的类型遵循多个协议的条件而不必显示的定义一个新的命名型的继承自每个你想要该类型遵循的协议的协议。比如,指定一个协议组成类型protocol<Protocol A, Protocol B, Protocol C>
实际上是和定义一个新的继承自Protocol A , Protocol B , Protocol C
的协议组合类型Protocol D
,但不需要引入不需引入一个新名字。
协议合成列表中的每项必须是协议名或协议合成类型的类型别名。如果列表为空,它就会指定一个空协议合成列
表,这样每个类型都能遵循。
协议合成列表中的每一项必须是协议名或协议组合类型的类型别名。如果列表为空,它指定一个每个类型都能遵循的空协议组合列表。
GRAMMAR OF A PROTOCOL COMPOSITION TYPE
protocol-composition-type → protocol<protocol-identifier-list opt>
protocol-identifier-list → protocol-identifier | protocol-identifier , protocol-identifier-list
protocol-identifier → type-identifier
元类型类型
元类型类型指的是所有类型的类型,包括类类型、结构体类型、枚举类型和协议类型。类、结构体或枚举类型的元类型是紧跟.Type
的类型的名字 。协议类型的元类型—并不是运行时遵循该协议的具体类型—是紧跟.Protocol
的该协议名字。例如,类类型SomeClass
的元类型就是SomeClass.Type
,协议类型SomeProtocol
的元类型就是SomeProtocal.Protocol
。
你可以使用后缀self
表达式来获取类型作为一个值。比如,SomeClass.self
返回SomeClass
本身,而不是SomeClas 的一个实例。并且SomeProtocol.self
返回SomeProtocol
本身,而不是运行时遵循SomeProtocol
的某个类型的实例。你可以对类型的实例使用dynamicType
表达式来获取该实例的动态运行时的类型,如下例所示:
class SomeBaseClass { class func printClassName() { print("SomeBaseClass") } } class SomeSubClass: SomeBaseClass { override class func printClassName() { print("SomeSubClass") } } let someInstance: SomeBaseClass = SomeSubClass() // The compile-time type of someInstance is SomeBaseClass, // and the runtime type of someInstance is SomeSubClass someInstance.dynamicType.printClassName() // Prints "SomeSubClass"
可以使用恒等运算符(===
和!==
)来测试一个实例的运行时类型和它的编译时类型是否一致。
if someInstance.dynamicType === someInstance.self { print("The dynamic type of someInstance is SomeBaseCass") } else { print("The dynamic type of someInstance isn't SomeBaseClass") } // prints "The dynamic type of someInstance isn't SomeBaseClass"
使用初始化表达式从类型的元类型的值构造出类型的实例。对于类实例,必须用required
关键字标记被调用的构造器或者使用final
关键字标记整个类。
class AnotherSubClass: SomeBaseClass { let string: String required init(string: String) { self.string = string } override class func printClassName() { print("AnotherSubClass") } } let metatype: AnotherSubClass.Type = AnotherSubClass.self let anotherInstance = metatype.init(string: "some string")
GRAMMAR OF A METATYPE TYPE
metatype-type → type.Type | type.Protocol
类型继承语句
类型继承语句被用来指定一个命名型类型继承哪个类和遵循的哪些协议。类型继承语句也用来指定协议的class
条件。类型继承语句开始于冒号(:
),其后是class
条件或类型标识符列表或者两者均有。
类类型可以继承单个超类,遵循任意个协议。当定义一个类时,超类的名字必须出现在类型标识符列表第一位,其后跟随该类必须遵循的任意个协议。如果一个类不继承自其他类,列表能够以协议开头。关于类继承更多的讨论和示例,参阅Inheritance。
其它命名型类型可能只继承或遵循一个协议列表。协议类型可能继承于任意个其它协议。当一个协议类型继承于其它协议时,那些其它协议的条件集合会被整合在一起,任何继承自当前协议的类型必须遵循所有这些条件。正如协议声明中讨论的那样,可以将class
关键字作为第一项包含在类型继承语句中来标记一个附有class
条件的协议声明。
类型继承语句在枚举定义中可以是协议列表或者是当枚举赋值原始值给它的情况时的一个指定那些原始值的类型的一个单个的命名型类型。使用类型继承语句来指定其原始值类型的枚举定义的例子,参阅Raw Values。
GRAMMAR OF A TYPE INHERITANCE CLAUSE
type-inheritance-clause → :class-requirement,type-inheritance-list
type-inheritance-clause → :class-requirement
type-inheritance-clause → :type-inheritance-list
type-inheritance-list → type-identifier | type-identifier,type-inheritance-list
class-requirement → class
类型推断
Swift广泛的使用类型推断,允许你在你的代码中忽略很多变量和表达式的类型或部分类型。比如,你可以写成var x = 0
,完全忽略类型,而不是写var x: Int = 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
。
let e = 2.71828 // The type of e is inferred to be Double. let eFloat: Float = 2.71828 // The type of eFloat is Float.
Swift中的类型推断操作在单独的表达式或语句的级别。这意味所有用于推断省略的类型或表达式中的类型所必需的信息必须可以从表达式或其子表达式中的某个表达式的类型检查中获取。