类型安全和类型推断
Swift 是一个类型安全(type safe)的语言,它会在编译你的代码时进⾏类型检查(type checks),并把不匹配的类型标记为错误。
如果你没有显式指定类型,Swift 会使用类型推断(type inference)来选择合适的类型。
当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字⾯量就是会直接出现在你代码
中的值,⽐如: 42 和 3.14159 。)
例如,如果你给一个新常量赋值 42 并且没有标明类型,Swift 可以推断出常量类型是 Int ,因为你给它赋的初始值 看起来像一个
整数
let meaningOfLife = 42 // meaningOfLife 会被推测为 Int 类型
同理,如果你没有给浮点字⾯量标明类型,Swift 会推断你想要的是 Double :
let pi = 3.14159 // pi 会被推测为 Double 类型
当推断浮点数的类型时,Swift 总是会选择 Double ⽽不是 Float 。
如果表达式中同时出现了整数和浮点数,会被推断为 Double 类型:
let anotherPi = 3 + 0.14159 // anotherPi 会被推测为 Double 类型
数值型字⾯面量量
整数字⾯量可以被写作:
一个十进制数, 没有前缀 let decimalInteger = 17
⼀个二进制数, 前缀是 0b let binaryInteger = 0b10001 // ⼆进制的17
一个八进制数, 前缀是 0o let octalInteger = 0o21 // ⼋进制的17
一个十六进制数,前缀是 0x let hexadecimalInteger = 0x11 // ⼗六进制的17
浮点字⾯量可以是十进制(没有前缀)或者是⼗六进制(前缀是 0x )。小数点两边必须有⾄少一个十进制数字(或者是⼗六进制的数
字)。十进制浮点数也可以有一个可选的指数(exponent),通过⼤写或者⼩写的 e 来指定;⼗六进制浮点数必须有一个指数,通过
⼤写或者⼩写的 p 来指定。
如果⼀个⼗进制数的指数为 exp ,那这个数相当于基数和10^exp 的乘积:
1.25e2 表示 1.25 × 10^2,等于 125.0 。
1.25e-2 表示 1.25 × 10^-2,等于 0.0125 。
如果⼀个⼗六进制数的指数为 exp ,那这个数相当于基数和2^exp 的乘积:
0xFp2 表示 15 × 2^2,等于 60.0 。
0xFp-2 表示 15 × 2^-2,等于 3.75 。
下⾯的这些浮点字⾯量都等于十进制的 12.1875 :
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
数值类字⾯量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量:
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
数值型类型转换
通常来讲,即使代码中的整数常量和变量已知非负,也请使用 Int 类型。总是使用默认的整数类型可以保证你的整数常量和变量
可以直接被复⽤并且可以匹配整数类字⾯量的类型推断。
只有在必要的时候才使⽤其他整数类型,⽐如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长
度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。
整数转换
不同整数类型的变量和常量可以存储不同范围的数字。 Int8 类型的常量或者变量可以存储的数字范围是 -128 ~ 127 ,⽽ UInt8
类型的常量或者变量能存储的数字范围是 0 ~ 255 。如果数字超出了了常量或者变量可存储的范围,编译的时候会报错:
let cannotBeNegative: UInt8 = -1 // UInt8 类型不能存储负数,所以会报错
let tooBig: Int8 = Int8.max + 1 // Int8 类型不能存储超过最大值的数,所以会报错
要将一种数字类型转换成另一种,你要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。在下面的
例子中,常量 twoThousand 是 UInt16 类型,然而常量 one 是 UInt8 类型。它们不能直接相加,因为它们类型不同。所以要调用
UInt16(one) 来创建一个新的 UInt16 数字并用 one 的值来初始化,然后使用这个新数字来计算:
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
现在两个数字的类型都是 UInt16 ,可以进行相加。目标常量 twoThousandAndOne 的类型被推断为 UInt16 ,因为它是两个
UInt16 值的和。
SomeType(ofInitialValue) 是调用 Swift 构造器并传⼊一个初始值的默认方法。在语言内部, UInt16 有一个构造器,可以接受
一个 UInt8 类型的值,所以这个构造器可以用现有的 UInt8 来创建一个新的 UInt16 。注意,你并不能传入任意类型的值,只能传
入 UInt16 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型)
整数和浮点数转换
整数和浮点数的转换必须显式指定类型:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159,所以被推测为 Double 类型
注意
结合数字类常量和变量不同于结合数字类字⾯量。字⾯量 3 可以直接和字⾯量 0.14159 相加,因为数字字⾯量本身没有明确的类
型。它们的类型只在编译器需要求值的时候被推测。
类型别名
类型别名(type aliases)就是给现有类型定义另一个名字。你可以使用 typealias 关键字来定义类型别名。 当你想要给现有类型
起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据:
typealias AudioSample = UInt16
定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名:
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 现在是 0
本例中, AudioSample 被定义为 UInt16 的一个别名。因为它是别名, AudioSample.min 实际上是UInt16.min ,所以会给
maxAmplitudeFound 赋一个初值 0 。
布尔值
Swift 有一个基本的布尔(Boolean)类型,叫做 Bool 。布尔值指逻辑上的值,因为它们只能是真或者假。Swift 有两个布尔常
量, true 和 false :
let turnipsAreDelicious = false
当你编写条件语句比如 if 语句的时候,布尔值⾮常有用:
if turnipsAreDelicious {
print("Mmm, tasty turnips!")
} else {
print("Eww, turnips are horrible.")
}
// 输出“Eww, turnips are horrible.”
如果你在需要使用 Bool 类型的地⽅使用了非布尔值,Swift 的类型安全机制会报错。下⾯的例子会报告一个编译时错误:
let i = 1
if i {
// 这个例子不会通过编译,会报错
}
然而,下⾯的例子是合法的:
let i = 1
if i == 1 {
// 这个例子会编译成功
}
i == 1 的⽐较结果是 Bool 类型,所以第二个例子可以通过类型检查。
和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的。
元组
元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
下⾯这个例子中, (404, "Not Found") 是一个描述 HTTP 状态码(HTTP status code)的元组。HTTP 状态码是当你请求⽹页的时
候 web服务器返回的一个特殊值。如果你请求的⽹页不存在就会返回一个 404 Not Found 状态码。
let http404Error = (404, "Not Found")
// http404Error 的类型是 (Int, String),值是 (404, "Not Found")
(404, "Not Found") 元组把一个 Int 值和一个 String 值组合起来表示 HTTP 状态码的两个部分:一个数字和 一个⼈类可读的描述。
这个元组可以被描述为“一个类型为 (Int, String) 的元组”。你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类
型。只要你想,你可以创建一个类型为 (Int, Int, Int) 或者 (String, Bool) 或者其他任何你想要的组合的元组。
你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了:
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 输出“The status code is 404”
print("The status message is \(statusMessage)")
// 输出“The status message is Not Found”
如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线( _ )标记:
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// 输出“The status code is 404”
此外,你还可以通过下标来访问元组中的单个元素,下标从零开始:
print("The status code is \(http404Error.0)")
// 输出“The status code is 404”
print("The status message is \(http404Error.1)")
// 输出“The status message is Not Found”
作为函数返回值时,元组非常有⽤。一个⽤来获取网页的函数可能会返回一个 (Int, String) 元组来描述是否获取成功。和只能返
回⼀个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。
注意
当遇到一些相关值的简单分组时,元组是很有用的。元组不适合用来创建复杂的数据结构。如果你的数据结构比较复杂,不要使
用元组,用类或结构体去建模。