常量和变量
声明常量和变量
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
一行声明多个常量或者变量,逗号分隔
var x = 0.0,y = 0.0,z = 0.0
类型标注
类型标注说明常量或者变量中要存储的值的类型,类型标注,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
var welcomeMessage: String
“声明一个类型为 String ,名字为 welcomeMessage 的变量。”
在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型标注:
var red,green,blue: Double
常量变量命名
可以用任何你喜欢的字符作为常量和变量名,包括 Unicode 字符:
let π = 3.14159
let 你好 = "你好世界"
let ���� = "dogcow"
常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。
输出常量和变量
你可以用print(_:separator:terminator:)函数来输出当前常量或变量的值:
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
print(friendlyWelcome)
// 输出 "Bonjour!"
print(_:separator:terminator:) 将会输出内容到“console”面板上。separator 和 terminator 参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给 terminator 参数–例如,print(someValue,terminator:”“)
Swift 用字符串插值(string interpola
tion)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// 输出 "The current value of friendlyWelcome is Bonjour!
注释
// 这是一个注释
/* 这是一个,多行注释 */
/* 这是第一个多行注释的开头 /* 这是第二个被嵌套的多行注释 */
这是第一个多行注释的结尾 */
分号
Swift 并不强制要求你在每条语句的结尾处使用分号(;),你打算在同一行内写多条独立的语句,必须使用分号
let cat = "��"; print(cat)
// 输出 "��"
整数
整数可以是 有符号(正、负、零)或者 无符号(正、零)。
Swift 提供了8,16,32和64位的有符号和无符号整数类型,与C命名方式很像,UInt8,Int32
整数范围
你可以访问不同整数类型的 min 和 max 属性来获取对应类型的最小值和最大值:
let minValue = UInt8.min // minValue 为 0,是 UInt8 类型
let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型
Int
Swift 提供了一个特殊的整数类型Int,长度与当前平台的原生字长相同:
- 在32位平台上,Int 和 Int32 长度相同。
- 在64位平台上,Int 和 Int64 长度相同。
除非你需要特定长度的整数,一般来说使用 Int 就够了。这可以提高代码一致性和可复用性
UInt
Swift 也提供了一个特殊的无符号类型 UInt,长度与当前平台的原生字长相同:
- 在32位平台上,UInt 和 UInt32 长度相同。
- 在64位平台上,UInt 和 UInt64 长度相同。
浮点数
浮点数是有小数部分的数字,比如 3.14159 ,0.1 和 -273.15。
浮点类型比整数类型表示的范围更大,可以存储比 Int 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:
- Double表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。
- Float表示32位浮点数。精度要求不高的话可以使用此类型。
类型安全和类型推断
Swift 是一个类型安全(type safe)的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个String,你绝对不可能不小心传进去一个Int。
由于 Swift 是类型安全的,所以它会在编译你的代码时进行类型检查(type checks),并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。
当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型,Swift 会使用类型推断(type inference)来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。
因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。
例如,如果你给一个新常量赋值 42 并且没有标明类型,Swift 可以推断出常量类型是 Int ,因为你给它赋的初始值看起来像一个整数:
let meaningOfLife = 42
// meaningOfLife 会被推测为 Int 类型
当推断浮点数的类型时,Swift 总是会选择 Double 而不是Float。
如果表达式中同时出现了整数和浮点数,会被推断为 Double 类型:
let anotherPi = 3 + 0.14159
// anotherPi 会被推测为 Double 类型
数值型字面量
整数字面量可以被写作:
- 一个十进制数,没有前缀
- 一个二进制数,前缀是0b
- 一个八进制数,前缀是0o
- 一个十六进制数,前缀是0x
下面的所有整数字面量的十进制值都是17:
let decimalInteger = 17
let binaryInteger = 0b10001 // 二进制的17
let octalInteger = 0o21 // 八进制的17
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)
SomeType(ofInitialValue) 是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,UInt16 有一个构造器,可以接受一个UInt8类型的值,所以这个构造器可以用现有的 UInt8 来创建一个新的 UInt16。注意,你并不能传入任意类型的值,只能传入 UInt16 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考扩展。
整数和浮点数转换
整数和浮点数的转换必须显式指定类型:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159,所以被推测为 Double 类型
浮点数到整数的反向转换同样行,整数类型可以用 Double 或者 Float 类型来初始化:
let integerPi = Int(pi)
// integerPi 等于 3,所以被推测为 Int 类型
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 4.75 会变成 4,-3.9 会变成 -3。
类型别名
类型别名(type aliases)就是给现有类型定义另一个名字。你可以使用typealias关键字来定义类型别名。
当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据:
typealias AudioSample = UInt16
定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名:
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 现在是 0
布尔值
Swift 有一个基本的布尔(Boolean)类型,叫做Bool。布尔值指逻辑上的值,因为它们只能是真或者假。Swift 有两个布尔常量,true 和 false:
let orangesAreOrange = true
let turnipsAreDelicIoUs = false
if turnipsAreDelicIoUs {
print("Mmm,tasty turnips!")
} else {
print("Eww,turnips are horrible.")
}
// 输出 "Eww,turnips are horrible."
如果你在需要使用 Bool 类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误:
let i = 1
if i { // 这个例子不会通过编译,会报错 }
if i == 1 { // 这个例子会编译成功 }
元组
元组(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"
你可以在定义元组的时候给单个元素命名:
let http200Status = (statusCode: 200,description: "OK")
给元组中的元素命名后,你可以通过名字来获取这些元素的值:
print("The status code is \(http200Status.statusCode)")
// 输出 "The status code is 200"
print("The status message is \(http200Status.description)")
// 输出 "The status message is OK"
可选类型
使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示:有值,等于 x;或者,没有值
来看一个例子。Swift 的 Int 类型有一种构造器,作用是将一个 String 值转换成一个 Int 值。然而,并不是所有的字符串都可以转换成一个整数。字符串 “123” 可以被转换成数字 123 ,但是字符串 “hello,world” 不行。、下面的例子使用这种构造器来尝试将一个 String 转换成 Int:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个 Int。一个可选的 Int 被写作 Int? 而不是 Int。问号暗示包含的值是可选类型,也就是说可能包含 Int 值也可能不包含值。(不能包含其他任何值比如 Bool 值或者 String 值。只能是 Int 或者什么都没有。)
nil
你可以给可选变量赋值为nil来表示它没有值:
var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 nil:
var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil
if 语句以及强制解析
你可以使用 if 语句和 nil 比较来判断一个可选值是否包含值。你可以使用“相等”(==)或“不等”(!=)来执行比较。
如果可选类型有值,它将不等于 nil:
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
// 输出 "convertedNumber contains some integer value."
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping):
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// 输出 "convertedNumber has an integer value of 123."
可选绑定
使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 if 和 while 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。if 和 while 语句,请参考控制流。
像下面这样在 if 语句中写一个可选绑定:
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")
}
// 输出 "'123' has an integer value of 123"
这段代码可以被理解为:
“如果 Int(possibleNumber) 返回的可选 Int 包含一个值,创建一个叫做 actualNumber 的新常量并将可选包含的值赋给它。”
如果转换成功,actualNumber 常量可以在 if 语句的第一个分支中使用。它已经被可选类型 包含的 值初始化过,所以不需要再使用 ! 后缀来获取它的值。在这个例子中,actualNumber 只被用来输出转换结果。
你可以在可选绑定中使用常量和变量。如果你想在if语句的第一个分支中操作 actualNumber 的值,你可以改成 if var actualNumber,这样可选类型包含的值就会被赋给一个变量而非常量。
你可以包含多个可选绑定或多个布尔条件在一个 if 语句中,只要使用逗号分开就行。只要有任意一个可选绑定的值为nil,或者任意一个布尔条件为false,则整个if条件判断为false,这时你就需要使用嵌套 if 条件语句来处理,如下所示:
if let firstNumber = Int("4"),let secondNumber = Int("42"),firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
// 输出 "4 < 42 < 100"
if let firstNumber = Int("4") {
if let secondNumber = Int("42") {
if firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
}
}
// 输出 "4 < 42 < 100"
隐式解析可选类型
如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过 if 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。
有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(String?)改成感叹号(String!)来声明一个隐式解析可选类型。
当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考无主引用以及隐式解析可选属性。
一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 String 和隐式解析可选类型 String 之间的区别:
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感叹号
你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。
你仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值:
if assumedString != nil {
print(assumedString)
}
// 输出 "An implicitly unwrapped optional string."
你也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值:
if let definiteString = assumedString {
print(definiteString)
}
// 输出 "An implicitly unwrapped optional string."
错误处理
你可以使用 错误处理(error handling) 来应对程序执行中可能会遇到的错误条件。
相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。
当一个函数遇到错误条件,它能报错。调用函数的地方能抛出错误消息并合理处理。
func canThrowAnError() throws {
// 这个函数有可能抛出错误
}
一个函数可以通过在声明中添加throws关键词来抛出错误消息。当你的函数能抛出错误消息时,你应该在表达式中前置try关键词。
do {
try canThrowAnError()
// 没有错误消息抛出
} catch {
// 有一个错误消息抛出
}
一个do语句创建了一个新的包含作用域,使得错误能被传播到一个或多个catch从句。
func makeASandwich() throws {
// ...
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
在此例中,如果没有干净的盘子或者某个原料缺失,makeASandwich()(做一个三明治)函数会抛出一个错误消息。因为 makeASandwich() 抛出错误,函数调用被包裹在 try 表达式中。将函数包裹在一个 do 语句中,任何被抛出的错误会被传播到提供的 catch 从句中。
如果没有错误被抛出,eatASandwich() 函数会被调用。如果一个匹配 SandwichError.outOfCleanDishes 的错误被抛出,washDishes() 函数会被调用。如果一个匹配 SandwichError.missingIngredients 的错误被抛出,buyGroceries(_:) 函数会被调用,并且使用 catch 所捕捉到的关联值 [String] 作为参数。
断言和预处理
断言和预处理是负责检查的,发生在运行时,在执行后面的代码之前,可以使用它们判断是否满足基本条件。
如果断言或者预处理中的Boolean条件是true,后续代码照常执行,如果Boolean条件是false,当前程序的状态是无效的,代码执行结束,app也会终止。
编码的时候,使用断言和预处理来表示假设和期望,所以可以把它们作为程序的一部分,断言帮助你找到开发阶段的错误和不正确的假设,预处理帮你探测产品中的问题,处理在运行时验证你的期望,断言和预处理在你的代码中也变成了一个有用的格式文档,不像在Error Handling讨论的错误情况,断言和预处理不用于预判错误或者恢复操作,因为一个失败的断言或者预处理显示程序的无效状态,没有方法捕获一个失败的断言。
使用断言和预处理并不能保证你的代码不会出现无效状态,然而,使用它们可以强制无效的数据、状态可以让你的程序更可预期的结束,并且协助调试问题。当无效状态出现的时候今早停止执行,也可以帮助减小无效状态造成的损害。
断言和预处理的区别是他们检查的时机,断言是仅在调试阶段执行检查,但是预处理会在调试和产品阶段都会执行检查。在产品阶段,断言中的条件是不会执行的,也就是说,可以在开发阶段尽量多的写断言,而不会影响产品阶段的性能表现。
使用断言调试
调用assert(::file:line:)函数来写一个断言,参数是一个结果为true或者false的表达式和一个在表达式为false的时候打印的字符串消息,例如:
let age = -3
assert(age >= 0,"A person's age can't be less than zero.")
// This assertion fails because -3 is not >= 0.
例子中,如果age > 0,代码执行继续,如果age是负数,断言执行失败,终止程序
也可以省略消息部分,例如:
assert(age >= 0)
如果代码已经检查了条件,使用 assertionFailure(_:file:line:)函数来标明断言失败,例如:
if age > 10 {
print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {
print("You can ride the ferris wheel.")
} else {
assertionFailure("A person's age can't be less than zero.")
}
Enforcing Preconditions
当条件可能是false的时候使用预处理,但是最终必须确保是true继续执行执行,例如,使用预处理检查下标操作是否越界或者函数的传入值是否有效
通过调用 precondition(::file:line:)函数写一个预处理,传入一个结果是true或者false的表达式,以及一个在表达式是false的时候显示的消息字符串,例如:
// In the implementation of a subscript...
precondition(index > 0,"Index must be greater than zero.")
也可以调用 preconditionFailure(:file:line:) 函数来表示错误发生,例如,所有的有效输入数据应该被其中一个case处理,但是switch的默认case执行,这个时候就可以调用 preconditionFailure(:file:line:) 来显示发生了错误。