基本数据类型
- 整数
整数就是没有小数部分的数字,如 42 和 -23 。整数是有符号的(正、负、零)或无符号的(正、零)。
Swift 提供了8,16,32和64位的有符号和无符号整数类型。这些整数命名约定类似于C语言,如8位无符号整数类型是 UInt8,32位有符号整数类型是 Int32。像所有Swift的其他类型一样,整数类型采用大写命名法。
整数范围
您可以访问每个整数类型的 min 和 max 属性获取其最小值和最大值:
let minValue = UInt8.min // minValue is equal to 0, and is of type UInt8
let maxValue = UInt8.max // maxValue is equal to 255, and is of type UInt8
- Int类型
在大多数情况下,你不需要指定整数的长度。Swift 提供了一个特殊的整数类型:Int,长度与当前平台的字长相同。
在32位平台上,Int和Int32长度相同。
在64位平台上,Int和Int64长度相同。
除非你需要使用特定大小的整数,平时在代码中使用 Int 整数就够了。这可以提高代码的一致性和复用性。在32位平台上,Int可以存储-2147483648至2147483647之间的值,这个范围已经足够大了。 - UInt类型
Swift 还提供了一个特殊的无符号类型UInt,长度与当前平台的字长相同:
在32位平台上,UInt和UInt32长度相同。
在64位平台上,UInt和UInt64长度相同。
注意:尽量不要使用UInt,除非你真的需要存储一个和当前平台字长相同的无符号整数。
最好使用Int,即使你要存储的值是非负的。统一使用Int可以提高代码的复用性,避免不同类型数字之间的转换。 - 浮点数
浮点数是由整数和小数部分组成,如 3.14159,0.1,-273.15。
浮点数类型可以表示比整数类型更大范围的值,可以存储比 Int 类型更大或者更小的数字。Swift 提供了两种浮点数类型:
Double:64位浮点数。当你需要存储很大或者很高精度的浮点数时使用此类型。
Float:32位浮点数。精度要求不高时使用此类型。
注意:Double 精确度很高,至少15位数字,而Float最少只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围。
类型安全和类型引用
Swift 是一种类型安全(type safe )的语言。类型安全的语言会让你更清楚代码中可以使用的值的类型。如果你的代码预期一个String,你绝不能误入一个Int。
因为 Swift 是类型安全的,编译器编译你的代码时会执行类型检查(type checks),并标记出任何不匹配的类型错误。这使你在开发过程中能够尽早捕获并修复错误。
当你使用不同类型的值时,类型安全检查可以帮助你避免错误。当然,这并不意味着你必须为每一个常量和变量显示地指定类型。如果你没有显示地指定类型,Swift会进行类型推测(type inference),选择出适当的类型。类型推测就是编译器在编译代码时能够推断出特定的表达式的类型,主要是根据所提供的值进行推测。
因为类型推测,所以 Swift 比 C 或 Objective - C 需要更少的类型声明。虽然常量和变量需要明确类型,但大部分的工作不需要你来做。
类型推测是特别有用的,当你声明一个常量或变量并初始值的时候,即触发类型推测。
例如,你给一个常量赋值42,但没有标明类型,Swift 可以推测出常量类型是Int,因为你给它赋的值看起来像一个整数:
let meaningOfLife = 42
// meaningOfLife is inferred to be of type Int
同样的,如果你不指定一个常量为浮点类型,Swift 推断出你想要的是Double类型
let pi = 3.14159
// pi is inferred to be of type Double
当推测浮点数的类型时,Swift 总是会选择 Double 而不是 Float。
如果你把整数和浮点数同时用在表达式中,Swift 会推测为Double 类型:
let anotherPi = 3 + 0.14159
// anotherPi is also inferred to be of type Double
数字 3 没有显式的类型,而表达式中出现了一个浮点数,因此,表达式被推测为 Double 类型。
数字类型字面值
整数字面值可以写为:
一个十进制数,没有前缀
一个二进制数,前缀是 0b
一个八进制数,前缀是 0o
一个十六进制数,前缀是 0x
下面的所有整数面值的十进制值都是17:
let decimalInteger = 17
let binaryInteger = 0b10001 // 17 in binary notation
let octalInteger = 0o21 // 17 in octal notation
let hexadecimalInteger = 0x11 // 17 in hexadecimal notation
浮点数面值可以是十进制(没有前缀),或者是十六进制(0x前缀)。必须有至少一个十进制数字(或十六进制数)在小数点两侧。
浮点数面值还有一个可选的指数(exponent),在十进制浮点数中通过大写或者小写的e来表示,在十六进制浮点数中通过大写或者小写的p来表示。
如果一个十进制数的指数为exp,那这个数相当于基数和10^exp(10的exp次方)的乘积:
1.25e2 表示 1.25×10^2,等于 125.0
1.25e-2 表示 1.25×10^-2,等于 0.0125。
如果一个十六进制数的指数为exp,那这个数相当于基数和2^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 类型,即使他们知道非负。使用 Int 类型意味着整数常量和变量将易于复用,易于匹配整数面值的类型推测。
只有当必要的时候,才使用其他整数类型,比如处理外部长度明确的数据,或者为了优化性能、内存占用等,或其他必要的优化。使用显示长度的类型有助于发现值溢出和暗示正在使用的数据的性质。 - 整数转换
不同整数类型的常量或变量存储的范围是不同的。Int8 的范围是-128至127,而 UInt8 的范围是 0 至 255 。如果数字不在整数类型的范围内,编译器将报告一个错误:
let cannotBeNegative: UInt8 = -1
// UInt8 cannot store negative numbers, and so this will report an error
let tooBig: Int8 = Int8.max + 1
// Int8 cannot store a number larger than its maximum value,
// and so this will also report an error
因为每个整数类型可以存储不同范围的值,所以,你必须根据不同的情况选择数值类型的转换。这样可以防止隐式转换的错误,也可以表明类型转换的意图。
将一个特定的数字类型转换成另一个,要用这个数字来初始化成一个需要类型的新数字。在下面的示例中,常量 twoThousand 是 UInt16 类型,然而常量 one 是 UInt8 类型。 它们不能直接加在一起,因为他们不是同一类型。所以,这个例子中调用 UInt16(one) 来创建新的 one 的值,并使用这个新值来计算:
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
因为这2个数现在都是 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 equals 3.14159, and is inferred to be of type Double
通过常量 three 的值创建一个新类型 Double 的值,所以,加号两边是相同的类型。如果不转换,是不允许相加的。
反过来也是一样的,可以将 Float 、Double 类型的值转换为整数
let integerPi = Int(pi)
// integerPi equals 3, and is inferred to be of type Int
Float 和 Double 类型的值会被截断。比如:4.75会变成4,-3.9会变成-3。
注意:结合数字常量和变量的规则不同于字面量的规则。字面量3可以直接和字面量0.14159相加,因为数字字面量本身没有明确的类型。在编译器求值的时候会推测类型。
类型别名
类型别名就是给一个类型定义的一个小名。通过 typealias 关键字进行定义。
类型别名是非常有用的,特别是一个非常有意义的名称时。比如,处理特定大小的外部来源的数据:
typealias AudioSample = UInt16
一旦你定义一个类型别名,你可以在任何地方使用别名来替代原来的名称:
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound is now 0
本例中,AudioSample 被定义为 UInt16 的一个别名。所以,AudioSample.min 实际上是 UInt16.min ,所以 maxAmplitudeFound 的初值是0。
其它数据类型
- 布尔型
Swift 有一个基本的类型: 布尔型(Bool)。布尔值是逻辑值,他们只能是真或假。Swift 提供了两个布尔常量值:true ,false:
let orangesAreOrange = true
let turnipsAreDelicious = false
orangesAreOrange 和 turnipsAreDelicious 的类型会被推测为 Bool 型,因为它们的初值是布尔字面量。就像 Int 和 Double 一样,在创建变量并赋值 true 和 false的时候,就不需要显示的声明为 Boolean 型了。编译器会自动推测出变量的类型,所以 Swift 代码更加高效,可读性更强。
当你编写条件语句的时候,比如 if ,Boolean 是非常重要的:
if turnipsAreDelicious {
print("Mmm, tasty turnips! ")
} else {
print("Eww, turnips are horrible.")
}
// prints "Eww, turnips are horrible."
Swfit 类型安全检查会阻止非 Boolean 类型的值用在 Boolean 的地方。下面的例子中编译器会报告错误:
let i = 1
if i {
// this example will not compile, and will report an error
}
然而,下面选择例子是可以的:
let i = 1
if i == 1 {
// this example will compile successfully
}
i == 1 表达式的结果是 Boolean 型的,所以可以通过类型安全检查。
和 Swift 的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图是清晰的。
- 元组
元组(tuples)是把多个值组合成一个复合值。元组内的值可以使任意类型,并不要求是相同类型。
比如(404, "Not Found") ,这是一个描述 HTTP 状态码(HTTP status code)的元组,Http 状态码是请求web 服务器时返回的一个值。如果你请求的页面不存在,就会返回 404 。
let http404Error = (404, "Not Found")
// http404Error is of type (Int, String), and equals (404, "Not Found")
(404, "Not Found") 是将一个 Int 类型值和一个 String 类型值组合在一起,表示 HTTP 状态码的两个部分:数字和描述。它可以被描述为 “一个(Int,String)类型的元组”。
元组可以包含任何类型,类型的顺序也是随意的。比如,你可以创建 (Int, Int, Int) 或者 (String, Bool) 元组,或者其他任何你想要的元组。
您可以将一个元组的内容分解成单独的常量或变量,然后就可以正常访问了:
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// prints "The status code is 404"
print("The status message is \(statusMessage)")
// prints "The status message is Not Found"
如果你只需要一部分元组的值,忽略的部分用下划线(_)标记:
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// prints "The status code is 404"
另外,可以使用索引访问元组中的各个元素,索引数字从0开始:
print("The status code is \(http404Error.0)")
// prints "The status code is 404"
print("The status message is \(http404Error.1)")
// prints "The status message is Not Found"
你可以给元组的各个元素进行命名:
let http200Status = (statusCode: 200, description: "OK")
这时,可以使用元素名来访问这些元素的值:
print("The status code is \(http200Status.statusCode)")
// prints "The status code is 200"
print("The status message is \(http200Status.description)")
// prints "The status message is OK"
元组作为函数的返回值时特别有用。一个试图访问网页的函数可能会返回一个(Int, String) 元组来描述是否成功。和只能返回一个值比起来,返回一个包含两个不同类型值的元组让函数更有用。
注意:元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体。
- 可选(Optionals)
使用可选(optionals)来处理可能缺失值的情况。一个可选(optionals)表示:
有值,等于 x
或者
没有值
注意:C 和 Objective-C 中没有可选这个概念。Objective-C 中的一个特性与可选比较类似:一个方法要么返回一个对象,要么返回 nil,nil 表示“缺少一个合法的对象”。
例如:Swift 的 String 类型有一个方法 Int ,用于将一个 String 值转换成一个 Int 值。然而,不是所有的字符串都可以转换成一个整数。字符串 "123" 可以被转换成数字123,但是"hello, world"是不行的。
下面的例子调用 Int 方法试图将一个 String 转换成 Int:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber is inferred to be of type "Int?", or "optional Int"
因为 Int 方法可能失败,所以它返回一个可选的(optional)Int,而不是一个Int。一个可选的 Int 表示为: Int? 。问号表示是可选的,也就是说可能包含 Int 值也可能不包含。(只能是 Int 或者什么都没有。不能包含其他值,比如 Bool值或者 String 值。)
If语句和强制解析
可以使用 if 语句来判断一个可选的是否包含一个值。如果包含一个值,结果为 true ;否则 false。
如果你确定可选包含一个值,你可以在名称后面加惊叹号 ( ! ) 访问可选的值。惊叹号 ( ! )表示:“ 这个可选有值,请使用它。” 这就是所谓的 强制解析(forced unwrapping)可选的值。
if (convertedNumber != nil) {
print("\(possibleNumber) has an integer value of \(convertedNumber!)")
} else {
print("\(possibleNumber) could not be converted to an integer")
}
注意:如果用 ! 来获取一个不存在的可选值会导致运行时错误。使用 ! 来强制解析值之前,一定要确定可选包含一个非 nil 的值(if判断一下)。
可选绑定
可选绑定(optional binding)用来判断可选是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定一般用在 if 和 while 语句中,对可选的值进行判断并把值赋给一个常量或者变量。
if let constantName = someOptional {
statements
}
你可以使用可选绑定重写 possibleNumber 例子:
if let actualNumber = Int(possibleNumber) {
print("\(possibleNumber) has an integer value of \(actualNumber)")
} else {
print("\(possibleNumber) could not be converted to an integer")
}
// prints "123 has an integer value of 123"
这段代码表示:“ 如果 possibleNumber.toInt 返回的可选 Int 包含一个值,创建常量 actualNumber,并将可选包含的值赋给它。”
如果转换成功,actualNumber 常量可以在 if 语句的第一个分支中使用。actualNumber已经被初始化,所以不需要再使用 ! 获取它的值。在这个例子中,actualNumber用来输出转换结果。
你可以在可选绑定中使用常量和变量。如果你只想在if语句的第一个分支中使用actualNumber的值,你可以改成 if var actualNumber,这样可选包含的值就会被赋给一个变量而非常量。
nil
通过给可选的变量赋值为 nil 来表示它没有值:
var serverResponseCode: Int? = 404
// serverResponseCode contains an actual Int value of 404
serverResponseCode = nil
// serverResponseCode now contains no value
注意:nil 不能用于非可选的常量和变量。如果代码中有常量或者变量需要处理值缺失的情况,把它们声明成对应的可选类型。
如果定义一个可选常量或变量没有提供默认值,它们会被自动设置为 nil :
var surveyAnswer: String?
// surveyAnswer is automatically set to nil
注意:Swift 的 nil 和 Objective-C 的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针,它是一个确定的值,用来表示值的缺失。任何类型的可选都可以被设置为 nil,不只是对象类型。
隐式解析可选
可选暗示了常量或者变量可以“没有值”。可选可以通过 if 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。
有时候在程序中,第一次被赋值之后,可以确定一个可选总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
这种情况下的可选类型可以被定义为:隐式解析可选(implicitly unwrapped optionals)。声明一个隐式解析可选的方式是:把可选的类型的后面的问号(String? )改成感叹号(String! )。
一个隐式解析可选本质上是一个普通的可选,但是可以被当做非可选使用,并不需要每次都使用解析来获取可选值。下面的例子说明了可选 String 和隐式解析可选 String 的区别:
let possibleString: String? = "An optional string."
print(possibleString! ) // requires an exclamation mark to access its value
// prints "An optional string."
let assumedString: String! = "An implicitly unwrapped optional string."
print(assumedString) // no exclamation mark is needed to access its value
// prints "An implicitly unwrapped optional string."
可以把隐式解析可选当做一个可以自动解析的可选。只要声明的时候把惊叹号(!)放到类型的结尾,而不是在每次取值时,把惊叹号(!)放到可选名字的结尾。
注意:如果隐式解析可选没有值,尝试取值会触发运行时错误。和在没有值的普通可选后面加一个惊叹号是一样的。
当然,仍然可以把隐式解析可选当做普通可选来判断它是否包含值:
if (assumedString != nil) {
print(assumedString)
}
// prints "An implicitly unwrapped optional string."
在可选绑定中使用隐式解析可选来检查并解析它的值:
if let definiteString = assumedString {
print(definiteString)
}
// prints "An implicitly unwrapped optional string."
注意:如果一个变量值可能变成 nil 的话请不要使用隐式解析可选。如果你需要在变量的生命周期中判断是否是 nil 的话,请使用普通可选。
断言
可选会让你检查可能存在的或者也可能不存在的值,这样,你就可以在编码过程中处理缺乏的值。然而,在某些情况下,如果值不存或者不满足某些条件时,你的代码可能不需要继续执行。在这些情况下,你可以在代码结束执行之前触发一个断言(assertion),并通过调试来查找值缺失或无效的原因。
- 断言调试
断言会在运行时判断一个逻辑条件是否为true。从字面上讲,断言“断言”条件是true。使用断言来确保某些重要的条件被满足后再运行其他代码。
断言:如果条件的结果为 true,代码正常执行;如果条件的结果为 false,代码执行结束,并终止应用程序。
在调试环境中触发断言:
比如当你在 Xcode 构建和运行一个应用程序,断言触发时,你可以看到错误状态发生的确切位置和查询应用程序的状态。此外,断言允许你附加一条调试信息。
你可以使用全局 assert 函数来写一个断言。向 assert 函数传入一个结果为 true 或者 false 的表达式以及一条信息,当表达式为 false 的时候这条信息会被显示:
let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// this causes the assertion to trigger, because age is not >= 0
在这个例子中,只有 age >= 0 的时候代码才会继续运行。如果 age 的值是负数,那么 age >= 0 为 false,断言被触发,结束应用。
断言信息不能使用字符串插值。断言信息可以省略,就像这样:
assert(age >= 0)
- 何时使用断言
当条件可能为假时触发断言,但是最终一定要保证条件为真,这样代码才能继续运行。断言的适用情景:
整数下标索引被传递一个定制的下标实现,下标索引值可能太小或者太大。
给函数传入一个值,但是非法的值可能导致函数不能正常执行。
可选值现在是 nil,但是后面的代码运行需要一个非nil值。
注意:断言会导致应用终止运行,所以代码的设计要避免非法条件的出现。但是,在应用发布之前,非法条件的出现触发断言可以快速发现问题。