9.控制流程
Swift提供了与C语言相似的控制流结构,其中包括用于执行多次任务的for 和 while循环。基于一定的条件下,if 和 switch 语句用于执行不同的分支代码,break 和 continue 语句用于转移执行流到其他代码处。
除了提供类似于 C 语言中的传统 for 递增循环外,Swift 增加了for-in 循环,它使得程序在遍历数组、字典、范围、字符串和其他序列时变得更容易。Swift 语言的 switch 语句比它在C语言中时更强大。即使缺少 break 语句这种常见的 C 语言错误时,使用多个 case 的Switch语句也不会执行到最后一个 case 那里。使用多个 case 能匹配多种不同的模式类型,包括范围匹配、元组和特定类型转换。在含有 case 的 switch 语句中,已经匹配的值将与临时常量或 case 语句块中的变量绑定在一起。而复杂的匹配条件可以用一个 where 语句来表示。
9.1.For 循环
For 循环用于多次执行一组语句,Swift 提供了两种 For 循环:
For-in 循环用于执行一组语句中的每一项,包括范围(range)、序列(sequence)、集合(collection)和进展(progression)。
For 条件递增循环用于执行满足一个特定条件时的一组语句,通常为每次循环结束时增加计数值一次。
9.2.For-In循环
你可以使用for-in循环去迭代集合项,例如数字范围,数组项或者字符串中的字符。
下面的例子打印了一些5的倍数表:
println("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
被迭代的集合是一个包含1到5的闭区间值,正如在用闭区间操作符(…)。索引值为范围中的第一个值(1),同时循环里的语句将被执行。在这种情况下,循环 里只包含一条语句,它用于打印5的倍数表中的当前值。在这条语句被执行后,索引值将被更新为范围中的第二个值(2),同时println函数再次被调用, 这个过程将持续直到范围中的末尾。
在上述的例子中,索引是一个常数,它在循环每次迭代开始时被自动设定。正如,它使用前可以不用声明,不需要用let关键字来声明,通过在循环中包含来做隐式声明。
注意:索引常量只在循环体内有效,如果你想在循环体之后检查索引值,或者你想让它像变量而不是常量那样工作时,你必须在循环体之前手动声明。
如果你不需要范围中的每一个值,你可以通过在变量名处使用下划线来忽略索引值:
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
println("\(base) to the power of \(power) is \(answer)")
// prints "3 to the power of 10 is 59049"
这个例子计算power的answer次方中的每一个值(上例中,3的10次方),它用3乘以开始值1(就是3的0次方),通过使用从0到9的半封闭循环 来重复这个过程,一共十次。这种计算不需要知道每次循环时的个别计数值-它只需要知道执行循环的准确次数。下划线字符_导致个别值被忽略,同时在每次循环 迭代时,不需要获取当前值。
使用for-in循环来迭代数组中的项:
for name in names {
println("Hello, \(name)! ")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!
你也可以通过获取关键字-值对来迭代字典中的数据,当迭代字典中的数据时,字典中的每一项将以(关键字-值)元组作为返回值。你可以在for-in循环体 中用显示命名常数来分解(关键字-值)元组的数据以供使用,在此种情况下,字典的关键字被分解为animalName,同时字典的值被分解为常量 legCount:
for (animalName, legCount) in numberOfLegs {
println("\(animalName)s have \(legCount) legs")
}
// spiders have 8 legs
// ants have 6 legs
// cats have 4 legs
当往字典中插入数据时,不需要迭代字典中的数据项,因为字典中的内容是无序的。迭代字典中的数据项不能保证它们被检索时的顺序,关于数组和字典的更多内容,请阅集合类型那一部分。
除了数组和字典外,你也可以使用for-in循环去迭代字符串中的字符值:
println(character)
}
// H
// e
// l
// l
// o
9.3.For条件递增
除了 For-in 循环,Swift 提供使用条件判断和递增的For循环样式:
println("index is \(index)")
}
// index is 0
// index is 1
// index is 2
这种循环的格式如下:
statements
}
和 C 语言一样,分号将 For 循环分为 3 个部分,不同的是,Swift 不需用圆括号将他们包括起来。
执行流程如下:
1.循环启动时,初始化表达式(initialization expression)被调用一次,初始化循环所需的常量和变量。
2.条件表达式(condition expression)被调用,如果表达式的结果为false,循环结束,继续执行 for 循环后的代码;如果表达式的结果为true,则执行 for 循环大括号内部的代码(statements)。
3.执行所有语句(statements)之后,执行递增表达式(increment):通常会增加或减少计数器的值,或者根据语句(statements)修改某一个变量。当递增表达式(increment)执行完成后,重复执行第 2 步。如此循环下去。
等同于:
while condition {
statements
increment
}
在初始化表达式中声明的常量和变量(比如 var index = 0)只在 for 循环的生命周期里有效。如果想在循环结束后继续使用 index ,需要要在循环开始之前声明 index。
for index = 0; index < 3; ++index {
println("index is \(index)")
}
// index is 0
// index is 1
// index is 2
println("The loop statements were executed \(index) times")
// prints "The loop statements were executed 3 times"
注意: index 在循环结束后最终的值是 3 而不是 2 。因为最后一次递增表达式 ++index 被调用将 index 设置为 3,导致 index < 3 是false,终止循环。
9.4.While循环
while 循环执行一系列语句直到条件变成 false 。while 循环使用于第一次迭代时迭代值未知的情况下。Swift 提供两种 while 循环形式:
while 循环:在每次循环开始时判断条件是否为 true;
do-while 循环:在每次循环结束时判断条件是否为 true;
9.5.While
while 循环从判断一个循环条件开始:如果条件为true,重复执行一组代码,直到条件变为false。
while 循环的格式:
statements
}
9.6.Do-While
do-while 是while 循环的另一种形式,它和 while 的区别是:先执行一次循环的代码块,再在判断循环条件,然后重复执行循环的代码块直到循环条件为 false 。
do-while 的格式:
statements
} while condition
9.7.条件语句
在不同的条件下执行不同的代码块是非常有用的。要实现这个功能,需要使用条件语句。
Swift 提供两种件语句类型:if语句和switch语句。
当条件比较简单,可能的情况很少时,使用if语句。
当条件比较复杂,可能情况较多时,使用switch语句。
9.8.If
if 语句最简单的形式就是只包含一个条件,当且仅当该条件为 true 时,才执行相关代码:
if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.")
}
// prints "It's very cold. Consider wearing a scarf."
上面的例子判断如果温度小于等于 32 华氏度(水的冰点),则打印一条消息;否则,不打印任何消息,继续执行 if 块后面的代码。
if 语句允许二选一,也就是当条件为 false 时,执行 else 语句:
if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.")
} else {
println("It's not that cold. Wear a t-shirt.")
}
// prints "It's not that cold. Wear a t-shirt."
显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,else 分支就会执行。
你可以把多个 if 语句链接在一起,像下面这样:
if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
println("It's really warm. Don't forget to wear sunscreen.")
} else {
println("It's not that cold. Wear a t-shirt.")
}
// prints "It's really warm. Don't forget to wear sunscreen."
在上面的例子中,else if 语句用于判断是不是特别热。最后的 else 用于打印既不冷也不热时的消息。
实际上,最后的else语句是可选的:
if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
println("It's really warm. Don't forget to wear sunscreen.")
}
9.9.Switch
switch 试图把某个值与若干个模式(pattern)进行匹配。如果第一个模式匹配成功,switch 语句会执行对应的代码。
switch 语句最简单的形式就是把某个值与若干个相同类型的值作匹配:
case value 1 :
respond to value 1
case value 2 , value 3 :
respond to value 2 or 3
default:
otherwise, do something else
}
switch 语句由若干个 case 构成。每个 case 都是一条分支,与if语句类似。switch 语句会决定哪一条分支应该被执行。
switch 语句必须是完整的。这就是说,每一个可能的值都必须至少有一个 case 与之对应。在不能覆盖所有值的情况下,使用默认(default)分支处理,默认分支只能在 switch 语句的最后面。
例子:使用 switch 语句匹配常量 someCharacter 的小写字符:
switch someCharacter {
case "a", "e", "i", "o", "u":
println("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
println("\(someCharacter) is a consonant")
default:
println("\(someCharacter) is not a vowel or a consonant")
}
// prints "e is a vowel"
在这个例子中,第一个 case 分支用于匹配五个元音,第二个 case 分支用于匹配所有的辅音。
其它字符没有必要写 case ,所以使用默认分支来处理,保证了switch 语句的完整性。
9.10.非隐式贯穿
Swift 的 switch 与 C 语言和 Objective-C 是不同的。当匹配的 case 分支中的代码执行完毕后,switch就终止了,不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用 break 语句。这使得 switch 语句更安全、更易用,也避免了因忘记写 break 语句而产生的错误。
注意:当然也可以在 case 分支中的代码执行完毕前 break
switch anotherCharacter {
case "a":
case "A":
println("The letter A")
default:
println("Not the letter A")
}
// this will report a compile-time error
一个 case 可以包含多个模式,用逗号将它们分开(如果太长,可以分行写):
case value 1 ,
value 2 :
statements
}
注意:如果想要贯穿至特定的 case 分支中,使用 fallthrough 语句
9.11.范围匹配
let count = 3_000_000_000_000
let countedThings = "stars in the Milky Way"
var naturalCount: String
switch count {
case 0:
naturalCount = "no"
case 1...3:
naturalCount = "a few"
case 4...9:
naturalCount = "several"
case 10...99:
naturalCount = "tens of"
case 100...999:
naturalCount = "hundreds of"
case 1000...999_999:
naturalCount = "thousands of"
default:
naturalCount = "millions and millions of"
}
println("There are \(naturalCount) \(countedThings).")
// prints "There are millions and millions of stars in the Milky Way."
9.12.元组
switch 语句中可以使用 元组 测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(_)来匹配所有可能的值。
下面的例子展示了如何使用一个(Int, Int)类型的元组来分类下图中的点(x, y):
switch somePoint {
case (0, 0):
println("(0, 0) is at the origin")
case (_, 0):
println("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
println("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
println("(\(somePoint.0), \(somePoint.1)) is inside the box")
default:
println("(\(somePoint.0), \(somePoint.1)) is outside of the box")
}
// prints "(1, 1) is inside the box"
在上面的例子中,switch 语句会判断某个点是否是原点(0, 0),是否在红色的x轴上,是否在黄色y轴上,是否在一个以原点为中心的4x4的矩形里,或者在这个矩形外面。
Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有四个 case。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配case (0, 0),因此剩下的能够匹配(0, 0)的 case 分支都会被忽视掉。
9.13.值绑定
值绑定(value binding):case 分支允许将要匹配的值绑定给临时常量或变量,这些常量或变量在该 case 分支可以被引用。
下面的例子展示了如何在一个(Int, Int)类型的元组中使用值绑定来分类下图中的点(x, y):
switch anotherPoint {
case (let x, 0):
println("on the x-axis with an x value of \(x)")
case (0, let y):
println("on the y-axis with a y value of \(y)")
case let (x, y):
println("somewhere else at (\(x), \(y))")
}
// prints "on the x-axis with an x value of 2"
在上面的例子中,switch 语句会判断某个点是否在红色的 x 轴上,是否在黄色 y 轴上,或者不在坐标轴上。
这三个 case 都声明了常量 x 和 y 的占位符,用于临时获取元组 anotherPoint 的一个或两个值。第一个 case ——case (let x, 0)将匹配一个纵坐标为0的点,并把这个点的横坐标赋给临时的常量x。类似的,第二个 case ——case (0, let y)将匹配一个横坐标为0的点,并把这个点的纵坐标赋给临时的常量y。
一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里引用。在这个例子中,用println进行打印。
请注意,这个switch语句不包含默认分支。这是因为最后一个 case ——case let(x, y) 可以匹配余下所有值的元组。这使得 switch 语句已经完整了,因此不需要默认分支。
在上面的例子中,x 和 y 是常量,这是因为没有必要在其对应的 case 分支中修改它们的值。然而,它们也可以是变量 —— 程序会创建临时变量,并用相应的值初始化它。修改这些变量只会影响其对应的 case 分支。
9.14.Where语句
case 分支模式可以使用 where 语句判断额外的条件。
下面的例子把下图中的点(x, y)进行了分类:
switch yetAnotherPoint {
case let (x, y) where x == y:
println("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
println("(\(x), \(y)) is on the line x == -y")
case let (x, y):
println("(\(x), \(y)) is just some arbitrary point")
}
// prints "(1, -1) is on the line x == -y"
在上面的例子中,switch 语句会判断某个点是否在绿色的对角线 x == y 上,是否在紫色的对角线 x == -y 上,或者不在对角线上。
这三个 case 都声明了常量 x 和 y 占位符,用于临时获取元组 yetAnotherPoint 的两个值。这些常量被用作 where 语句的一部分,从而创建一个动态的过滤器 (filter)。当且仅当 where 语句的条件为 true 时,匹配到的 case 分支才会被执行。
就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,switch 语句就已经完备了,因此不需要再书写默认分支。
9.15.控制转移语句
控制转移语句可以改变代码的执行顺序,通过它可以实现代码的跳转。Swift 有四种控制转移语句。
continue
break
fallthrough
return
后面讲详细讨论 continue、break和fallthrough语句。return 语句将会在函数章节讨论。
9.16.Continue
continue :让循环体立刻停止本次循环,重新开始下一次循环。就好像在说 “本次循环迭代可以完了”,但是并不会离开整个循环体。
注意:在一个 for 条件递增(for-condition-increment)循环体中,在调用 continue 语句后,迭代增量仍然会被计算求值。循环体继续像往常一样工作,仅仅只是循环体中的执行代码会被跳过。
下面的例子把一个小写字符串中的元音字母和空格字符移除,生成一个含义模糊的短句:
var puzzleOutput = ""
for character in puzzleInput {
switch character {
case "a", "e", "i", "o", "u", " ":
continue
default:
puzzleOutput += character
}
}
println(puzzleOutput)
// prints "grtmndsthnklk"
只要匹配到元音字母或者空格字符,就调用 continue 语句,使本次循环结束,然后新开始下次循环。这使 switch 匹配到元音字母和空格字符时不做处理,而不是打印每一个匹配到的字符。
9.17.Break
9.18.从循环中Break
9.19.从Switch中Break
在 switch 代码块中使用 break 时,会立即中断该 switch 代码块的执行,并跳转到 switch 代码块结束(大括号(}))后的第一行代码。
这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的 switch 需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上 break 语句。当那个分支被匹配到时,分支内的 break 语句立即结束 switch 代码块。
注意:当一个switch分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让switch分支达到被忽略的效果。你总是可以使用break来忽略某个分支。
下面的例子通过switch来判断一个Character值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。
var possibleIntegerValue: Int?
switch numberSymbol {
case "1", "١", "一", "๑":
possibleIntegerValue = 1
case "2", "٢", "二", "๒":
possibleIntegerValue = 2
case "3", "٣", "三", "๓":
possibleIntegerValue = 3
case "4", "٤", "四", "๔":
possibleIntegerValue = 4
default:
break
}
if let integerValue = possibleIntegerValue {
println("The integer value of \(numberSymbol) is \(integerValue).")
} else {
println("An integer value could not be found for \(numberSymbol).")
}
// prints "The integer value of 三 is 3."
这个例子检查numberSymbol是否是拉丁,阿拉伯,中文或者泰语中的1到4之一。如果被匹配到,该switch分支语句给Int?类型变量possibleIntegerValue设置一个整数值。
当switch代码块执行完后,接下来的代码通过使用可选绑定来判断possibleIntegerValue是否曾经被设置过值。因为是可选类型的缘 故,possibleIntegerValue有一个隐式的初始值nil,所以仅仅当possibleIntegerValue曾被switch代码块的 前四个分支中的某个设置过一个值时,可选的绑定将会被判定为成功。
在上面的例子中,想要把Character所有的的可能性都枚举出来是不现实的,所以使用default分支来包含所有上面没有匹配到字符的情况。由于这 个default分支不需要执行任何动作,所以它只写了一条break语句。一旦落入到default分支中后,break语句就完成了该分支的所有代码 操作,代码继续向下,开始执行if let语句。
本文转自 http://www.swiftguide.cn/chapter2/05_Control_Flow.html
9.20.贯穿(Fallthrough)
Swift 中的switch不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个switch代码块完成了它的执行。相比之下,C 语言要求你显示的插入break语句到每个switch分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的switch 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
如果你确实需要 C 风格的贯穿(fallthrough)的特性,你可以在每个需要该特性的 case 分支中使用fallthrough关键字。下面的例子使用fallthrough来创建一个数字的描述语句。
Swift 中的switch不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个switch代码块完成了它的执行。相比之下,C 语言要求你显示的插入break语句到每个switch分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的switch 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
如果你确实需要 C 风格的贯穿(fallthrough)的特性,你可以在每个需要该特性的 case 分支中使用fallthrough关键字。下面的例子使用fallthrough来创建一个数字的描述语句。
var description = "The number \(integerToDescribe) is"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
description += " a prime number, and also"
fallthrough
default:
description += " an integer."
}
println(description)
// prints "The number 5 is a prime number, and also an integer."
这个例子定义了一个String类型的变量description并且给它设置了一个初始值。函数使用switch逻辑来判断 integerToDescribe变量的值。当integerToDescribe的值属于列表中的质数之一时,该函数添加一段文字在 description后,来表明这个是数字是一个质数。然后它使用fallthrough关键字来“贯穿”到default分支中。default分支 添加一段额外的文字在description的最后,至此switch代码块执行完了。
如果integerToDescribe的值不属于列表中的任何质数,那么它不会匹配到第一个switch分支。而这里没有其他特别的分支情况,所以integerToDescribe匹配到包含所有的default分支中。
当switch代码块执行完后,使用println函数打印该数字的描述。在这个例子中,数字5被准确的识别为了一个质数。
注意:fallthrough关键字不会检查它下一个将会落入执行的 case 中的匹配条件。fallthrough简单地使代码执行继续连接到下一个 case 中的执行代码,这和 C 语言标准中的switch语句特性是一样的。
本文转自 http://www.swiftguide.cn/chapter2/05_Control_Flow.html
9.21.标签声明
在 Swift 中,你可以在循环体和switch代码块中嵌套循环体和switch代码块来创造复杂的控制流结构。然而,循环体和switch代码块两者都可以使用 break语句来提前结束整个方法体。因此,显示地指明break语句想要终止的是哪个循环体或者switch代码块,会很有用。类似地,如果你有许多嵌 套的循环体,显示指明continue语句想要影响哪一个循环体也会非常有用。
为了实现这个目的,你可以使用标签来标记一个循环体或者switch代码块,当使用break或者continue时,带上这个标签,可以控制该标签代表对象的中断或者执行。
产生一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,并且该标签后面还需带着一个冒号。下面是一个while循环体的语法,同样的规则适用于所有的循环体和switch代码块。
statements
}
下面的例子是在一个带有标签的while循环体中调用break和continue语句,该循环体是前面章节中蛇和梯子的改编版本。这次,游戏增加了一条额外的规则:
为了获胜,你必须刚好落在第 25 个方块中。
如果某次掷骰子使你的移动超出第 25 个方块,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在第 25 个方块中。
游戏的棋盘和之前一样:
值finalSquare、board、square 和 diceRoll 的初始化也和之前一样:
var board = Int[](count: finalSquare + 1, repeatedValue: 0)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0
这个版本的游戏使用while循环体和switch方法块来实现游戏的逻辑。while循环体有一个标签名gameLoop,来表明它是蛇与梯子的主循环。
该while循环体的条件判断语句是while square !=finalSquare,这表明你必须刚好落在方格25中。
if ++diceRoll == 7 { diceRoll = 1 }
switch square + diceRoll {
case finalSquare:
// 到达最后一个方块,游戏结束
break gameLoop
case let newSquare where newSquare > finalSquare:
// 超出最后一个方块,再掷一次骰子
continue gameLoop
default:
// 本次移动有效
square += diceRoll
square += board[square]
}
}
println("Game over!")
如果骰子数刚好使玩家移动到最终的方格里,游戏结束。break gameLoop语句跳转控制去执行while循环体后的第一行代码,游戏结束。
如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。continue gameLoop语句结束本次while循环的迭代,开始下一次循环迭代。
在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动骰子数个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。本次循环迭代结束,控制跳转到while循环体的条件判断语句处,再决定是否能够继续执行下次循环迭代
注意:如果上述 的break语句没有使用gameLoop标签,那么它将会中断switch代码块而不是while循环体。使用gameLoop标签清晰的表明了 break想要中断的是哪个代码块。 同时请注意,当调用continue gameLoop去跳转到下一次循环迭代时,这里使用gameLoop标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以continue语 句会影响到哪个循环体是没有歧义的。然而,continue语句使用gameLoop标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的 break gameLoop,能够使游戏的逻辑更加清晰和易于理解。
本文转自 http://www.swiftguide.cn/chapter2/05_Control_Flow.html
10.函数
给一个函数起一个合适的名字,表明函数用于做什么,通过“调用”名字执行函数。
Swift 的函数语法可以灵活地表达函数的一切:包括最简单的没有参数的C语言风格函数,到复杂的带局部和外部参数的Objective-C语言风格函数。参数可以提供默认值;也可以作为输入输出参数,当函数执行结束的时候,传入的参数值可以被修改。
Swift 的每个函数都有一种类型,包括函数的参数值类型和返回值类型。可以把函数类型当做变量类型一样处理,函数可以作为其他的函数的参数,函数也可以作为其他函数的返回值;函数也可以定义在其他函数中,即函数嵌套,方便在函数内部实现功能封装。
10.1.定义和调用函数
函数定义时,定义一个或者多个命名类型参数,作为函数的输入(函数参数);定义某种类型的值,作为函数的输出(函数返回值)。
每个函数都有函数名,用于描述函数的任务。使用函数时,“调用”函数名,并传入匹配的类型实参即可。函数的实参必须与函数定义时参数的顺序和类型一致。
下面的例子,定义了一个名称为 “greetingForPerson” 的函数,用于对某人返回问候语。调用的时候,传入某人的名字(参数 personName 是 String 类型),就会返回对这个人的问候语(返回值是String 类型)。
let greeting = "Hello, " + personName + "! "
return greeting
所有的这些组成函数的定义,并以 func 关键字开头。指定函数返回类型时,用返回箭头 -> (一个减号和一个右尖括号)紧跟返回类型的方式来表示。
以上描述了函数的定义、函数期望输入什么和函数会返回什么。函数清晰的定义很容易被用在代码中:
// prints "Hello, Anna! "
println(sayHello("Brian"))
// prints "Hello, Brian! "
调用 sayHello 函数时,传给它一个 String 类型的实参;因为这个函数返回一个 String 类型的值,所以 sayHello 可以被包含在 println 函数中,输出 sayHello 的返回值。
在 sayHello 的函数体中,先定义了类型为 String 的 greeting 常量,并把 “给 personName 的问候消息” 赋值给它,然后用 return 关键字将其返回。当 return greeting 被调用时,该函数结束执行并返回 greeting 的当前值。
你可以用不同的参数值多次调用 sayHello 。上面的例子展示的是分别用 "Anna" 和 "Brian" 作为参数值调用的结果,该函数返回了不同的结果。
为了简化这个函数的定义,可以将问候消息的创建和返回写成一句:
return "Hello again, " + personName + "! "
}
println(sayHelloAgain("Anna"))
// prints "Hello again, Anna! "
10.2.函数参数和返回值
在 Swift 中,函数参数和返回值是非常灵活的。你可以定义任何类型的函数:可以是只带一个无名参数的简单函数,也可以是带有复杂的函数参数名称和不同的参数选项的函数。
下面我们就来看看函数参数和返回值的定义...
10.3.多个输入参数
函数可以有多个输入参数:写在圆括号中,并用逗号分隔。
下面这个函数用于计算一个半开区间(结束点减去开始点)内包含多少个数字:
return end - start
}
println(halfOpenRangeLength(1, 10))
// prints "9"
10.4.无参函数
函数可以没有参数。下面这个函数就是一个无参函数,它返回固定的 String 消息:
return "hello, world"
}
println(sayHelloWorld())
// prints "hello, world"
尽管这个函数没有参数,但是定义的时候,函数名后面还是需要一对圆括号。无参函数被调用时,在函数名后面也需要一对圆括号。
10.5.无返回值函数
函数可以没有返回值。下面是 sayHello 函数的另一个版本,叫 waveGoodbye,这个函数直接输出 String 值,而不是返回:
println("Goodbye, \(personName)! ")
}
sayGoodbye("Dave")
// prints "Goodbye, Dave! "
如果函数没有返回值,函数的定义中不需要 箭头(->)和返回类型。
注意:严格上来说,虽然没有定义返回值,但 sayGoodbye 函数仍然返回了值,叫 Void;它是一个特殊的值,是一个空的元组(tuple),没有任何元素,也就是()。
当然,函数被调用时,它的返回值可以被忽略:
println(stringToPrint)
return countElements(stringToPrint)
}
func printWithoutCounting(stringToPrint: String) {
printAndCount(stringToPrint)
}
printAndCount("hello, world")
// prints "hello, world" and returns a value of 12
printWithoutCounting("hello, world")
// prints "hello, world" but does not return a value
函数 printAndCount,打印一个字符串并返回 Int 类型的值。
函数 printWithoutCounting 调用了 printAndCount ,并忽略了它的返回值。当 printWithoutCounting 被调用时,printAndCount 函数会打印消息,但它的返回值不会被用到。
注意:返回值可以被忽略,但函数定义了返回值就必须返回一个值,如果没有返回任何值,会导致编译错误(compile-time error)。
10.6.多个返回值函数
使用元组(tuple)类型组合多个值为一个复合值作为函数的返回值。
下面的这个例子,函数 count 用来计算一个字符串中元音,辅音和其他字母的个数(基于美式英语的标准)。
var vowels = 0, consonants = 0, others = 0
for character in string {
switch String(character).lowercaseString {
case "a", "e", "i", "o", "u":
++vowels
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
++consonants
default:
++others
}
}
return (vowels, consonants, others)
}
可以用 count 函数来处理任何一个字符串,返回的值是一个包含三个 Int 型值的元组(tuple):
println("\(total.vowels) vowels and \(total.consonants) consonants")
// prints "6 vowels and 13 consonants"
需要注意的是,在返回值中并不需要对元组成员进行命名,因为它们的名称已经存在了(就是变量名)。
10.7.函数参数
以上所有的函数都给它们的参数定义了参数名称(parameter name):
// function body goes here, and can use parameterName
// to refer to the argument value for that parameter
}
这些参数只能在函数体中使用,不能在函数调用时使用。这种类型的参数被称作局部参数(local parameter name),因为它们只能在函数体中使用。
10.8.外部参数名
函数调用时,参数名是非常有用的,因为这些参数名说明实参的用途是什么。
如果你希望函数的使用者在调用函数时提供参数名字,那就需要给每个参数(除了局部参数)外再定义一个外部参数名。外部参数名写在局部参数名之前,用空格分开。
// function body goes here, and can use localParameterName
// to refer to the argument value for that parameter
}
下面这个函数例子,使用 joiner 参数名把两个字符串联在一起:
return s1 + joiner + s2
}
// returns "hello, world"
为了让这些字符串的用途更明确,我们为 join 函数添加外部参数名:
return s1 + joiner + s2
}
在这个版本的 join 函数中,第一个参数有一个叫 string 的外部参数名和叫 s1 的局部参数名,第二个参数有一个叫 toString 的外部参数名和叫 s2 的局部参数名,第三个参数有一个叫 withJoiner 的外部参数名和叫 joiner 的局部参数名。
现在,使用这些外部参数名调用函数,这样会更清晰:
// returns "hello, world"
注意:如果函数参数的意图不明显,考虑使用外部参数名;否则,就不需要使用外部参数名。
10.9.简写外部参数名
如果需要提供外部参数名,而局部参数名的定义已经非常明确了,那么你不需要写两次这个参数名。只写一次参数名,并用井号(#)作为前缀就可以了。这说明这个参数名既作为局部参数名也作为外部参数名。
下面这个例子定义了一个叫 containsCharacter 的函数,使用井号(#)的方式定义了外部参数名:
for character in string {
if character == characterToFind {
return true
}
}
return false
}
简写的外部参数名,使用函数更清晰,可读性更强;调用的时候也更清晰。
// containsAVee equals true, because "aardvark" contains a "v"
10.10.参数默认值
定义函数的时候可以为每个参数定义默认值,这样调用这个函数时可以略去这个参数。
注意:将带有默认值的参数放在函数参数列表的最后。这样可以保证在调用函数时,非默认参数的顺序是一致的。
下例是另一个版本的 join 函数,其中 joiner 参数有了默认值:
withJoiner joiner: String = " ") -> String {
return s1 + joiner + s2
}
像第一个版本的 join 函数一样,如果 joiner 被赋值,函数使用这个值连接两个字符串:
// returns "hello-world"
然而,如果 joiner 没有被赋值,函数会使用默认值(" "):
// returns "hello world"
10.11.外部参数默认值
给有默认值的参数起一个外部参数名是很有用的。这样可以保证当函数被调时,参数的意图是明显的。看下面的例子:
return s1 + joiner + s2
}
当调用函数时,外部参数名必须使用,参数用途很清晰:
// returns "hello-world"
10.12.可变参数
可变参数(variadic parameter)可以接受一个或多个实参值。函数调用时,可以传入不确定数量的实参。在变量名后面加 ... 来定义可变参数。
传入可变参数的实参值在函数体内是参数类型的数组。
来看一个例子:
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8, 19)
// returns 10.0, which is the arithmetic mean of these three numbers
注意:一个函数最多只能由一个可变参数,它必须是参数列表的最后的一个。这样做是为了避免函数调用时出现歧义。
如果函数有一个或多个带默认值的参数,并且还有一个可变参数,那么是可变参数放在参数表的最后。
10.13.常量和变量参数
函数参数默认是常量。所以,试图在函数体中更改参数值将会导致编译错误。
但是,有时候,传入参数的变量值有副本将是很有用的。指定一个或多个参数为变量参数,来避免在函数中定义新的变量。变量参数不是常量,在函数体中,它是可修改副本。
通过在参数名前加关键字 var 来定义变量参数:
let amountToPad = count - countElements(string)
for _ in 1...amountToPad {
string = pad + string
}
return string
}
let originalString = "hello"
let paddedString = alignRight(originalString, 10, "-")
// paddedString is equal to "-----hello"
// originalString is still equal to "hello"
alignRight 函数将参数 string 定义为变量参数。这意味着 string 可以作为一个局部变量,用传入的字符串值进行初始化,并且在函数体中可以进行操作。
注意:对变量参数的修改在函数调用结束后就结束了,对于函数体外是不可见的。变量参数的生命周期在函数调用过程中。
10.14.输入输出参数
正如上面所述:变量参数只能在函数体内被修改。如果需要一个函数可以修改参数值,并且这些修改在函数调用结束后仍然生效,那么就把这个参数定义为输入输出参数(In-Out Parameters)。
通过在参数前加 inout 关键字定义一个输入输出参数。输入输出参数值被传入函数,被函数修改,然后再被传出函数,原来的值将被替换。
只能传入一个变量作为输入输出参数。不能传入常量或者字面量(literal value),因为这些值是不允许被修改的。当传入的实参作为输入输出参数时,需要在实参前加 & 符,表示这个值可以被函数修改。
注意:输入输出参数不能有默认值,不能是可变参数。如果一个参数被 inout 标记,这个参数就不能被 var 或 let 标记。
函数 swapTwoInts 有两个分别叫做 a 和 b 的输出输出参数:
let temporaryA = a
a = b
b = temporaryA
}
函数 swapTwoInts 用于交换 a 与 b 的值。该函数先将 a 的值赋给临时常量 temporaryA ,然后将 b 的值赋给 a,最后将 temporaryA 的值赋给 b。
用两个 Int 型的变量来调用 swapTwoInts。注意:someInt 和 anotherInt 在传入 swapTwoInts 函数前,都必须加 & 前缀:
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
println("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// prints "someInt is now 107, and anotherInt is now 3"
从上面这个例子,可以看到 someInt 和 anotherInt 的原始值在 swapTwoInts 函数中已经被修改了,尽管它们定义在函数体外。
注意:输入输出参数和返回值是不一样的。函数 swapTwoInts 没有定义返回值,却修改了 someInt 和 anotherInt 的值。输入输出参数是函数对函数体外产生影响的另一种方式。
10.15.函数类型
每个函数都有特定的类型,由函数的参数类型和返回类型组成。
return a + b
}
func multiplyTwoInts(a: Int, b: Int) -> Int {
return a * b
}
以上定义了两个函数:addTwoInts 和 multiplyTwoInts。他们都有两个 Int 类型参数, 都返回一个 Int 类型值。
这两个函数的类型是 (Int, Int) -> Int,可以读作 “有2个Int类型参数,返回Int类型值的函数类型”。
看下面的例子
println("hello, world")
}
这个函数的类型是: () -> () ,可以读作“没有参数,没有返回值(或者返回 Void)的函数类型”。没有指定返回值类型的函数总是返回 Void。在Swift中,Void 与空的元组是一样的。
10.16.使用函数类型
在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将函数赋值给它:
理解为:定义一个叫做 mathFunction 的变量,类型是“有2个Int类型参数,返回Int类型值的函数类型”,是 addTwoInts 函数的引用。
addTwoInts 和 mathFunction 都是(Int, Int) -> Int类型,所以这个赋值在类型检查时是允许的。
用 mathFunction 来调用被赋值的函数(addTwoInts):
// prints "Result: 5"
有相同函数类型的不同函数可以被赋值给同一个变量,就像非函数类型的变量一样:
println("Result: \(mathFunction(2, 3))")
// prints "Result: 6"
像其他非函数类型一样,当给常量或变量赋值一个函数时,Swift 会推测其函数类型:
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
10.17.函数类型作为参数
像其他类型一样,函数类型可以作为参数。这样可以将函数的一部分功能交给给函数类型的参数。
比如用(Int, Int) -> Int 的函数类型作为另一个函数的参数类型:
println("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// prints "Result: 8"
当 printMathResult 被调用时,传入参数: addTwoInts 函数、整数3和5。函数内部调用 addTwoInts 函数,并输出结果 8 。
10.18.函数类型作为返回值
函数类型也可以作为另一个函数的返回值类型。就是在返回箭头(->)后写一个完整的函数类型。
来看例子:定义2个函数:stepForward 和stepBackward。stepForward 函数返回输入值加1的值。stepBackward 函数返回输入值减1的值。这两个函数的类型都是 (Int) -> Int:
return input + 1
}
func stepBackward(input: Int) -> Int {
return input - 1
}
函数 chooseStepFunction ,它的返回值类型是 (Int) -> Int 的函数类型。chooseStepFunction 根据布尔值 backwards 的真假返回 stepForward 函数或 stepBackward 函数:
return backwards ? stepBackward : stepForward
}
用 chooseStepFunction 来获得一个函数:
let moveNearerToZero = chooseStepFunction(currentValue > 0)
// moveNearerToZero now refers to the stepBackward() function
currentValue 的初始值是3,currentValue > 0 是 true ,所以 chooseStepFunction 返回 stepBackward 函数,并将返回值赋值给 moveNearerToZero 常量。
现在,moveNearerToZero 被用来数到 0:
// Counting to zero:
while currentValue != 0 {
println("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
println("zero! ")
// 3...
// 2...
// 1...
// zero!
10.19.嵌套函数
当然函数也可以被定义在别的函数体中,称作嵌套函数(nested functions)。
默认情况下,嵌套函数是对外是不可见的,但可以被封闭他们的函数(enclosing function)调用。封闭函数可以返回它的一个嵌套函数,使这个函数在其他域中可以被使用。
用返回嵌套函数的方式重写 chooseStepFunction 函数:
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backwards ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
println("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
println("zero! ")
// -4...
// -3...
// -2...
// -1...
// zero!
相关推荐
总的来说,Swift语言为iOS开发提供了一个高效、安全且易于学习的环境。通过深入学习Swift,不仅可以提升个人技能,也为参与苹果生态系统的创新和开发打开了大门。无论是初入编程的新手还是经验丰富的开发者,Swift都...
Swift语言实战入门源代码是针对初学者的一份宝贵资源,它包含了《Swift语言实战入门》这本书中的所有示例和练习代码。Swift是由Apple开发的一种强大且直观的编程语言,用于构建iOS、iPadOS、macOS、watchOS和tvOS的...
swift学习例子集合 Swift语言基础 【实例简介】swiftui 学习例子集合,多个页面集合到一个项目中,可供参考学习 【核心代码】. ├── swift-example │ ├── Example │ │ ├── AppDelegate.swift │ │ ├─...
swift语言实战晋级的扫描版 非常清晰 附带书签 供大家学习参考
在“Swift语言快速入门”这个教程中,我们可以期待学习到以下几个关键知识点: 1. **基础语法**:Swift的基础语法简洁明了,包括变量和常量的声明(var和let)、数据类型(Int、Double、String等)、运算符(算术、...
自 2014年 WWDC 发布 Swift 语言以来,本项目 一直致力于将主流 Swift 中文学习、开发资源汇集于此,并且尽力紧密地跟踪、甄选优秀 Swift 开源项目,以方便开发者快速获得并使用。考虑 Swift 已经正式发布超过四年半...
本资源“Swift语言学习基础Demo集合”旨在为初学者提供一系列实践示例,帮助他们更好地理解和掌握Swift的基础概念。 1. **变量与常量**:在Swift中,我们使用`var`声明变量,`let`声明常量。通过这些基础元素,你...
Swift语言是苹果公司推出的一种强大的、现代化的编程语言,用于构建iOS、iPadOS、macOS、watchOS和tvOS的应用程序。本教程“Swift语言教程:Swift项目实战”旨在通过实际项目开发,深入理解Swift语言的各个方面,...
通过"Swift语言快速入门第七章"和"Swift语言快速入门第八章"的学习,初学者将能够掌握Swift的基本语法和面向对象编程概念,为进一步深入开发iOS应用打下坚实基础。同时,提供的"大学霸淘宝店.url"可能是一个资源链接...
Swift语言是苹果公司于2014年推出的一种面向对象的编程语言,旨在为iOS、macOS、watchOS和tvOS等平台的开发提供更高效、简洁和安全的编程体验。本快速入门教程将帮助初学者掌握Swift的基础知识,逐步进阶到高级编程...
Swift编程入门教程,从零开始,深入浅出的Swift语言学习指南; Swift编程入门教程,从零开始,深入浅出的Swift语言学习指南; Swift编程入门教程,从零开始,深入浅出的Swift语言学习指南; Swift编程入门教程,从零...
Swift语言是一种现代、安全且性能优异的编程语言,特别适用于苹果操作系统的应用程序开发。Swift语言的特点在于其简洁、易学和强大的编译器,它能够快速发现程序中的错误,并提供实时反馈。 首先,Swift语言的基本...
Swift语言实战精讲的课程源文件是一份深入学习Swift编程的宝贵资料,涵盖了从基础到高级的各种主题。Swift是由Apple开发的一种强大且易学的编程语言,主要用于iOS、iPadOS、macOS、watchOS和tvOS的应用开发。这份...
对于Swift语言的广大开发者而言,这不仅是一个编程语言的学习资源,也是一个关于团队协作和奉献精神的启示录。通过本教程的学习,开发者们可以更好地掌握Swift语言,为构建强大的iOS应用打下坚实的基础。
Swift语言案例资源涵盖了从基础语法到高级功能...总之,Swift语言案例资源是开发者学习和实践Swift语言的重要参考,它们提供了从基础到高级的系统学习材料,以及丰富的实际应用场景和工具库支持。通过学习和实践这些案
《Swift语言》中文版API是苹果公司为开发者提供的官方文档,旨在帮助中文用户更好地理解和使用Swift编程语言。Swift是一款高效、安全、互动性强的开源编程语言,被广泛应用于iOS、iPadOS、macOS、watchOS以及tvOS的...
综上,"Swift语言实战晋级-课件代码源文件(第二版)[基于Xcode6.3]"提供了丰富的学习材料,涵盖了Swift的核心概念和实际应用,对于希望深入理解Swift编程的开发者来说是一份宝贵的资源。通过实践这些代码示例,可以...
Swift 通过向其他现代编程模式学习,定义了大量类来避免常⻅的编程错误: 变量一定是在使用前初始化的; 数组索引会检查越界错误; 整数会检查溢出; 可选项保证了 nil 值会显式处理; 内存自动管理; 错误处理允许从意外...