Swift语法图
2021-03-29 15:37:10 0 举报
AI智能生成
swift语法流程图
作者其他创作
大纲/内容
构造过程
----
----
----
----
----
----
----
----
----
----
----
----
----
----
析构过程
----
----
----
----
----
----
----
----
可选链
----
----
----
----
----
----
----
----
----
----
----
----
----
错误处理
----
----
----
----
----
----
----
----
----
----
----
----
----
----
类型转换
----
----
----
----
----
----
----
----
----
----
----
----
----
----
嵌套类型
----
----
----
----
----
----
----
----
----
----
----
----
----
----
扩展
----
----
----
----
----
----
----
----
----
----
----
----
----
----
协议
----
----
----
----
----
----
----
----
----
----
----
----
----
----
泛型
----
----
----
----
----
----
----
----
----
----
----
----
----
----
不透明类型
----
----
----
----
----
----
----
----
----
----
----
----
----
----
自动引用计数
----
----
----
----
----
----
----
----
----
----
----
----
----
----
内存安全
----
----
----
----
----
----
----
----
----
----
----
----
----
----
访问控制
----
----
----
----
----
----
----
----
----
----
----
----
----
----
高级运算符
----
----
----
----
----
----
----
----
----
----
----
----
----
----
基础知识
变量与常量
用 var 来声明变量
用 let 来声明常量
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
注意
常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。一旦你将常量或者变量声明为确定的类型,你就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时,你也不能将常量与变量进行互转。
如果你需要使用与 Swift 保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。
输出常量和变量
可以用 print(_:separator:terminator:) 函数来输出当前常量或变量的值,<br>是一个用来输出一个或多个值到适当输出区的全局函数; 默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给 terminator 参数--例如,print(someValue, terminator:"") <br>
Swift 用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:<br>print("The current value of friendlyWelcome is \(friendlyWelcome)")<br>// 输出“The current value of friendlyWelcome is Bonjour!”<br>
类型注解<br>
var welcomeMessage: String
这个例子给 welcomeMessage 变量添加了类型注解,表示这个变量可以存储 String 类型的值:声明中的冒号代表着“是...类型”,所以这行代码可以被理解为:<br>“声明一个类型为 String ,名字为 welcomeMessage 的变量。”<br>“类型为 String ”的意思是“可以存储任意 String 类型的值。”
注释<br>
有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句:<br>let cat = "🐱"; print(cat)<br>// 输出“🐱”<br>
单行注释以双正斜杠(//)作为起始标记:<br>/* 这也是一个注释,<br>但是是多行的 */<br>
可以先生成一个多行注释块,然后在这个注释块之中再嵌套成第二个多行注释。终止注释时先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记<br>/* 这是第一个多行注释的开头<br>/* 这是第二个被嵌套的多行注释 */<br>这是第一个多行注释的结尾 */<br>
整数
整数就是没有小数部分的数字,比如 42 和 -23 。整数可以是 有符号(正、负、零)或者 无符号(正、零)。Swift 提供了8、16、32和64位的有符号和无符号整数类型<br>比如8位无符号整数类型是 UInt8,32位有符号整数类型是 Int32<br>
整数范围
let minValue = UInt8.min // minValue 为 0,是 UInt8 类型<br>let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型
Int
Swift 提供了一个特殊的整数类型 Int,长度与当前平台的原生字长相同:<br>在32位平台上,Int 和 Int32 长度相同。<br>在64位平台上,Int 和 Int64 长度相同。
UInt
Swift 也提供了一个特殊的无符号类型 UInt,长度与当前平台的原生字长相同:<br>在32位平台上,UInt 和 UInt32 长度相同。<br>在64位平台上,UInt 和 UInt64 长度相同。
注意<br>尽量不要使用 UInt,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用 Int,即使你要存储的值已知是非负的。统一使用 Int 可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断
浮点数
浮点数是有小数部分的数字,比如 3.14159、0.1 和 -273.15。<br>浮点类型比整数类型表示的范围更大,可以存储比 Int 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:<br>Double 表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。<br>Float 表示32位浮点数。精度要求不高的话可以使用此类型。
注意<br>Double 精确度很高,至少有 15 位小数,而 Float 只有 6 位小数。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择 Double。
类型安全(type safe)和类型推断(type inference)
let meaningOfLife = 42<br>// meaningOfLife 会被推测为 Int 类型
let pi = 3.14159<br>// pi 会被推测为 Double 类型<br>当推断浮点数的类型时,Swift 总是会选择 Double 而不是 Float。<br>
let anotherPi = 3 + 0.14159<br>// anotherPi 会被推测为 Double 类型<br>如果表达式中同时出现了整数和浮点数,会被推断为 Double 类型:<br>
数值类型转换
整数字面量可以被写作:<br>一个十进制数,没有前缀<br>一个二进制数,前缀是 0b<br>一个八进制数,前缀是 0o<br>一个十六进制数,前缀是 0x<br>let decimalInteger = 17<br>let binaryInteger = 0b10001 // 二进制的17<br>let octalInteger = 0o21 // 八进制的17<br>let hexadecimalInteger = 0x11 // 十六进制的17<br>
浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是 0x )。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。十进制浮点数也可以有一个可选的指数(exponent),通过大写或者小写的 e 来指定;十六进制浮点数必须有一个指数,通过大写或者小写的 p 来指定。<br>下面的这些浮点字面量都等于十进制的 12.1875:<br>let decimalDouble = 12.1875<br>let exponentDouble = 1.21875e1<br>let hexadecimalDouble = 0xC.3p0<br>
通常来讲,即使代码中的整数常量和变量已知非负,也请使用 Int 类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量的类型推断。<br>只有在必要的时候才使用其他整数类型,
整数转换
Int8 类型的常量或者变量可以存储的数字范围是 -128~127,而 UInt8 类型的常量或者变量能存储的数字范围是 0~255。如果数字超出了常量或者变量可存储的范围,编译的时候会报错:<br>let cannotBeNegative: UInt8 = -1<br>// UInt8 类型不能存储负数,所以会报错<br>let tooBig: Int8 = Int8.max + 1<br>// Int8 类型不能存储超过最大值的数,所以会报错<br><br>因为它们类型不同。所以要调用 UInt16(one) 来创建一个新的 UInt16 数字并用 one 的值来初始化,然后使用这个新数字来计算:<br>let twoThousand: UInt16 = 2_000<br>let one: UInt8 = 1<br>let twoThousandAndOne = twoThousand + UInt16(one)<br>
整数和浮点数转换
整数和浮点数的转换必须显式指定类型:<br>let three = 3<br>let pointOneFourOneFiveNine = 0.14159<br>let pi = Double(three) + pointOneFourOneFiveNine<br>// pi 等于 3.14159,所以被推测为 Double 类型<br>
浮点数到整数的反向转换同样行,整数类型可以用 Double 或者 Float 类型来初始化:<br>let integerPi = Int(pi)<br>// integerPi 等于 3,所以被推测为 Int 类型<br>
类型别名(type aliases)
就是给现有类型定义另一个名字。你可以使用 typealias 关键字来定义类型别名。<br>typealias AudioSample = UInt16<br>var maxAmplitudeFound = AudioSample.min<br>// maxAmplitudeFound 现在是 0 ,AudioSample 被定义为 UInt16 的一个别名。因为它是别名,AudioSample.min 实际上是 UInt16.min,所以会给 maxAmplitudeFound 赋一个初值 0。<br>
布尔值(Boolean)
Swift 有两个布尔常量,true 和 false:<br>let orangesAreOrange = true<br>let turnipsAreDelicious = false<br>
if turnipsAreDelicious {<br> print("Mmm, tasty turnips!")<br>} else {<br> print("Eww, turnips are horrible.")<br>}<br>// 输出“Eww, turnips are horrible.”
let i = 1<br>if i == 1 {<br> // 这个例子会编译成功<br>}
元组(tuples)
元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。<br>let http404Error = (404, "Not Found")<br>// http404Error 的类型是 (Int, String),值是 (404, "Not Found") (404, "Not Found") 元组把一个 Int 值和一个 String 值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为 (Int, String) 的元组”。<br>
可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了:<br>let (statusCode, statusMessage) = http404Error<br>print("The status code is \(statusCode)")<br>// 输出“The status code is 404”<br>print("The status message is \(statusMessage)")<br>// 输出“The status message is Not Found”<br>
只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(_)标记:<br>let (justTheStatusCode, _) = http404Error<br>print("The status code is \(justTheStatusCode)")<br>// 输出“The status code is 404”<br>
可以通过下标来访问元组中的单个元素,下标从零开始:<br>print("The status code is \(http404Error.0)")<br>// 输出“The status code is 404”<br>print("The status message is \(http404Error.1)")<br>// 输出“The status message is Not Found”<br>
可以在定义元组的时候给单个元素命名:<br>let http200Status = (statusCode: 200, description: "OK")<br>
给元组中的元素命名后,你可以通过名字来获取这些元素的值:<br>print("The status code is \(http200Status.statusCode)")<br>// 输出“The status code is 200”<br>print("The status message is \(http200Status.description)")<br>// 输出“The status message is OK”<br>
作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 (Int, String) 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。<br>注意<br>当遇到一些相关值的简单分组时,元组是很有用的。元组不适合用来创建复杂的数据结构。如果你的数据结构比较复杂,不要使用元组,用类或结构体去建模。<br>
可选类型(optionals)
可选类型(optionals)来处理值可能缺失的情况。可选类型表示两种可能: 或者有值,或者根本没有值。注意 C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回 nil,nil 表示“缺少一个合法的对象”。<br>Swift 的可选类型可以让你暗示任意类型的值缺失,并不需要一个特殊值。<br>
let possibleNumber = "123"<br>let convertedNumber = Int(possibleNumber)<br>// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
nil
可以给可选变量赋值为 nil 来表示它没有值:<br>var serverResponseCode: Int? = 404<br>// serverResponseCode 包含一个可选的 Int 值 404<br>serverResponseCode = nil<br>// serverResponseCode 现在不包含值<br>注意<br>nil 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。<br>如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 nil:<br>var surveyAnswer: String?<br>// surveyAnswer 被自动设置为 nil<br>
注意<br>Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。
if 语句以及强制解析
可以使用 if 语句和 nil 比较来判断一个可选值是否包含值。你可以使用“相等”(==)或“不等”(!=)来执行比较。<br>如果可选类型有值,它将不等于 nil:<br>if convertedNumber != nil {<br> print("convertedNumber contains some integer value.")<br>}<br>// 输出“convertedNumber contains some integer value.”<br>
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping):<br>if convertedNumber != nil {<br> print("convertedNumber has an integer value of \(convertedNumber!).")<br>}<br>// 输出“convertedNumber has an integer value of 123.”<br>
注意 使用 ! 来获取一个不存在的可选值会导致运行时错误。使用 ! 来强制解析值之前,一定要确定可选包含一个非 nil 的值。
可选绑定(optional binding)
是用来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 if 和 while 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。
if let actualNumber = Int(possibleNumber) {<br> print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")<br>} else {<br> print("\'\(possibleNumber)\' could not be converted to an integer")<br>}<br>// 输出“'123' has an integer value of 123”<br>这段代码可以被理解为:<br>“如果 Int(possibleNumber) 返回的可选 Int 包含一个值,创建一个叫做 actualNumber 的新常量并将可选包含的值赋给它。”<br>如果转换成功,actualNumber 常量可以在 if 语句的第一个分支中使用。它已经被可选类型 包含的 值初始化过,所以不需要再使用 ! 后缀来获取它的值。在这个例子中,actualNumber 只被用来输出转换结果。<br>你可以在可选绑定中使用常量和变量。如果你想在 if 语句的第一个分支中操作 actualNumber 的值,你可以改成 if var actualNumber,这样可选类型包含的值就会被赋给一个变量而非常量。<br>你可以包含多个可选绑定或多个布尔条件在一个 if 语句中,只要使用逗号分开就行。只要有任意一个可选绑定的值为 nil,或者任意一个布尔条件为 false,则整个 if 条件判断为 false。下面的两个 if 语句是等价的:<br>if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {<br> print("\(firstNumber) < \(secondNumber) < 100")<br>}<br>// 输出“4 < 42 < 100”<br><br>if let firstNumber = Int("4") {<br> if let secondNumber = Int("42") {<br> if firstNumber < secondNumber && secondNumber < 100 {<br> print("\(firstNumber) < \(secondNumber) < 100")<br> }<br> }<br>}<br>// 输出“4 < 42 < 100”<br>
注意<br>在 if 条件语句中使用常量和变量来创建一个可选绑定,仅在 if 语句的句中(body)中才能获取到值。相反,在 guard 语句中使用常量和变量来创建一个可选绑定,仅在 guard 语句外且在语句后才能获取到值
隐式解析可选类型(implicitly unwrapped optionals)
可选类型暗示了常量或者变量可以“没有值”。可选可以通过 if 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(String?)改成感叹号(String!)来声明一个隐式解析可选类型。与其在使用时把感叹号放在可选类型的名称的后面,你可以在定义它时,直接把感叹号放在可选类型的后面。当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中
一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 String 和隐式解析可选类型 String 之间的区别:<br>let possibleString: String? = "An optional string."<br>let forcedString: String = possibleString! // 需要感叹号来获取值<br>let assumedString: String! = "An implicitly unwrapped optional string."<br>let implicitString: String = assumedString // 不需要感叹号<br>
你可以把隐式解析可选类型当做一个可以自动解析的可选类型。当你使用一个隐式解析可选值时,Swift 首先会把它当作普通的可选值;如果它不能被当成可选类型使用,Swift 会强制解析可选值。在以上的代码中,可选值 assumedString 在把自己的值赋给 implicitString 之前会被强制解析,原因是 implicitString 本身的类型是非可选类型的 String。在下面的代码中,optionalString 并没有显式的数据类型。那么根据类型推断,它就是一个普通的可选类型。<br>let optionalString = assumedString<br>// optionalString 的类型是 "String?",assumedString 也没有被强制解析。<br>
如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个感叹号一样。<br>你可以把隐式解析可选类型当做普通可选类型来判断它是否包含值:<br>if assumedString != nil {<br> print(assumedString!)<br>}<br>// 输出“An implicitly unwrapped optional string.”<br>也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值:<br>if let definiteString = assumedString {<br> print(definiteString)<br>}<br>// 输出“An implicitly unwrapped optional string.”<br>
注意<br>如果一个变量之后可能变成 nil 的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是 nil 的话,请使用普通可选类型。
错误处理(error handling)
相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。<br>当一个函数遇到错误条件,它能报错。调用函数的地方能抛出错误消息并合理处理。<br>func canThrowAnError() throws {<br> // 这个函数有可能抛出错误<br>}<br>一个函数可以通过在声明中添加 throws 关键词来抛出错误消息。当你的函数能抛出错误消息时,你应该在表达式中前置 try 关键词。<br>do {<br> try canThrowAnError()<br> // 没有错误消息抛出<br>} catch {<br> // 有一个错误消息抛出<br>}<br>
一个 do 语句创建了一个新的包含作用域,使得错误能被传播到一个或多个 catch 从句。<br>这里有一个错误处理如何用来应对不同错误条件的例子。<br>func makeASandwich() throws {<br> // ...<br>}<br>do {<br> try makeASandwich()<br> eatASandwich()<br>} catch SandwichError.outOfCleanDishes {<br> washDishes()<br>} catch SandwichError.missingIngredients(let ingredients) {<br> buyGroceries(ingredients)<br>}<br>
在此例中,makeASandwich()(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为 makeASandwich() 抛出错误,函数调用被包裹在 try 表达式中。将函数包裹在一个 do 语句中,任何被抛出的错误会被传播到提供的 catch 从句中。<br>如果没有错误被抛出,eatASandwich() 函数会被调用。如果一个匹配 SandwichError.outOfCleanDishes 的错误被抛出,washDishes() 函数会被调用。如果一个匹配 SandwichError.missingIngredients 的错误被抛出,buyGroceries(_:) 函数会被调用,并且使用 catch 所捕捉到的关联值 [String] 作为参数。
断言和先决条件
断言和先决条件是在运行时所做的检查。你可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。如果断言或者先决条件中的布尔条件评估的结果为 true(真),则代码像往常一样继续执行。如果布尔条件评估结果为 false(假),程序的当前状态是无效的,则代码执行结束,应用程序中止。<br>断言和先决条件的不同点是,他们什么时候进行状态检测:断言仅在调试环境运行,而先决条件则在调试环境和生产环境中运行。在生产环境中,断言的条件将不会进行评估。这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。<br>
使用断言进行调试
你可以调用 Swift 标准库的 assert(_:_:file:line:) 函数来写一个断言。向这个函数传入一个结果为 true 或者 false 的表达式以及一条信息,当表达式的结果为 false 的时候这条信息会被显示:<br>let age = -3<br>assert(age >= 0, "A person's age cannot be less than zero")<br>// 因为 age < 0,所以断言会触发<br><br>在这个例子中,只有 age >= 0 为 true 时,即 age 的值非负的时候,代码才会继续执行。如果 age 的值是负数,就像代码中那样,age >= 0 为 false,断言被触发,终止应用。<br>如果不需要断言信息,可以就像这样忽略掉:assert(age >= 0)<br>
如果代码已经检查了条件,你可以使用 assertionFailure(_:file:line:) 函数来表明断言失败了,例如:<br>if age > 10 {<br> print("You can ride the roller-coaster or the ferris wheel.")<br>} else if age > 0 {<br> print("You can ride the ferris wheel.")<br>} else {<br> assertionFailure("A person's age can't be less than zero.")<br>}<br>
强制执行先决条件
当一个条件可能为假,但是继续执行代码要求条件必须为真的时候,需要使用先决条件。例如使用先决条件来检查是否下标越界,或者来检查是否将一个正确的参数传给函数。<br>你可以使用全局 precondition(_:_:file:line:) 函数来写一个先决条件。向这个函数传入一个结果为 true 或者 false 的表达式以及一条信息,当表达式的结果为 false 的时候这条信息会被显示:// 在一个下标的实现里...<br>precondition(index > 0, "Index must be greater than zero.")<br>你可以调用 preconditionFailure(_:file:line:) 方法来表明出现了一个错误,例如,switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 default 分支)处理。<br>
注意<br>如果你使用 unchecked 模式(-Ounchecked)编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为 true(真),他将优化你的代码。然而,fatalError(_:file:line:) 函数总是中断执行,无论你怎么进行优化设定。<br>你能使用 fatalError(_:file:line:) 函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上 fatalError("Unimplemented")作为具体实现。因为 fatalError 不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。
基本运算符
赋值运算符
运算符分为一元、二元和三元运算符:
一元运算符对单一操作对象操作(如 -a)。一元运算符分前置运算符和后置运算符,前置运算符需紧跟在操作对象之前(如 !b),后置运算符需紧跟在操作对象之后(如 c!)。
二元运算符操作两个操作对象(如 2 + 3),是中置的,因为它们出现在两个操作对象之间。
三元运算符操作三个操作对象,和 C 语言一样,Swift 只有一个三元运算符,就是三目运算符(a ? b : c)。
赋值运算符(a = b),表示用 b 的值来初始化或更新 a 的值:<br>let b = 10<br>var a = 5<br>a = b<br>// a 现在等于 10<br>
let (x, y) = (1, 2)<br>// 现在 x 等于 1,y 等于 2
算术运算符<br>
加法(+)<br>减法(-)<br>乘法(*)<br>除法(/)
加法运算符也可用于 String 的拼接:<br>"hello, " + "world" // 等于 "hello, world"<br>
求余运算符
a = (b × 倍数) + 余数
9 % 4 // 等于 1<br>9 = (4 × 2) + 1<br>
-9 % 4 // 等于 -1<br>-9 = (4 × -2) + -1<br>
一元负号运算符
一元负号符(-)写在操作数之前,中间没有空格。数值的正负号可以使用前缀 -(即一元负号符)来切换:<br>let three = 3<br>let minusThree = -three // minusThree 等于 -3<br>let plusThree = -minusThree // plusThree 等于 3, 或 "负负3"<br>
一元正号运算符
一元正号符(+)不做任何改变地返回操作数的值:
let minusSix = -6<br>let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6
组合赋值运算符
如同 C 语言,Swift 也提供把其他运算符和赋值运算(=)组合的组合赋值运算符,组合加运算(+=)是其中一个例子:<br>var a = 1<br>a += 2<br>// a 现在是 3<br>
注意<br>复合赋值运算没有返回值,let b = a += 2 这类代码是错误。这不同于上面提到的自增和自减运算符。
比较运算符(Comparison Operators
Swift 支持以下的比较运算符:<br>等于(a == b)<br>不等于(a != b)<br>大于(a > b)<br>小于(a < b)<br>大于等于(a >= b)<br>小于等于(a <= b)
注意<br>Swift 也提供恒等(===)和不恒等(!==)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在 类与结构 章节的 Identity Operators 部分。<br>每个比较运算都返回了一个标识表达式是否成立的布尔值:<br>1 == 1 // true, 因为 1 等于 1<br>2 != 1 // true, 因为 2 不等于 1<br>2 > 1 // true, 因为 2 大于 1<br>1 < 2 // true, 因为 1 小于2<br>1 >= 1 // true, 因为 1 大于等于 1<br>2 <= 1 // false, 因为 2 并不小于等于 1<br>
比较运算多用于条件语句,如 if 条件:<br>let name = "world"<br>if name == "world" {<br> print("hello, world")<br>} else {<br> print("I'm sorry \(name), but I don't recognize you")<br>}<br>// 输出“hello, world", 因为 `name` 就是等于 "world”<br>
如果两个元组的元素相同,且长度相同的话,元组就可以被比较。比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如:<br>(1, "zebra") < (2, "apple") // true,因为 1 小于 2<br>(3, "apple") < (3, "bird") // true,因为 3 等于 3,但是 apple 小于 bird<br>(4, "dog") == (4, "dog") // true,因为 4 等于 4,dog 等于 dog<br>在上面的例子中,你可以看到,在第一行中从左到右的比较行为。因为 1 小于 2,所以 (1, "zebra") 小于 (2, "apple"),不管元组剩下的值如何。所以 "zebra" 大于 "apple" 对结果没有任何影响,因为元组的比较结果已经被第一个元素决定了。不过,当元组的第一个元素相同时候,第二个元素将会用作比较-第二行和第三行代码就发生了这样的比较。<br><br>当元组中的元素都可以被比较时,你也可以使用这些运算符来比较它们的大小。例如,像下面展示的代码,你可以比较两个类型为 (String, Int) 的元组,因为 Int 和 String 类型的值可以比较。相反,Bool 不能被比较,也意味着存有布尔类型的元组不能被比较。<br>("blue", -1) < ("purple", 1) // 正常,比较的结果为 true<br>("blue", false) < ("purple", true) // 错误,因为 < 不能比较布尔类型<br>
注意<br>Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。
三元运算符(Ternary Conditional Operator)
三元运算符的特殊在于它是有三个操作数的运算符,它的形式是 问题 ? 答案 1 : 答案 2。它简洁地表达根据 问题成立与否作出二选一的操作。如果 问题 成立,返回 答案 1 的结果;反之返回 答案 2 的结果。<br>三元运算符是以下代码的缩写形式:<br>if question {<br> answer1<br>} else {<br> answer2<br>}<br>
let contentHeight = 40<br>let hasHeader = true<br>let rowHeight = contentHeight + (hasHeader ? 50 : 20)<br>// rowHeight 现在是 90
空合运算符(Nil Coalescing Operator)
空合运算符(a ?? b)将对可选类型 a 进行空判断,如果 a 包含一个值就进行解包,否则就返回一个默认值 b。表达式 a 必须是 Optional 类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。<br>空合运算符是对以下代码的简短表达方法:a != nil ? a! : b<br>注意<br>如果 a 为非空值(non-nil),那么值 b 将不会被计算。这也就是所谓的短路求值。<br>
let defaultColorName = "red"<br>var userDefinedColorName: String? //默认值为 nil<br>var colorNameToUse = userDefinedColorName ?? defaultColorName<br>// userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red"
userDefinedColorName = "green"<br>colorNameToUse = userDefinedColorName ?? defaultColorName<br>// userDefinedColorName 非空,因此 colorNameToUse 的值为 "green"
区间运算符(Range Operators)
闭区间运算符
闭区间运算符(a...b)定义一个包含从 a 到 b(包括 a 和 b)的所有值的区间。a 的值不能超过 b。闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 for-in 循环中:<br>for index in 1...5 {<br> print("\(index) * 5 = \(index * 5)")<br>}<br>// 1 * 5 = 5<br>// 2 * 5 = 10<br>// 3 * 5 = 15<br>// 4 * 5 = 20<br>// 5 * 5 = 25<br>
半开区间运算符
半开区间运算符(a..<b)定义一个从 a 到 b 但不包括 b 的区间。 之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。<br>半开区间的实用性在于当你使用一个从 0 开始的列表(如数组)时,非常方便地从0数到列表的长度。<br>let names = ["Anna", "Alex", "Brian", "Jack"]<br>let count = names.count<br>for i in 0..<count {<br> print("第 \(i + 1) 个人叫 \(names[i])")<br>}<br>// 第 1 个人叫 Anna 第 2 个人叫 Alex第 3 个人叫 Brian 第 4 个人叫 Jack<br><br>半开区间操作符也有单侧表达形式,附带上它的最终值。就像你使用区间去包含一个值,最终值并不会落在区间内。例如:<br>for name in names[..<2] {<br> print(name)<br>}<br>// Anna Alex<br>
单侧区间
闭区间操作符有另一个表达形式,可以表达往一侧无限延伸的区间 —— 例如,一个包含了数组从索引 2 到结尾的所有值的区间。在这些情况下,你可以省略掉区间操作符一侧的值。这种区间叫做单侧区间,因为操作符只有一侧有值。例如:<br>for name in names[2...] {<br> print(name)<br>}<br>// Brian Jack<br><br>for name in names[...2] {<br> print(name)<br>}<br>// Anna Alex Brian<br><br>let range = ...5<br>range.contains(7) // false range.contains(4) // true range.contains(-1) // true<br>
逻辑运算符(Logical Operators)
逻辑运算符的操作对象是逻辑布尔值。Swift 支持基于 C 语言的三个标准逻辑运算。<br>逻辑非(!a) <br>逻辑与(a && b)<br>逻辑或(a || b)
逻辑非运算符(!a)对一个布尔值取反,使得 true 变 false,false 变 true。<br>let allowedEntry = false<br>if !allowedEntry {<br> print("ACCESS DENIED")<br>}<br>// 输出“ACCESS DENIED” if !allowedEntry 语句可以读作「如果非 allowedEntry」,接下一行代码只有在「非 allowedEntry」为 true,即 allowEntry 为 false 时被执行。<br>
逻辑与运算符(a && b)表达了只有 a 和 b 的值都为 true 时,整个表达式的值才会是 true。只要任意一个值为 false,整个表达式的值就为 false<br>let enteredDoorCode = true<br>let passedRetinaScan = false<br>if enteredDoorCode && passedRetinaScan {<br> print("Welcome!")<br>} else {<br> print("ACCESS DENIED")<br>}<br>// 输出“ACCESS DENIED”<br>
逻辑或运算符(a || b)是一个由两个连续的 | 组成的中置运算符。它表示了两个逻辑表达式的其中一个为 true,整个表达式就为 true。<br>let hasDoorKey = false<br>let knowsOverridePassword = true<br>if hasDoorKey || knowsOverridePassword {<br> print("Welcome!")<br>} else {<br> print("ACCESS DENIED")<br>}<br>// 输出“Welcome!”<br>
括号优先级
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {<br> print("Welcome!")<br>} else {<br> print("ACCESS DENIED")<br>}<br>// 输出“Welcome!”<br>注意<br>Swift 逻辑操作符 && 和 || 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。<br>
字符串和字符
字符串
与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。<br>但 Swift 中的 String 类型的实现却很快速和现代化。每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式。注意<br>Swift 的 String 类型与 Foundation NSString 类进行了无缝桥接。Foundation 还对 String 进行扩展使其可以访问 NSString 类型中定义的方法。这意味着调用那些 NSString 的方法,你无需进行任何类型转换。<br>
字符串字面量<br>
字符串字面量是由一对双引号包裹着的具有固定顺序的字符集。<br>字符串字面量可以用于为常量和变量提供初始值:<br>let someString = "Some string literal value"<br>注意,Swift 之所以推断 someString 常量为字符串类型,是因为它使用了字面量方式进行初始化。<br>
多行字符串字面量<br>
一个多行字符串字面量包含了所有的在开启和关闭引号中的行。这个字符从开启引号(""")之后的第一行开始,到关闭引号(""")之前为止。这就意味着字符串开启引号之后(""")或者结束引号(""")之前都没有换行符号。
如果你的代码中,多行字符串字面量包含换行符的话,则多行字符串字面量中也会包含换行符。如果你想换行,以便加强代码的可读性,但是你又不想在你的多行字符串字面量中出现换行符的话,你可以用在行尾写一个反斜杠(\)作为续行符。<br>let softWrappedQuotation = """<br>The White Rabbit put on his spectacles. "Where shall I begin, \<br>please your Majesty?" he asked.<br><br>"Begin at the beginning," the King said gravely, "and go on \<br>till you come to the end; then stop."<br>"""<br>
为了让一个多行字符串字面量开始和结束于换行符,请将换行写在第一行和最后一行,例如:let lineBreaks = """<br><br>This string starts with a line break.<br>It also ends with a line break.<br><br>"""
字符串字面量的特殊字符<br>
字符串字面量可以包含以下特殊字符:<br>转义字符 \0(空字符)、\\(反斜线)、\t(水平制表符)、\n(换行符)、\r(回车符)、\"(双引号)、\'(单引号)。<br>Unicode 标量,写成 \u{n}(u 为小写),其中 n 为任意一到八位十六进制数且可用的 Unicode 位码。<br>let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"<br>// "Imageination is more important than knowledge" - Enistein<br>let dollarSign = "\u{24}" // $,Unicode 标量 U+0024<br>let blackHeart = "\u{2665}" // ♥,Unicode 标量 U+2665<br>let sparklingHeart = "\u{1F496}" // 💖,Unicode 标量 U+1F496<br>
由于多行字符串字面量使用了三个双引号,而不是一个,所以你可以在多行字符串字面量里直接使用双引号(")而不必加上转义符(\)。要在多行字符串字面量中使用 """ 的话,就需要使用至少一个转义符(\):<br>let threeDoubleQuotes = """<br>Escaping the first quote \"""<br>Escaping all three quotes \"\"\"<br>"""<br>
扩展字符串分隔符<br>
您可以将字符串文字放在扩展分隔符中,这样字符串中的特殊字符将会被直接包含而非转义后的效果。将字符串放在引号(")中并用数字符号(#)括起来。例如,打印字符串文字 #"Line 1 \nLine 2"# 会打印换行符转义序列(\n)而不是给文字换行。<br>如果需要字符串文字中字符的特殊效果,请匹配转义字符(\)后面添加与起始位置个数相匹配的 # 符。 例如,如果您的字符串是 #"Line 1 \nLine 2"# 并且您想要换行,则可以使用 #"Line 1 \#nLine 2"# 来代替。 同样,###"Line1 \###nLine2"### 也可以实现换行效果。<br>
可以使用扩展分隔符在多行字符串中包含文本 """,覆盖原有的结束文字的默认行为。例如:<br>let threeMoreDoubleQuotationMarks = #"""<br>Here are three more double quotes: """<br>"""#<br>
初始化空字符串<br>
var emptyString = "" // 空字符串字面量<br>var anotherEmptyString = String() // 初始化方法<br>// 两个字符串均为空并等价。
可以通过检查 Bool 类型的 isEmpty 属性来判断该字符串是否为空:<br>if emptyString.isEmpty {<br> print("Nothing to see here")<br>}<br>// 打印输出:“Nothing to see here”<br>
字符串可变性<br>
可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改:<br>var variableString = "Horse"<br>variableString += " and carriage"<br>// variableString 现在为 "Horse and carriage"<br><br>let constantString = "Highlander"<br>constantString += " and another Highlander"<br>// 这会报告一个编译错误(compile-time error) - 常量字符串不可以被修改。<br>
字符串是值类型<br>
在 Swift 中 String 类型是值类型。如果你创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。Swift 默认拷贝字符串的行为保证了在函数/方法向你传递的字符串所属权属于你,无论该值来自于哪里。你可以确信传递的字符串不会被修改,除非你自己去修改它。<br>在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着你将字符串作为值类型的同时可以获得极高的性能。
for character in "Dog!🐶" {<br> print(character)<br>}<br>// D// o// g// !// 🐶
字符串可以通过传递一个值类型为 Character 的数组作为自变量来初始化:<br>
let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]<br>let catString = String(catCharacters)<br>print(catString)<br>// 打印输出:“Cat!🐱”
连接字符串和字符<br>
可以通过加法赋值运算符(+=)将一个字符串添加到一个已经存在字符串变量上:
let string2 = " there"<br>var instruction = "look over"<br>instruction += string2<br>// instruction 现在等于 "look over there"<br>
可以用 append() 方法将一个字符附加到一个字符串变量的尾部:
let exclamationMark: Character = "!"<br>welcome.append(exclamationMark)<br>// welcome 现在等于 "hello there!"
注意<br>你不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
如果你需要使用多行字符串字面量来拼接字符串,并且你需要字符串每一行都以换行符结尾,包括最后一行:
let badStart = """<br>one<br>two<br>"""<br>let end = """<br>three<br>"""<br>print(badStart + end)<br>// 打印两行:<br>// one<br>// twothree<br><br>let goodStart = """<br>one<br>two<br><br>"""<br>print(goodStart + end)<br>// 打印三行:<br>// one<br>// two<br>// three
字符串插值<br>
字符串字面量和多行字符串字面量都可以使用字符串插值。你插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:<br>let multiplier = 3<br>let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"<br>// message 是 "3 times 2.5 is 7.5"<br>
如果要在使用扩展字符串分隔符的字符串中使用字符串插值,需要在反斜杠后面添加与开头和结尾数量相同扩展字符串分隔符。例如:<br>print(#"6 times 7 is \#(6 * 7)."#)<br>// 打印 "6 times 7 is 42."<br>
注意<br>插值字符串中写在括号中的表达式不能包含非转义反斜杠(\),并且不能包含回车或换行符。不过,插值字符串可以包含其他字面量
Unicode<br>
Swift 的 String 类型是基于 Unicode 标量 建立的。Unicode 标量是对应字符或者修饰符的唯一的 21 位数字,能够以其他三种 Unicode 兼容的方式访问字符串的值:<br>UTF-8 代码单元集合(利用字符串的 utf8 属性进行访问)<br>UTF-16 代码单元集合(利用字符串的 utf16 属性进行访问)<br>21 位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式(利用字符串的 unicodeScalars 属性进行访问)<br>
let dogString = "Dog‼🐶"<br>for codeUnit in dogString.utf8 {<br>print("\(codeUnit) ", terminator: "")<br>}<br>print("")<br>// 68 111 103 226 128 188 240 159 144 182
for codeUnit in dogString.utf16 {<br>print("\(codeUnit) ", terminator: "")<br>}<br>print("")<br>// 68 111 103 8252 55357 56374
for scalar in dogString.unicodeScalars {<br>print("\(scalar.value) ", terminator: "")<br>}<br>print("")<br>// 68 111 103 8252 128054
for scalar in dogString.unicodeScalars {<br> print("\(scalar) ")<br>}<br>// D<br>// o<br>// g<br>// ‼<br>// 🐶
计算字符数量<br>
如果想要获得一个字符串中 Character 值的数量,可以使用 count 属性:<br>let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"<br>print("unusualMenagerie has \(unusualMenagerie.count) characters")<br>// 打印输出“unusualMenagerie has 40 characters”<br>
字符串索引<br>
let greeting = "Guten Tag!"<br>greeting[greeting.startIndex]<br>// G<br>greeting[greeting.index(before: greeting.endIndex)]<br>// !<br>greeting[greeting.index(after: greeting.startIndex)]<br>// u<br>let index = greeting.index(greeting.startIndex, offsetBy: 7)<br>greeting[index]<br>// a
注意<br>你可以使用 startIndex 和 endIndex 属性或者 index(before:) 、index(after:) 和 index(_:offsetBy:) 方法在任意一个确认的并遵循 Collection 协议的类型里面
插入和删除<br>
调用 insert(_:at:) 方法可以在一个字符串的指定索引插入一个字符,调用 insert(contentsOf:at:) 方法可以在一个字符串的指定索引插入一个段字符串。<br>var welcome = "hello"<br>welcome.insert("!", at: welcome.endIndex)<br>// welcome 变量现在等于 "hello!"<br>welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex))<br>// welcome 变量现在等于 "hello there!"<br>
调用 remove(at:) 方法可以在一个字符串的指定索引删除一个字符,调用 removeSubrange(_:) 方法可以在一个字符串的指定索引删除一个子字符串。<br>welcome.remove(at: welcome.index(before: welcome.endIndex))<br>// welcome 现在等于 "hello there"<br>let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex<br>welcome.removeSubrange(range)<br>// welcome 现在等于 "hello"<br>
注意<br>你可以使用 insert(_:at:)、insert(contentsOf:at:)、remove(at:) 和 removeSubrange(_:) 方法在任意一个确认的并遵循 RangeReplaceableCollection 协议的类型里面,如上文所示是使用在 String 中,你也可以使用在 Array、Dictionary 和 Set 中。
字符串索引<br>
你从字符串中获取一个子字符串 —— 例如,使用下标或者 prefix(_:) 之类的方法 —— 就可以得到一个 Substring 的实例,而非另外一个 String。当你需要长时间保存结果时,就把 Substring 转化为 String 的实例:<br>let greeting = "Hello, world!"<br>let index = greeting.firstIndex(of: ",") ?? greeting.endIndex<br>let beginning = greeting[..<index]<br>// beginning 的值为 "Hello"<br>// 把结果转化为 String 以便长期存储。<br>let newString = String(beginning)<br>
比较字符串
字符串/字符相等
字符串/字符可以用等于操作符(==)和不等于操作符(!=),详细描述在 比较运算符:<br>let quotation = "We're a lot alike, you and I."<br>let sameQuotation = "We're a lot alike, you and I."<br>if quotation == sameQuotation {<br> print("These two strings are considered equal")<br>}<br>// 打印输出“These two strings are considered equal”<br>
前缀/后缀相等
通过调用字符串的 hasPrefix(_:)/hasSuffix(_:) 方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个 String 类型的参数,并返回一个布尔值。hasPrefix(_:) 和 hasSuffix(_:) 方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等<br>let romeoAndJuliet = [<br> "Act 1 Scene 1: Verona, A public place",<br> "Act 1 Scene 2: Capulet's mansion",<br> "Act 1 Scene 3: A room in Capulet's mansion",<br> "Act 1 Scene 4: A street outside Capulet's mansion",<br> "Act 1 Scene 5: The Great Hall in Capulet's mansion",<br>"Act 2 Scene 1: Outside Capulet's mansion",<br> "Act 2 Scene 2: Capulet's orchard",<br> "Act 2 Scene 3: Outside Friar Lawrence's cell",<br> "Act 2 Scene 4: A street in Verona",<br> "Act 2 Scene 5: Capulet's mansion",<br> "Act 2 Scene 6: Friar Lawrence's cell"<br>]<br>var act1SceneCount = 0<br>for scene in romeoAndJuliet {<br> if scene.hasPrefix("Act 1 ") {<br> act1SceneCount += 1<br> }<br>}<br>print("There are \(act1SceneCount) scenes in Act 1")<br>// 打印输出“There are 5 scenes in Act 1”<br><br>可以调用 hasPrefix(_:) 方法来计算话剧中第一幕的场景数:<br>var mansionCount = 0<br>var cellCount = 0<br>for scene in romeoAndJuliet {<br> if scene.hasSuffix("Capulet's mansion") {<br> mansionCount += 1<br> } else if scene.hasSuffix("Friar Lawrence's cell") {<br> cellCount += 1<br> }<br>}<br>print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")<br>// 打印输出“6 mansion scenes; 2 cell scenes”<br>
集合类型
数组(Arrays)
Swift 的 Array 类型被桥接到 Foundation 中的 NSArray 类。<br>Swift 中数组的完整写法为 Array<Element>,其中 Element 是这个数组中唯一允许存在的数据类型。也可以使用像 [Element] 这样的简单语法。<br><br>
创建一个空数组: <br>var someInts = [Int]()<br>print("someInts is of type [Int] with \(someInts.count) items.")<br>// 打印“someInts is of type [Int] with 0 items.”<br>如果代码上下文中已经提供了类型信息,例如一个函数参数或者一个已经定义好类型的常量或者变量,你可以使用空数组语句创建一个空数组,它的写法很简单:[]<br>someInts.append(3)<br>// someInts 现在包含一个 Int 值<br>someInts = []<br>// someInts 现在是空数组,但是仍然是 [Int] 类型的。
创建一个带有默认值的数组<br>通过两个数组相加创建一个数组<br>
Swift 中的 Array 类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。可以把准备加入新数组的数据项数量(count)和适当类型的初始值(repeating)传入数组构造函数:<br>var threeDoubles = Array(repeating: 0.0, count: 3)<br>// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0]<br>var anotherThreeDoubles = Array(repeating: 2.5, count: 3)<br>// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5]<br>var sixDoubles = threeDoubles + anotherThreeDoubles<br>// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]<br>
用数组字面量构造数组
var shoppingList: [String] = ["Eggs", "Milk"]<br>// shoppingList 已经被构造并且拥有两个初始项。<br>由于 Swift 的类型推断机制,当你用字面量构造拥有相同类型值数组的时候,不必把数组的类型定义清楚。shoppingList 的构造也可以这样写:<br>var shoppingList = ["Eggs", "Milk"]<br>
注意<br>shoppingList 数组被声明为变量(var 关键字创建)而不是常量(let 创建)是因为之后会有更多的数据项被插入其中。
访问和修改数组<br><br>
使用布尔属性 isEmpty 作为一个缩写形式去检查 count 属性是否为 0:<br>if shoppingList.isEmpty {<br> print("The shopping list is empty.")<br>} else {<br> print("The shopping list is not empty.")<br>}<br>// 打印“The shopping list is not empty.”(shoppinglist 不是空的)<br>
可以使用 append(_:) 方法在数组后面添加新的数据项:<br>shoppingList.append("Flour")<br>// shoppingList 现在有3个数据项<br>
除此之外,也可以使用加法赋值运算符(+=)直接将另一个相同类型数组中的数据添加到该数组后面:<br>shoppingList += ["Baking Powder"]<br>// shoppingList 现在有四项了<br>shoppingList += ["Chocolate Spread", "Cheese", "Butter"]<br>// shoppingList 现在有七项了<br>
var firstItem = shoppingList[0]<br>// 第一项是“Eggs”
以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把 "Chocolate Spread"、"Cheese" 和 "Butter" 替换为 "Bananas" 和 "Apples":<br>shoppingList[4...6] = ["Bananas", "Apples"]<br>// shoppingList 现在有6项<br>
通过调用数组的 insert(_:at:) 方法在某个指定索引值之前添加数据项:<br>shoppingList.insert("Maple Syrup", at: 0)<br>// shoppingList 现在有7项<br>// 现在是这个列表中的第一项是“Maple Syrup”<br>
let mapleSyrup = shoppingList.remove(at: 0)<br>// 索引值为0的数据项被移除<br>// shoppingList 现在只有6项,而且不包括 Maple Syrup<br>// mapleSyrup 常量的值等于被移除数据项“Maple Syrup”
Swift 中的数组索引总是从零开始。<br>除了当 count 等于 0 时(说明这是个空数组),最大索引值一直是 count - 1,因为数组都是零起索引。
如果你只想把数组中的最后一项移除,可以使用 removeLast() 方法而不是 remove(at:) 方法来避免需要获取数组的 count 属性。就像后者一样,前者也会返回被移除的数据项:<br>let apples = shoppingList.removeLast()<br>// 数组的最后一项被移除了<br>// shoppingList 现在只有5项,不包括 Apples<br>// apples 常量的值现在等于字符串“Apples”<br>
数组的遍历
for item in shoppingList {<br> print(item)<br>}<br>// Six eggs// Milk// Flour// Baking Powder// Bananas<br><br>for (index, value) in shoppingList.enumerated() {<br> print("Item \(String(index + 1)): \(value)")<br>}<br>// Item 1: Six eggs// Item 2: Milk// Item 3: Flour<br>// Item 4: Baking Powder// Item 5: Bananas<br>
字典(Dictionary)<br>
Swift 的 Dictionary 类型被桥接到 Foundation 的 NSDictionary 类<br>字典是一种无序的集合,它存储的是键值对之间的关系,其所有键的值需要是相同的类型,所有值的类型也需要相同。每个值(value)都关联唯一的键(key),键作为字典中这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。<br>
Swift 的字典使用 Dictionary<Key, Value> 定义,其中 Key 是一种可以在字典中被用作键的类型,Value 是字典中对应于这些键所存储值的数据类型。<br><br>
创建一个空字典
var namesOfIntegers = [Int: String]()<br>// namesOfIntegers 是一个空的 [Int: String] 字典
如果上下文已经提供了类型信息,你可以使用空字典字面量来创建一个空字典,记作 [:] (一对方括号中放一个冒号)<br>namesOfIntegers[16] = "sixteen"<br>// namesOfIntegers 现在包含一个键值对<br>namesOfIntegers = [:]<br>// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典<br>
用字典字面量创建字典
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]<br>airports 字典也可以用这种简短方式定义:<br>var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]<br>
访问和修改字典
和数组一样,可以通过 Dictionary 的只读属性 count 来获取字典的数据项数量:<br>print("The dictionary of airports contains \(airports.count) items.")<br>// 打印“The dictionary of airports contains 2 items.”(这个字典有两个数据项)<br>
使用布尔属性 isEmpty 作为一个缩写形式去检查 count 属性是否为 0:<br>if airports.isEmpty {<br> print("The airports dictionary is empty.")<br>} else {<br> print("The airports dictionary is not empty.")<br>}<br>// 打印“The airports dictionary is not empty.”<br>
你可以通过下标语法来给字典添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值:<br>airports["LHR"] = "London"<br>// airports 字典现在有三个数据项<br>
也可以使用下标语法来改变特定键对应的值<br>airports["LHR"] = "London Heathrow"<br>// “LHR”对应的值被改为“London Heathrow”<br>
updateValue(_:forKey:) 方法会返回对应值类型的可选类型。举例来说:对于存储 String 值的字典,这个函数会返回一个 String? 或者“可选 String”类型的值。如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是 nil :<br>if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {<br> print("The old value for DUB was \(oldValue).")<br>}<br>// 输出“The old value for DUB was Dublin.”<br>if let airportName = airports["DUB"] {<br> print("The name of the airport is \(airportName).")<br>} else {<br> print("That airport is not in the airports dictionary.")<br>}<br>// 打印“The name of the airport is Dublin Airport.”<br>
还可以使用下标语法通过将某个键的对应值赋值为 nil 来从字典里移除一个键值对:<br>airports["APL"] = "Apple Internation"<br>// “Apple Internation”不是真的 APL 机场,删除它<br>airports["APL"] = nil<br>// APL 现在被移除了<br>removeValue(forKey:) 方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有对应值的情况下返回 nil:<br>if let removedValue = airports.removeValue(forKey: "DUB") {<br> print("The removed airport's name is \(removedValue).")<br>} else {<br> print("The airports dictionary does not contain a value for DUB.")<br>}<br>// 打印“The removed airport's name is Dublin Airport.”<br>
字典遍历
可以使用 for-in 循环来遍历某个字典中的键值对。每一个字典中的数据项都以 (key, value) 元组形式返回,并且可以使用临时常量或者变量来分解这些元组:
for (airportCode, airportName) in airports {<br> print("\(airportCode): \(airportName)")<br>}<br>// YYZ: Toronto Pearson<br>// LHR: London Heathrow
for airportCode in airports.keys {<br> print("Airport code: \(airportCode)")<br>}<br>// Airport code: YYZ<br>// Airport code: LHR<br><br>for airportName in airports.values {<br> print("Airport name: \(airportName)")<br>}<br>// Airport name: Toronto Pearson<br>// Airport name: London Heathrow
let airportCodes = [String](airports.keys)<br>// airportCodes 是 ["YYZ", "LHR"]<br><br>let airportNames = [String](airports.values)<br>// airportNames 是 ["Toronto Pearson", "London Heathrow"]
Swift 的 Dictionary 是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的 keys 或 values 属性使用 sorted() 方法。
集合(Set)<br>
注意 Swift 的 Set 类型被桥接到 Foundation 中的 NSSet 类。<br>集合用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。注意 Swift 的 Set 类型被桥接到 Foundation 中的 NSSet 类。Swift 的所有基本类型(比如 String、Int、Double 和 Bool)默认都是可哈希化的,可以作为集合值的类型或者字典键的类型。没有关联值的枚举成员值(在 枚举 有讲述)默认也是可哈希化的。<br>
Swift 中的集合类型被写为 Set<Element>,这里的 Element 表示集合中允许存储的类型。和数组不同的是,集合没有等价的简化形式。<br>
创建和构造一个空的集合
var letters = Set<Character>()<br>print("letters is of type Set<Character> with \(letters.count) items.")<br>// 打印“letters is of type Set<Character> with 0 items.”<br>此外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,你可以通过一个空的数组字面量创建一个空的集合:<br>letters.insert("a")<br>// letters 现在含有1个 Character 类型的值<br>letters = []<br>// letters 现在是一个空的 Set,但是它依然是 Set<Character> 类型<br>
用数组字面量创建集合
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]<br>// favoriteGenres 被构造成含有三个初始值的集合<br><br>favoriteGenres 的构造形式可以采用简化的方式代替:<br>var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]<br>
访问和修改一个集合
print("I have \(favoriteGenres.count) favorite music genres.")<br>// 打印“I have 3 favorite music genres.”<br>if favoriteGenres.isEmpty {<br> print("As far as music goes, I'm not picky.")<br>} else {<br> print("I have particular music preferences.")<br>}<br>// 打印“I have particular music preferences.”<br>
if let removedGenre = favoriteGenres.remove("Rock") {<br> print("\(removedGenre)? I'm over it.")<br>} else {<br> print("I never much cared for that.")<br>}<br>// 打印“Rock? I'm over it.”
使用 contains(_:) 方法去检查集合中是否包含一个特定的值:<br>if favoriteGenres.contains("Funk") {<br> print("I get up on the good foot.")<br>} else {<br> print("It's too funky in here.")<br>}<br>// 打印“It's too funky in here.”<br>
遍历一个集合
for genre in favoriteGenres {<br> print("\(genre)")<br>}<br>// Classical// Jazz// Hip hop
Swift 的 Set 类型没有确定的顺序,为了按照特定顺序来遍历一个集合中的值可以使用 sorted() 方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符 < 对元素进行比较的结果来确定。
for genre in favoriteGenres.sorted() {<br> print("\(genre)")<br>}<br>// Classical<br>// Hip hop<br>// Jazz
集合操作
let oddDigits: Set = [1, 3, 5, 7, 9]<br>let evenDigits: Set = [0, 2, 4, 6, 8]<br>let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]<br><br>oddDigits.union(evenDigits).sorted()<br>// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]<br>oddDigits.intersection(evenDigits).sorted()<br>// []<br>oddDigits.subtracting(singleDigitPrimeNumbers).sorted()<br>// [1, 9]<br>oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()<br>// [1, 2, 9]
集合成员关系和相等
使用“是否相等”运算符(==)来判断两个集合包含的值是否全部相同。<br>使用 isSubset(of:) 方法来判断一个集合中的所有值是否也被包含在另外一个集合中。<br>使用 isSuperset(of:) 方法来判断一个集合是否包含另一个集合中所有的值。<br>使用 isStrictSubset(of:) 或者 isStrictSuperset(of:) 方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。<br>使用 isDisjoint(with:) 方法来判断两个集合是否不含有相同的值(是否没有交集)。<br><br>let houseAnimals: Set = ["🐶", "🐱"]<br>let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]<br>let cityAnimals: Set = ["🐦", "🐭"]<br><br>houseAnimals.isSubset(of: farmAnimals)<br>// true<br>farmAnimals.isSuperset(of: houseAnimals)<br>// true<br>farmAnimals.isDisjoint(with: cityAnimals)<br>// true<br>
Swift 语言提供数组(Array)、集合(Set)和字典(Dictionary)三种基本的集合类型用来存储集合数据。数组是有序数据的集。集合是无序无重复数据的集。字典是无序的键值对的集。
流程控制
For-In 循环
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]<br>for (animalName, legCount) in numberOfLegs {<br> print("\(animalName)s have \(legCount) legs")<br>}<br>// cats have 4 legs<br>// ants have 6 legs<br>// spiders have 8 legs
for index in 1...5 {<br> print("\(index) times 5 is \(index * 5)")<br>}<br>// 1 times 5 is 5<br>// 2 times 5 is 10<br>// 3 times 5 is 15<br>// 4 times 5 is 20<br>// 5 times 5 is 25
如果你不需要区间序列内每一项的值,你可以使用下划线(_)替代变量名来忽略这个值:<br>let base = 3<br>let power = 10<br>var answer = 1<br>for _ in 1...power {<br> answer *= base<br>}<br>print("\(base) to the power of \(power) is \(answer)")<br>// 输出“3 to the power of 10 is 59049”<br>
let minutes = 60<br>for tickMark in 0..<minutes {<br> // 每一分钟都渲染一个刻度线(60次)<br>}
一些用户可能在其 UI 中可能需要较少的刻度。他们可以每 5 分钟作为一个刻度。使用 stride(from:to:by:) 函数跳过不需要的标记。<br>let minuteInterval = 5<br>for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {<br> // 每5分钟渲染一个刻度线(0, 5, 10, 15 ... 45, 50, 55)<br>}<br>
可以在闭区间使用 stride(from:through:by:) 起到同样作用:<br>let hours = 12<br>let hourInterval = 3<br>for tickMark in stride(from: 3, through: hours, by: hourInterval) {<br> // 每3小时渲染一个刻度线(3, 6, 9, 12)<br>}<br>
while循环<br>
while 循环会一直运行一段语句直到条件变成 false。这类循环适合使用在第一次迭代前,迭代次数未知的情况下。Swift 提供两种 while 循环形式:<br>while 循环,每次在循环开始时计算条件是否符合;<br>repeat-while 循环,每次在循环结束时计算条件是否符合。
While
while 循环从计算一个条件开始。如果条件为 true,会重复运行一段语句,直到条件变为 false。一般格式:<br>while condition {<br> statements<br>}<br>
Repeat-While
while 循环的另外一种形式是 repeat-while,它和 while 的区别是在判断循环条件之前,先执行一次循环的代码块。然后重复循环直到条件为 false。注意<br>Swift 语言的 repeat-while 循环和其他语言中的 do-while 循环是类似的。repeat-while 循环的一般格式:<br>repeat {<br> statements<br>} while condition<br>
应用场景:<br>let finalSquare = 25<br>var board = [Int](repeating: 0, count: finalSquare + 1)<br>var square = 0<br>var diceRoll = 0<br>while square < finalSquare {<br> // 掷骰子<br> diceRoll += 1<br> if diceRoll == 7 { diceRoll = 1 }<br> // 根据点数移动<br> square += diceRoll<br> if square < board.count {<br> // 如果玩家还在棋盘上,顺着梯子爬上去或者顺着蛇滑下去<br> square += board[square]<br> }<br>}<br>print("Game over!")<br><br>repeat {<br> // 顺着梯子爬上去或者顺着蛇滑下去<br> square += board[square]<br> // 掷骰子<br> diceRoll += 1<br> if diceRoll == 7 { diceRoll = 1 }<br> // 根据点数移动<br> square += diceRoll<br>} while square < finalSquare<br>print("Game over!")<br>
条件语句<br>
Swift 提供两种类型的条件语句:if 语句和 switch 语句。通常,当条件较为简单且可能的情况很少时,使用 if 语句。而 switch 语句更适用于条件较复杂、有更多排列组合的时候。并且 switch 在需要用到模式匹配(pattern-matching)的情况下会更有用。
temperatureInFahrenheit = 90<br>if temperatureInFahrenheit <= 32 {<br> print("It's very cold. Consider wearing a scarf.")<br>} else if temperatureInFahrenheit >= 86 {<br> print("It's really warm. Don't forget to wear sunscreen.")<br>} else {<br> print("It's not that cold. Wear a t-shirt.")<br>}<br>// 输出“It's really warm. Don't forget to wear sunscreen.”
switch some value to consider {<br>case value 1:<br> respond to value 1<br>case value 2,<br> value 3:<br> respond to value 2 or 3<br>default:<br> otherwise, do something else<br>}
区间匹配
let approximateCount = 62<br>let countedThings = "moons orbiting Saturn"<br>let naturalCount: String<br>switch approximateCount {<br>case 0:<br> naturalCount = "no"<br>case 1..<5:<br> naturalCount = "a few"<br>case 5..<12:<br> naturalCount = "several"<br>case 12..<100:<br> naturalCount = "dozens of"<br>case 100..<1000:<br> naturalCount = "hundreds of"<br>default:<br> naturalCount = "many"<br>}<br>print("There are \(naturalCount) \(countedThings).")<br>// 输出“There are dozens of moons orbiting Saturn.”
元组
let somePoint = (1, 1)<br>switch somePoint {<br>case (0, 0):<br> print("\(somePoint) is at the origin")<br>case (_, 0):<br> print("\(somePoint) is on the x-axis")<br>case (0, _):<br> print("\(somePoint) is on the y-axis")<br>case (-2...2, -2...2):<br> print("\(somePoint) is inside the box")<br>default:<br> print("\(somePoint) is outside of the box")<br>}<br>// 输出“(1, 1) is inside the box”
Where
let yetAnotherPoint = (1, -1)<br>switch yetAnotherPoint {<br>case let (x, y) where x == y:<br> print("(\(x), \(y)) is on the line x == y")<br>case let (x, y) where x == -y:<br> print("(\(x), \(y)) is on the line x == -y")<br>case let (x, y):<br> print("(\(x), \(y)) is just some arbitrary point")<br>}<br>// 输出“(1, -1) is on the line x == -y”
let someCharacter: Character = "e"<br>switch someCharacter {<br>case "a", "e", "i", "o", "u":<br> print("\(someCharacter) is a vowel")<br>case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",<br> "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":<br> print("\(someCharacter) is a consonant")<br>default:<br> print("\(someCharacter) is not a vowel or a consonant")<br>}<br>// 输出“e is a vowel”
控制转移语句<br>
控制转移语句改变你代码的执行顺序,通过它可以实现代码的跳转。Swift 有五种控制转移语句:<br>continue<br>break<br>fallthrough<br>return<br>throw
Continue
continue 语句告诉一个循环体立刻停止本次循环,重新开始下次循环。就好像在说“本次循环我已经执行完了”,但是并不会离开整个循环体。<br>let puzzleInput = "great minds think alike"<br>var puzzleOutput = ""<br>for character in puzzleInput {<br> switch character {<br> case "a", "e", "i", "o", "u", " ":<br> continue<br> default:<br> puzzleOutput.append(character)<br> }<br>}<br>print(puzzleOutput)<br> // 输出“grtmndsthnklk”<br>
break
break 语句会立刻结束整个控制流的执行。break 可以在 switch 或循环语句中使用,用来提前结束 switch 或循环语句。<br>let numberSymbol: Character = "三" // 简体中文里的数字 3<br>var possibleIntegerValue: Int?<br>switch numberSymbol {<br>case "1", "١", "一", "๑":<br> possibleIntegerValue = 1<br>case "2", "٢", "二", "๒":<br> possibleIntegerValue = 2<br>case "3", "٣", "三", "๓":<br> possibleIntegerValue = 3<br>case "4", "٤", "四", "๔":<br> possibleIntegerValue = 4<br>default:<br> break<br>}<br>if let integerValue = possibleIntegerValue {<br> print("The integer value of \(numberSymbol) is \(integerValue).")<br>} else {<br> print("An integer value could not be found for \(numberSymbol).")<br>}<br>// 输出“The integer value of 三 is 3.”<br>
贯穿(Fallthrough)
注意<br>fallthrough 关键字不会检查它下一个将会落入执行的 case 中的匹配条件。fallthrough 简单地使代码继续连接到下一个 case 中的代码,这和 C 语言标准中的 switch 语句特性是一样的。
使用 fallthrough 关键字来“贯穿”到 default 分支中。<br>let integerToDescribe = 5<br>var description = "The number \(integerToDescribe) is"<br>switch integerToDescribe {<br>case 2, 3, 5, 7, 11, 13, 17, 19:<br> description += " a prime number, and also"<br> fallthrough<br>default:<br> description += " an integer."<br>}<br>print(description)<br>// 输出“The number 5 is a prime number, and also an integer.”<br>
带标签的语句
可以使用标签(statement label)来标记一个循环体或者条件语句,对于一个条件语句,你可以使用 break 加标签的方式,来结束这个被标记的语句。对于一个循环语句,你可以使用 break 或者 continue 加标签,来结束或者继续这条被标记语句的执行。<br>声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字(introducor keyword),并且该标签后面跟随一个冒号。下面是一个针对 while 循环体的标签语法,同样的规则适用于所有的循环体和条件语句。<br> label name: while condition {<br> statements<br> }<br>
let finalSquare = 25<br>var board = [Int](repeating: 0, count: finalSquare + 1)<br>board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02<br>board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08<br>var square = 0<br>var diceRoll = 0<br><br>gameLoop: while square != finalSquare {<br> diceRoll += 1<br> if diceRoll == 7 { diceRoll = 1 }<br> switch square + diceRoll {<br> case finalSquare:<br> // 骰子数刚好使玩家移动到最终的方格里,游戏结束。<br> break gameLoop<br> case let newSquare where newSquare > finalSquare:<br> // 骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子<br> continue gameLoop<br> default:<br> // 合法移动,做正常的处理<br> square += diceRoll<br> square += board[square]<br> }<br>}<br>print("Game over!")<br>
注意<br>如果上述的 break 语句没有使用 gameLoop 标签,那么它将会中断 switch 语句而不是 while 循环。使用 gameLoop 标签清晰的表明了 break 想要中断的是哪个代码块。<br>同时请注意,当调用 continue gameLoop 去跳转到下一次循环迭代时,这里使用 gameLoop 标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以 continue 语句会影响到哪个循环体是没有歧义的。然而,continue 语句使用 gameLoop 标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的 break gameLoop,能够使游戏的逻辑更加清晰和易于理解。
guard 提前退出
像 if 语句一样,guard 的执行取决于一个表达式的布尔值。我们可以使用 guard 语句来要求条件必须为真时,以执行 guard 语句后的代码。不同于 if 语句,一个 guard 语句总是有一个 else 从句,如果条件不为真则执行 else 从句中的代码。
func greet(person: [String: String]) {<br> guard let name = person["name"] else {<br> return<br> }<br><br> print("Hello \(name)!")<br><br> guard let location = person["location"] else {<br> print("I hope the weather is nice near you.")<br> return<br> }<br><br> print("I hope the weather is nice in \(location).")<br>}<br><br>greet(person: ["name": "John"])<br>// 输出“Hello John!”<br>// 输出“I hope the weather is nice near you.”<br>greet(person: ["name": "Jane", "location": "Cupertino"])<br>// 输出“Hello Jane!”<br>// 输出“I hope the weather is nice in Cupertino.”
如果 guard 语句的条件被满足,则继续执行 guard 语句大括号后的代码。将变量或者常量的可选绑定作为 guard 语句的条件,都可以保护 guard 语句后面的代码。<br>如果条件不被满足,在 else 分支上的代码就会被执行。这个分支必须转移控制以退出 guard 语句出现的代码段。它可以用控制转移语句如 return、break、continue 或者 throw 做这件事,或者调用一个不返回的方法或函数,例如 fatalError()。<br>相比于可以实现同样功能的 if 语句,按需使用 guard 语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在 else 块中,它可以使你在紧邻条件判断的地方,处理违规的情况。
if #available(iOS 10, macOS 10.12, *) {<br> // 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API<br>} else {<br> // 使用先前版本的 iOS 和 macOS 的 API<br>}
函数
函数的定义与调用
当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入,称为参数,也可以定义某种类型的值作为函数执行结束时的输出,称为返回类型。<br>func greetAgain(person: String) -> String {<br> return "Hello again, " + person + "!"<br>}<br>print(greetAgain(person: "Anna"))<br>// 打印“Hello again, Anna!”<br>
函数参数与返回值
无参数函数
func sayHelloWorld() -> String {<br> return "hello, world"<br>}<br>print(sayHelloWorld())<br>// 打印“hello, world”
多参数函数
func greet(person: String, alreadyGreeted: Bool) -> String {<br> if alreadyGreeted {<br> return greetAgain(person: person)<br> } else {<br> return greet(person: person)<br> }<br>}<br>print(greet(person: "Tim", alreadyGreeted: true))<br>// 打印“Hello again, Tim!”
无返回值函数
func greet(person: String) {<br> print("Hello, \(person)!")<br>}<br>greet(person: "Dave")<br>// 打印“Hello, Dave!”<br><br>
这个函数不需要返回值,所以这个函数的定义中没有返回箭头(->)和返回类型。<br>注意<br>严格地说,即使没有明确定义返回值,该 greet(Person:) 函数仍然返回一个值。没有明确定义返回类型的函数的返回一个 Void 类型特殊值,该值为一个空元组,写成 ()。<br>
调用函数时,可以忽略该函数的返回值:<br>func printAndCount(string: String) -> Int {<br> print(string)<br> return string.count<br>}<br>func printWithoutCounting(string: String) {<br> let _ = printAndCount(string: string)<br>}<br>printAndCount(string: "hello, world")<br>// 打印“hello, world”,并且返回值 12<br>printWithoutCounting(string: "hello, world")<br>// 打印“hello, world”,但是没有返回任何值<br>
第一个函数 printAndCount(string:),输出一个字符串并返回 Int 类型的字符数。第二个函数 printWithoutCounting(string:) 调用了第一个函数,但是忽略了它的返回值。当第二个函数被调用时,消息依然会由第一个函数输出,但是返回值不会被用到。<br>注意<br>返回值可以被忽略,但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,将导致编译时错误。
多重返回值函数
func minMax(array: [Int]) -> (min: Int, max: Int) {<br> var currentMin = array[0]<br> var currentMax = array[0]<br> for value in array[1..<array.count] {<br> if value < currentMin {<br> currentMin = value<br> } else if value > currentMax {<br> currentMax = value<br> }<br> }<br> return (currentMin, currentMax)<br>}
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])<br>print("min is \(bounds.min) and max is \(bounds.max)")<br>// 打印“min is -6 and max is 109”
可选元组返回类型
如果函数返回的元组类型有可能整个元组都“没有值”,你可以使用可选的 元组返回类型反映整个元组可以是 nil 的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如 (Int, Int)? 或 (String, Int, Bool)?<br>注意<br>可选元组类型如 (Int, Int)? 与元组包含可选类型如 (Int?, Int?) 是不同的。可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。
func minMax(array: [Int]) -> (min: Int, max: Int)? {<br> if array.isEmpty { return nil }<br> var currentMin = array[0]<br> var currentMax = array[0]<br> for value in array[1..<array.count] {<br> if value < currentMin {<br> currentMin = value<br> } else if value > currentMax {<br> currentMax = value<br> }<br> }<br> return (currentMin, currentMax)<br>}
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {<br> print("min is \(bounds.min) and max is \(bounds.max)")<br>}<br>// 打印“min is -6 and max is 109”
隐式返回的函数
如果一个函数的整个函数体是一个单行表达式,这个函数可以隐式地返回这个表达式。举个例子,以下的函数有着同样的作用:<br><br>func greeting(for person: String) -> String {<br> "Hello, " + person + "!"<br>}<br>print(greeting(for: "Dave"))<br>// 打印 "Hello, Dave!"<br><br>func anotherGreeting(for person: String) -> String {<br> return "Hello, " + person + "!"<br>}<br>print(anotherGreeting(for: "Dave"))<br>// 打印 "Hello, Dave!"<br>
greeting(for:) 函数的完整定义是打招呼内容的返回,这就意味着它能使用隐式返回这样更简短的形式。anothergreeting(for:) 函数返回同样的内容,却因为 return 关键字显得函数更长。任何一个可以被写成一行 return 语句的函数都可以忽略 return。
函数参数标签和参数名称
每个函数参数都有一个参数标签(argument label)以及一个参数名称(parameter name)。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。<br>func someFunction(firstParameterName: Int, secondParameterName: Int) {<br> // 在函数体内,firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值<br>}<br>someFunction(firstParameterName: 1, secondParameterName: 2)<br>
指定参数标签
你可以在参数名称前指定它的参数标签,中间以空格分隔:<br>func someFunction(argumentLabel parameterName: Int) {<br> // 在函数体内,parameterName 代表参数值<br>}
这个版本的 greet(person:) 函数,接收一个人的名字和他的家乡,并且返回一句问候:<br>func greet(person: String, from hometown: String) -> String {<br> return "Hello \(person)! Glad you could visit from \(hometown)."<br>}<br>print(greet(person: "Bill", from: "Cupertino"))<br>// 打印“Hello Bill! Glad you could visit from Cupertino.”
忽略参数标签
如果你不希望为某个参数添加一个标签,可以使用一个下划线(_)来代替一个明确的参数标签。如果一个参数有一个标签,那么在调用的时候必须使用标签来标记这个参数。<br>func someFunction(_ firstParameterName: Int, secondParameterName: Int) {<br> // 在函数体内,firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值<br>}<br>someFunction(1, secondParameterName: 2)<br>
默认参数值
将不带有默认值的参数放在函数参数列表的最前。一般来说,没有默认值的参数更加的重要,将不带默认值的参数放在最前保证在函数调用时,非默认参数的顺序是一致的,同时也使得相同的函数在不同情况下调用时显得更为清晰。
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {<br> // 如果你在调用时候不传第二个参数,parameterWithDefault 会值为 12 传入到函数体中。<br>}<br>someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault = 6<br>someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12
可变参数
一个可变参数(variadic parameter)可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(...)的方式来定义可变参数。<br>可变参数的传入值在函数体中变为此类型的一个数组。例如,一个叫做 numbers 的 Double... 型可变参数,在函数体内可以当做一个叫 numbers 的 [Double] 型的数组常量。<br>
func arithmeticMean(_ numbers: Double...) -> Double {<br> var total: Double = 0<br> for number in numbers {<br> total += number<br> }<br> return total / Double(numbers.count)<br>}<br>arithmeticMean(1, 2, 3, 4, 5)<br>// 返回 3.0, 是这 5 个数的平均数。<br>arithmeticMean(3, 8.25, 18.75)<br>// 返回 10.0, 是这 3 个数的平均数。
注意<br>一个函数最多只能拥有一个可变参数。
输入输出参数
如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。<br>定义一个输入输出参数时,在参数定义前加 inout 关键字。一个 输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {<br> let temporaryA = a<br> a = b<br> b = temporaryA<br>}<br><br>var someInt = 3<br>var anotherInt = 107<br>swapTwoInts(&someInt, &anotherInt)<br>print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")<br>// 打印“someInt is now 107, and anotherInt is now 3”<br>
只能传递变量给输入输出参数。你不能传入常量或者字面量,因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 & 符,表示这个值可以被函数修改。<br>注意<br>输入输出参数不能有默认值,而且可变参数不能用 inout 标记。
注意<br>输入输出参数和返回值是不一样的。上面的 swapTwoInts 函数并没有定义任何返回值,但仍然修改了 someInt 和 anotherInt 的值。输入输出参数是函数对函数体外产生影响的另一种方式。
函数类型
每个函数都有种特定的函数类型,函数的类型由函数的参数类型和返回类型组成。
这个例子中定义了两个简单的数学函数:addTwoInts 和 multiplyTwoInts。这两个函数都接受两个 Int 值, 返回一个 Int 值。这两个函数的类型是 (Int, Int) -> Int,可以解读为:<br>“这个函数类型有两个 Int 型的参数并返回一个 Int 型的值”。<br>func addTwoInts(_ a: Int, _ b: Int) -> Int {<br> return a + b<br>}<br>func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {<br> return a * b<br>}<br>
下面是另一个例子,一个没有参数,也没有返回值的函数:<br>func printHelloWorld() {<br> print("hello, world")<br>}
使用函数类型
在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它:<br>var mathFunction: (Int, Int) -> Int = addTwoInts<br>这段代码可以被解读为:<br>”定义一个叫做 mathFunction 的变量,类型是‘一个有两个 Int 型的参数并返回一个 Int 型的值的函数’,并让这个新变量指向 addTwoInts 函数”。<br>
print("Result: \(mathFunction(2, 3))")<br>// Prints "Result: 5"
mathFunction = multiplyTwoInts<br>print("Result: \(mathFunction(2, 3))")<br>// Prints "Result: 6"
就像其他类型一样,当赋值一个函数给常量或变量时,你可以让 Swift 来推断其函数类型:<br>let anotherMathFunction = addTwoInts<br>// anotherMathFunction 被推断为 (Int, Int) -> Int 类型<br>
函数类型作为参数类型
你可以用 (Int, Int) -> Int 这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现留给函数的调用者来提供。
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {<br> print("Result: \(mathFunction(a, b))")<br>}<br>printMathResult(addTwoInts, 3, 5)<br>// 打印“Result: 8”
这个例子定义了 printMathResult(_:_:_:) 函数,它有三个参数:第一个参数叫 mathFunction,类型是 (Int, Int) -> Int,你可以传入任何这种类型的函数;第二个和第三个参数叫 a 和 b,它们的类型都是 Int,这两个值作为已给出的函数的输入值。<br>当 printMathResult(_:_:_:) 被调用时,它被传入 addTwoInts 函数和整数 3 和 5。它用传入 3 和 5 调用 addTwoInts,并输出结果:8。<br>printMathResult(_:_:_:) 函数的作用就是输出另一个适当类型的数学函数的调用结果。它不关心传入函数是如何实现的,只关心传入的函数是不是一个正确的类型。这使得 printMathResult(_:_:_:) 能以一种类型安全(type-safe)的方式将一部分功能转给调用者实现
函数类型作为返回类型
你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(->)后写一个完整的函数类型。
func stepForward(_ input: Int) -> Int {<br> return input + 1<br>}<br>func stepBackward(_ input: Int) -> Int {<br> return input - 1<br>}<br>func chooseStepFunction(backward: Bool) -> (Int) -> Int {<br> return backward ? stepBackward : stepForward<br>}<br>var currentValue = 3<br>let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)<br>// moveNearerToZero 现在指向 stepBackward() 函数。<br><br>print("Counting to zero:")<br>// Counting to zero:<br>while currentValue != 0 {<br> print("\(currentValue)... ")<br> currentValue = moveNearerToZero(currentValue)<br>}<br>print("zero!")<br>// 3...<br>// 2...<br>// 1...<br>// zero!<br>
上面这个例子中计算出从 currentValue 逐渐接近到0是需要向正数走还是向负数走。currentValue 的初始值是 3,这意味着 currentValue > 0 为真(true),这将使得 chooseStepFunction(_:) 返回 stepBackward(_:) 函数。一个指向返回的函数的引用保存在了 moveNearerToZero 常量中。<br>现在,moveNearerToZero 指向了正确的函数,它可以被用来数到零:
嵌套函数
你所见到的所有函数都叫全局函数(global functions),它们定义在全局域中。你也可以把函数定义在别的函数体中,称作 嵌套函数(nested functions)。默认情况下,嵌套函数是对外界不可见的,但是可以被它们的外围函数(enclosing function)调用。一个外围函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用。<br>
你可以用返回嵌套函数的方式重写 chooseStepFunction(backward:) 函数:<br>func chooseStepFunction(backward: Bool) -> (Int) -> Int {<br> func stepForward(input: Int) -> Int { return input + 1 }<br> func stepBackward(input: Int) -> Int { return input - 1 }<br> return backward ? stepBackward : stepForward<br>}<br>var currentValue = -4<br>let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)<br>// moveNearerToZero now refers to the nested stepForward() function<br>while currentValue != 0 {<br> print("\(currentValue)... ")<br> currentValue = moveNearerToZero(currentValue)<br>}<br>print("zero!")<br>// -4...<br>// -3...<br>// -2...<br>// -1...<br>// zero!<br>
闭包
Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。<br>闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。<br>在 函数 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:<br>全局函数是一个有名字但不会捕获任何值的闭包<br>嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包<br>闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包<br>
Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:<br>利用上下文推断参数和返回值类型<br>隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字<br>参数名称缩写<br>尾随闭包语法<br>
闭包表达式
排序方法
下面的闭包表达式示例使用 sorted(by:) 方法对一个 String 类型的数组进行字母逆序排序。以下是初始数组:<br>let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]<br>func backward(_ s1: String, _ s2: String) -> Bool {<br> return s1 > s2<br>}<br>var reversedNames = names.sorted(by: backward)<br>// reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]<br>
闭包表达式语法
闭包表达式语法有如下的一般形式:<br>{ (parameters) -> return type in<br> statements<br>}<br>例子展示了之前 backward(_:_:) 函数对应的闭包表达式版本的代码:<br>reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in<br> return s1 > s2<br>})<br>
需要注意的是内联闭包参数和返回值类型声明与 backward(_:_:) 函数类型声明相同。在这两种方式中,都写成了 (s1: String, s2: String) -> Bool。然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。<br>闭包的函数体部分由关键字 in 引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。<br>由于这个闭包的函数体部分如此短,以至于可以将其改写成一行代码:<br>reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )<br>
根据上下文推断类型
因为排序闭包函数是作为 sorted(by:) 方法的参数传入的,Swift 可以推断其参数和返回值的类型。sorted(by:) 方法被一个字符串数组调用,因此其参数必须是 (String, String) -> Bool 类型的函数。这意味着 (String, String) 和 Bool 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
实际上,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,总是能够推断出闭包的参数和返回值类型。这意味着闭包作为函数或者方法的参数时,你几乎不需要利用完整格式构造内联闭包。<br>尽管如此,你仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则我们更鼓励采用完整格式的闭包。而在 sorted(by:) 方法这个例子里,显然闭包的目的就是排序。由于这个闭包是为了处理字符串数组的排序,因此读者能够推测出这个闭包是用于字符串处理的。
单表达式闭包的隐式返回
单行表达式闭包可以通过省略 return 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:<br>reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )<br>在这个例子中,sorted(by:) 方法的参数类型明确了闭包必须返回一个 Bool 类型值。因为闭包函数体只包含了一个单一表达式(s1 > s2),该表达式返回 Bool 类型值,因此这里没有歧义,return 关键字可以省略。<br>
参数名称缩写
Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0,$1,$2 来顺序调用闭包的参数,以此类推。<br>如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:<br>reversedNames = names.sorted(by: { $0 > $1 } )<br>在这个例子中,$0 和 $1 表示闭包中第一个和第二个 String 类型的参数。<br>
运算符方法
Swift 的 String 类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。而这正好与 sorted(by:) 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断找到系统自带的那个字符串函数的实现:<br>reversedNames = names.sorted(by: >)<br>
尾随闭包
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,将这个闭包替换成为尾随闭包的形式很有用。尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
func someFunctionThatTakesAClosure(closure: () -> Void) {<br> // 函数体部分<br>}<br><br>// 以下是不使用尾随闭包进行函数调用<br>someFunctionThatTakesAClosure(closure: {<br> // 闭包主体部分<br>})<br><br>// 以下是使用尾随闭包进行函数调用<br>someFunctionThatTakesAClosure() {<br> // 闭包主体部分<br>}
在 闭包表达式语法 上章节中的字符串排序闭包可以作为尾随包的形式改写在 sorted(by:) 方法圆括号的外面:<br>reversedNames = names.sorted() { $0 > $1 }<br>
如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉:<br>reversedNames = names.sorted { $0 > $1 }<br>
值捕获(capturing)
----
----
闭包是引用类型
----
----
逃逸闭包
----
----
自动闭包
----
枚举
枚举语法
----
----
使用 Switch 语句匹配枚举值
----
枚举成员的遍历
----
关联值
----
原始值
原始值的隐式赋值
----
使用原始值初始化枚举实例
----
递归枚举
----
类和结构体
结构体和类对比
类型定义的语法
----
结构体和类的实例
----
属性访问
----
结构体类型的成员逐一构造器
----
结构体和枚举是值类型
----
类是引用类型
恒等运算符
----
指针
----
属性
存储属性
常量结构体实例的存储属性
----
延时加载存储属性
----
存储属性和实例变量
----
计算属性
简化 Setter 声明
----
简化 Getter 声明
----
只读计算属性
----
属性观察器
----
----
属性包装器
设置被包装属性的初始值
----
从属性包装器中呈现一个值
----
全局变量和局部变量
----
----
类型属性
类型属性语法
----
获取和设置类型属性的值
----
方法
实例方法(Instance Methods)
self 属性
----
在实例方法中修改值类型
----
在可变方法中给 self 赋值
----
类型方法
----
----
----
下标
下标语法
----
下标用法
----
下标选项
----
类型下标
----
继承
定义一个基类
----
子类生成
----
重写
访问超类的方法,属性及下标
----
重写方法
----
重写属性
----
防止重写
----
子主题
子主题
0 条评论
下一页