类型
在Swift中,有两种类型:命名类型和复合类型。命名类型是一种可以在定义时给定特定名称的类型。命名类型包括类,结构,枚举和协议。例如,名为MyClass的用户定义类的实例具有MyClass类型。除了用户定义的命名类型外,Swift标准库还定义了许多常用的命名类型,包括代表数组,字典和可选值的类型。
在其他语言中通常被认为是基本或基本的数据类型(例如表示数字,字符和字符串的类型)实际上是命名类型,在Swift标准库中使用结构定义和实现。因为它们是被命名的类型,所以可以使用扩展声明来扩展它们的行为以适应程序的需求,这在扩展和扩展声明中进行了讨论。
复合类型是一种没有名称的类型,在Swift语言中定义。有两种复合类型:函数类型和元组类型。化合物类型可能包含命名类型和其他化合物类型。例如,元组类型(Int,(Int,Int))包含两个元素:第一个是命名类型Int,第二个是另一个复合类型(Int,Int)。
您可以将圆括号放在指定类型或复合类型的周围。但是,在类型周围添加括号不会产生任何影响。例如,(Int)等同于Int。
本章讨论在Swift语言中定义的类型,并描述Swift的类型推断行为
type → array-type-
type → dictionary-type-
type → function-type-
type → type-identifier-
type → tuple-type-
type → optional-type-
type → implicitly-unwrapped-optional-type-
type → protocol-composition-type-
type → metatype-type-
type → Any-
type → Self-
type → (-type-)-
类型注释
类型注释显式指定变量或表达式的类型。 类型注释以冒号(:)开头并以类型结尾,如以下示例所示:
let someTuple: (Double, Double) = (3.14159, 2.71828)
func someFunction(a: Int) { /* ... */ }
在第一个示例中,someTuple表达式指定具有元组类型(Double,Double)。 在第二个示例中,函数someFunction的参数a被指定为Int类型。
类型注释可以在类型之前包含可选的类型属性列表。
type-annotation → :-attributes-opt-inout-opt-type-
类型标识符
类型标识符引用命名类型或命名或复合类型的类型别名。
大多数情况下,类型标识符直接引用与标识符同名的命名类型。 例如,Int是直接引用指定类型Int的类型标识符,类型标识符Dictionary <String,Int>直接引用指定类型Dictionary <String,Int>。
有两种情况,其中一个类型标识符不引用具有相同名称的类型。 在第一种情况下,类型标识符是指名称或复合类型的类型别名。 例如,在下面的例子中,在类型注释中使用Point是指元组类型(Int,Int)。
typealiasPoint = (Int, Int)
let origin: Point = (0, 0)
在第二种情况下,类型标识符使用点(。)语法来引用在其他模块中声明的或在其他类型中嵌套的命名类型。 例如,以下代码中的类型标识符会引用在ExampleModule模块中声明的已命名类型MyType。
var someValue:ExampleModule.MyType
type-identifier → type-name-generic-argument-clause-opt-type-name-generic-argument-clause-opt-.-type-identifier-
type-name → identifier-
元组类型
元组类型是逗号分隔的类型列表,用括号括起来。
您可以使用元组类型作为函数的返回类型,以使函数能够返回包含多个值的单个元组。 您也可以命名元组类型的元素并使用这些名称来引用各个元素的值。 元素名称由紧随冒号(:)的标识符组成。 有关演示这两个功能的示例,请参阅具有多个返回值的函数。
当元组类型的元素具有名称时,该名称是该类型的一部分。
var someTuple = (top: 10, bottom: 12) // someTuple is of type (top: Int, bottom: Int)
someTuple = (top: 4, bottom: 42) // OK: names match
someTuple = (9, 99) // OK: names are inferred
someTuple = (left: 5, right: 5) // Error: names don't match
所有的元组类型都包含两个或更多的类型,除了Void,它是空元组类型的类型别名()。
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-
功能类型
函数类型表示函数,方法或闭包的类型,并由用箭头( - >)分隔的参数和返回类型组成:
(parameter type) -> return type
参数类型是用逗号分隔的类型列表。因为返回类型可以是元组类型,所以函数类型支持返回多个值的函数和方法。
函数type() - > T(其中T是任何类型)的参数可以应用autoclosure属性,以隐式地在其呼叫站点创建闭包。这提供了一种语法上方便的方式来推迟表达式的评估,而无需在调用函数时编写明确的闭包。有关自动闭合功能类型参数的示例,请参阅自动闭合。
函数类型可以在其参数类型中具有可变参数。在语法上,可变参数包含一个基本类型名称,后跟三个点(...),如Int ....可变参数被视为包含基本类型名称的元素的数组。例如,可变参数Int ...被视为[Int]。有关使用可变参数的示例,请参阅可变参数。
要指定输入输出参数,请使用inout关键字为参数类型添加前缀。您不能使用inout关键字标记可变参数或返回类型。输入输出参数中讨论输入输出参数。
如果一个函数类型只有一个参数,并且该参数的类型是一个元组类型,那么在写入该函数的类型时,元组类型必须加上括号。例如,((Int,Int)) - > Void是接受元组类型(Int,Int)的单个参数且不返回任何值的函数的类型。相反,没有括号,(Int,Int) - > Void是一个函数的类型,它接受两个Int参数并且不返回任何值。同样,由于Void是()的类型别名,因此函数类型(Void) - > Void与(()) - >()是相同的函数,该函数接受一个空元组的单个参数。这些类型不同于() - >() - 一个不带参数的函数。
函数和方法中的参数名称不是相应函数类型的一部分。例如:
func someFunction(left: Int, right: Int) {}
func anotherFunction(left: Int, right: Int) {}
func functionWithDifferentLabels(top: Int, bottom: Int) {}
var f = someFunction// The type of f is (Int, Int) -> Void, not (left: Int, right: Int) -> Void.
f = anotherFunction// OK
f = functionWithDifferentLabels// OK
func functionWithDifferentArgumentTypes(left: Int, right: String) {}
f = functionWithDifferentArgumentTypes// Error
func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}
f = functionWithDifferentNumberOfArguments// Error
由于参数标签不是函数类型的一部分,因此在编写函数类型时省略它们。
var operation: (lhs: Int, rhs: Int) -> Int// Error
var operation: (_lhs: Int, _rhs: Int) -> Int// OK
var operation: (Int, Int) -> Int// OK
如果函数类型包含多个单箭头( - >),则函数类型将从右向左分组。例如,函数类型(Int) - >(Int) - > Int被理解为(Int) - >((Int) - > Int) - 也就是说,一个函数接受一个Int并返回另一个函数,返回一个Int。
可以抛出错误的函数类型必须使用throws关键字标记,并且可以重新抛出错误的函数类型必须使用rethrows关键字标记。 throws关键字是函数类型的一部分,nonthrowing函数是抛出函数的子类型。因此,你可以在同一个地方使用非抛出函数作为抛出函数。投掷函数和方法以及重新函数和方法中描述了投掷和重投掷函数。
消除封锁的限制
一个参数是一个非观察函数,不能作为参数传递给另一个非观察函数参数。这个限制有助于Swift在编译时而不是在运行时执行更多的检查冲突访问内存。例如:
let external: (Any) -> Void = { _in () }
func takesTwoFunctions(first: (Any) -> Void, second: (Any) -> Void) {
first(first) // Error
second(second) // Error
first(second) // Error
second(first) // Error
first(external) // OK
external(first) // OK
}
在上面的代码中,takeTwoFunctions(first:second :)的两个参数都是函数。 这两个参数都没有被标记为@escaping,所以它们都作为结果不转义。
上述示例中标记为“错误”的四个函数调用会导致编译器错误。 因为第一个和第二个参数是非转义函数,所以它们不能作为参数传递给另一个非转义函数参数。 相反,标记为“OK”的两个函数调用不会导致编译器错误。 这些函数调用不会违反限制,因为外部函数不是takeTwoFunctions(first:second :)的参数之一。
如果您需要避免此限制,请将其中一个参数标记为转义,或者使用withoutActuallyEscaping(_:do :)函数临时将其中一个非转义函数参数转换为转义函数。 有关避免冲突访问内存的信息,请参阅内存安全。
函数类型的语法
function-type → attributes-opt-function-type-argument-clause-throws-opt-->-type-
function-type → attributes-opt-function-type-argument-clause-rethrows-->-type-
function-type-argument-clause → (-)-
function-type-argument-clause → (-function-type-argument-list-...-opt-)-
function-type-argument-list → function-type-argument-function-type-argument-,-function-type-argument-list-
function-type-argument → attributes-opt-inout-opt-type-argument-label-type-annotation-
argument-label → identifier-
数组类型
Swift语言为Swift标准库Array <Element>类型提供了以下语法糖:
[type]
换句话说,以下两个声明是等价的:
let someArray: Array<String> = ["Alex", "Brian", "Dave"]
let someArray: [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-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被声明为一个字典,其中字符串作为键和整数作为值。
通过在方括号中指定相应的键,可以通过下标来访问字典的值:someDictionary [“Alex”]指与键“Alex”相关联的值。 下标返回字典值类型的可选值。 如果指定的键不包含在字典中,则下标返回nil。
字典的键类型必须符合Swift标准库Hashable协议。
有关Swift标准库字典类型的详细讨论,请参阅字典。
dictionary-type → [-type-:-type-]-
可选类型
Swift语言定义了后缀? 作为命名类型可选的<Wrapped>的语法糖,它是在Swift标准库中定义的。 换句话说,以下两个声明是等价的:
var optionalInteger: Int?
var optionalInteger: Optional<Int>
在这两种情况下,变量optionalInteger被声明为具有可选整数的类型。 请注意,类型和?之间不会出现空格。
类型可选的<Wrapped>是一个枚举,有两种情况,无和一些(Wrapped),它们用于表示可能存在或可能不存在的值。 任何类型都可以显式声明为(或隐式转换为)可选类型。 如果您在声明可选变量或属性时未提供初始值,则其值自动默认为零。
如果可选类型的实例包含值,则可以使用postfix运算符!访问该值,如下所示:
optionalInteger = 42
optionalInteger! // 42
使用 ! 运算符打开一个值为零的可选结果导致运行时错误。
您还可以使用可选链和可选绑定来有条件地对可选表达式执行操作。 如果值为零,则不执行任何操作,因此不会产生运行时错误。
有关更多信息并查看显示如何使用可选类型的示例,请参阅可选项。
optional-type → type-?-
隐式解包可选类型
Swift语言定义了后缀! 作为在Swift标准库中定义的命名类型可选的<Wrapped>的语法糖,以及在访问它时自动解包的附加行为。 如果您尝试使用值为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
因为隐式解包选项与可选值具有相同的可选<Wrapped>类型,所以可以在代码中的所有相同位置使用隐式解包选项,以便使用可选项。 例如,您可以将隐式解包选项的值分配给变量,常量和可选属性,反之亦然。
与可选项一样,如果您在声明隐式解包的可选变量或属性时未提供初始值,则其值将自动默认为零。
使用可选链接有条件地对隐式解包的可选表达式执行操作。 如果值为零,则不执行任何操作,因此不会产生运行时错误。
有关隐式展开的可选类型的更多信息,请参见隐式展开的可选项
implicitly-unwrapped-optional-type → type-!-
协议组合类型
协议组合类型定义了一种符合指定协议列表中的每个协议的类型,或者是一个给定类的子类的类型,并且符合指定协议列表中的每个协议。 只有在类型注释中指定类型,泛型参数子句和泛型where子句时,才可以使用协议组合类型。
协议组合类型具有以下形式:
Protocol 1 & Protocol 2
协议组合类型允许您指定一个类型符合多个协议要求的值,而不明确定义一个新的,命名的协议,该协议从您希望类型符合的每个协议继承。例如,您可以使用协议组合类型ProtocolA&ProtocolB&ProtocolC而不是声明从ProtocolA,ProtocolB和ProtocolC继承的新协议。同样,您可以使用SuperClass&ProtocolA而不是声明一个新的协议,该协议是SuperClass的一个子类,并且符合ProtocolA。
协议组成列表中的每个项目都是以下之一;该列表最多可以包含一个类:
班级的名称
协议的名称
基础类型是协议组合类型,协议或类的类型别名。
当协议组合类型包含类型别名时,同一协议可能在定义中出现多次 - 重复项将被忽略。例如,下面代码中PQR的定义等同于P&Q&R。
typealias PQ = P&Q
typealias PQR = PQ&Q&R
协议组成类型的语法
protocol-composition-type → type-identifier-&-protocol-composition-continuation-
protocol-composition-continuation → type-identifier-protocol-composition-type-
元类型
元类型是指任何类型的类型,包括类类型,结构类型,枚举类型和协议类型。
类,结构或枚举类型的元类型是该类型的名称,后跟.Type。 协议类型的元类型(不是符合运行时协议的具体类型)是该协议的名称,后跟.Protocol。 例如,类类型SomeClass的元类型是SomeClass.Type,协议SomeProtocol的元类型是SomeProtocol.Protocol。
您可以使用postfix自我表达式来访问作为值的类型。 例如,SomeClass.self本身返回SomeClass,而不是SomeClass的实例。 SomeProtocol.self自身返回SomeProtocol,而不是在运行时符合SomeProtocol类型的实例。 您可以使用某个类型的实例调用类型(of :)函数,以便将该实例的动态运行时类型作为值进行访问,如以下示例所示:
class SomeBaseClass {
class funcprintClassName() {
print("SomeBaseClass")
}
}
class SomeSubClass: SomeBaseClass {
override class funcprintClassName() {
print("SomeSubClass")
}
}
let someInstance: SomeBaseClass = SomeSubClass()
// The compile-time type of someInstance is SomeBaseClass,
// and the runtime type of someInstance is SomeSubClass
type(of: someInstance).printClassName()
// Prints "SomeSubClass"
有关更多信息,请参阅Swift标准库中的(of :)类型。
使用初始化表达式从该类型的元类型值构造一个类型的实例。 对于类实例,所调用的初始化程序必须标记为必需的关键字或标有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”)
metatype-type → type-.-Type-type-.-Protocol-
类型继承子句
类型继承子句用于指定指定类型继承的类以及指定类型符合的协议。类型继承子句以冒号(:)开头,后跟类型标识符列表。
类类型可以从一个超类继承,并符合任何数量的协议。定义一个类时,超类的名称必须首先出现在类型标识符列表中,然后是类必须符合的任意数量的协议。如果该类不从另一个类继承,则该列表可以以协议开始。有关扩展的讨论和类继承的几个示例,请参阅继承。
其他命名类型只能继承或符合协议列表。协议类型可以从任何其他协议继承。当协议类型从其他协议继承时,来自这些其他协议的一组要求聚合在一起,并且从当前协议继承的任何类型都必须符合所有这些要求。
枚举定义中的类型继承子句可以是协议列表,也可以是将枚举原始值分配给其情况的枚举,也可以是指定这些原始值类型的单个命名类型。有关使用类型继承子句指定其原始值类型的枚举定义的示例,请参阅原始值。
一种遗传条款的语法
type-inheritance-clause → :-type-inheritance-list-
type-inheritance-list → type-identifier-type-identifier-,-type-inheritance-list-
类型推断
Swift广泛使用类型推断,允许您在代码中省略许多变量和表达式的类型或部分类型。例如,可以不写var x:Int = 0,而是写var x = 0,完全省略类型 - 编译器正确地推断x命名了Int类型的值。类似地,当从上下文中可以推断完整类型时,可以省略一部分类型。例如,如果您编写let dict:Dictionary = [“A”:1],则编译器会推断该dict的类型为Dictionary <String,Int>。
在上面的两个例子中,类型信息都从表达式树的叶子传递到它的根。也就是说,通过首先检查0的类型,然后将该类型信息传递给根(变量x),推断var x:Int = 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中的类型推断在单个表达式或语句的级别上运行。 这意味着推断表达式中的省略类型或部分类型所需的所有信息必须可以通过类型检查表达式或其子表达式之一来访问。