控制流
for、while、if、switch和C基本一样
for-in扩展 for
Swift的switch比C语言的要强大。swift的case不再默认执行下一case了,这样会避免遗漏break,导致下一case分支被执行;case可以匹配多种类型,可以是一个范围、元组、或者特殊类型。匹配值可以绑定到一个常量或者变量中,这样case可以使用;复杂的匹配条件可以使用where。
for循环
for-in遍历一个范围、序列、集合中的每一个元素;for按照一个条件执行遍历,典型的就是在每次操作后计数加1
for-in
for-in循环用来便利集合中的每一项。比如一定范围内的数字、数组中的每一项、字符串中的每一个字符等。
for index in 1...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的闭合区间(使用了范围操作符…),index会被赋予初始值1(也就是范围的第一个数字),循环体内可以被引用执行。这个例子中,只有一条语句,就是对当前index的值乘5,输出结果字符串。当语句被执行后,index的值被范围内的第二个数字2替换,循环体内的语句被再次执行。就这样,一直到范围的最后一项。例子中的index是一个常量,它会在每次循环前被设置新值。index是被非显示声明的常量,在循环语句中简话了let。
如果你在循环体内不使用范围内的每一项值,那么可以使用下划线替代常量(变量)名:
let base = 3 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"
上面例子是计算3的10次方。就是3连续乘自身10次(1-10的闭合区间)。循环体中不需要知道当前循环进行到的位置,这种情况下用下划线占位表示循环体内不需要引用当前循环值。
for-in遍历数组:
let names = ["Anna","Alex","Brian","Jack"] for name in names { println("Hello,\(name)!") } // Hello,Anna! // Hello,Alex! // Hello,Brian! // Hello,Jack!
你也可以通过键值对遍历字典。当字典在遍历时,会返回每一项对应的一个元组(形式为:(key,value)),可以在循环体内使用常量接收元组内容。下面的例子中,字典的键对应的常量名字叫做animalName,字典的值对应的常量名字叫做legCount。
let numberOfLegs = ["spider": 8,"ant": 6,"cat": 4] for (animalName,legCount) in numberOfLegs { println("\(animalName)s have \(legCount) legs") } // ants have 6 legs // cats have 4 legs // spiders have 8 legs
字典中的内容在遍历的时候可能和插入的顺序不同,因为字典天生就不是顺序存放内容的容器,不能保证插入顺序和遍历时的顺序一致。
for-int 同样可以使用在遍历字符串中字符的情形下:
for character in "Hello" { println(character) } // H // e // l // l // o
for
一般的形式:
for initialization; condition; increment { statements }
和C语言不同的就是缺少了圆括号。
执行顺序如下:
1:当遍历开始,初始化语句执行一次,做的工作是设置在循环体内使用的常量、变量。
2:条件语句被计算。如果条件判断为假,循环结束,执行末尾花括号之后的语句。如果条件判断为真,执行循环体内的语句。
3:循环体内语句执行完后,increment 语句被执行。一般会做一个计数器的自增或者自减或者修改一个初始化的值。这个语句被执行后,跳转到2,条件语句被再次执行。
上述的内容其实是下面语句的简化版本:
initialization while condition { statements increment }
上面的格式中,常量和变量的定义在初始化语句中进行,变量和常量的作用范围只限于循环内部。如果你在循环结束后也使用定义的内容,那么你就需要在进入循环前就定义:
var index: Int 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是2执行完后会执行++index,index已经变成了3,index<3的条件不成立,退出循环。
while循环
while循环,一直执行循环体的内容知道条件判断为假。尤其适合事先不知道迭代次数的情况。Swift提供两个格式的while:
1:while在每次执行循环体前判断条件。
2:do-while在每次执行循环体后判断条件
while
while condition { statements }
下面的游戏名字叫做《蛇和梯子》:
游戏规则是这样的:
1:有25个格子,目标是到达25格,或超过25格。
2:每回合,你掷一个6面的色子,根据色子上的个数,决定走几步。行进的方向按照虚线指引。
3:如果回合结束你到了一个梯子的底部,那么爬上梯子到达另外一端。
4:如果回合结束你到了一条蛇的头部,你将退回到那条蛇的尾部(If your turn ends at the head of a snake,you move down that snake.)
游戏面板用一个Int数组表示,它的长度按根据一个叫做finalSquare的常量而定。这个常量也是判断游戏胜利的条件。游戏面板被初始化为26个0,而不是25个,索引从0开始到25结束。:
let finalSquare = 25
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
3号格子是梯子的底部,你可以顺着梯子爬到11.为了表现这个,borad[03]被赋值为+8。+08是出于书写整洁考虑,+为了和-对应,10以下的数字前面添加0也是为了整齐。
玩家的起始位置在0号格子,就是游戏面板的左下角。下面给出第一种掷色子的方式:
var square = 0 var diceRoll = 0 while square < finalSquare { // roll the dice if ++diceRoll == 7 { diceRoll = 1 } // move by the rolled amount square += diceRoll if square < board.count { // if we're still on the board,move up or down for a snake or a ladder square += board[square] } } println("Game over!")
这是一个非常简单的掷色子方式。没有采用随机算法得到色子的点数,而是给出初始值0。每次循环都做一次自加操作,并作值检查,防止过大。当值过大(到7)的时候,被置为1。所以这个方式给出的色子点数序列一直都是这样的:“1,2,3,4,5,6,1,2……”。
循环体内执行完掷色子的操作后,玩家开始按照色子的点数移动。到达25号格子,游戏就结束了。为了达到这个目标,代码先做了检查:square 是不是还小于格子的总数。如果不是,游戏结束;如果是,执行如下语句:将board[square]的值加到square上(决定了是向上爬还是向下爬)。
如果没有做这个检查,board[square]会数组越界,造成运行时错误。
执行到循环体的最后,循环条件会被再次检查,确定是否可以再次执行循环。如果到达或超过25号格子,循环条件检查为假,游戏结束。
while适用这样的场合:循环开始前循环次数不定,循环会一直执行知道循环条件不成立。
do-while
do-while是另外一种while循环。在判断循环条件前,先执行循环体内容,直到循环条件判断为假。
基本形式:
do { statements } while condition
下面还是以蛇和梯子为例,用do-while实现。finalSquare,board,square,和 diceRoll在循环前就被初始化好了:
let finalSquare = 25 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
这个版本的游戏中,循环中第一步要做的就是判断是梯子还是蛇。没有梯子直通25号格子,所以不可能通过梯子赢得游戏,进而检查时梯子还是蛇是安全的。
在游戏的开始,玩家处于0号格子:
do { // move up or down for a snake or ladder square += board[square] // roll the dice if ++diceRoll == 7 { diceRoll = 1 } // move by the rolled amount square += diceRoll } while square < finalSquare println("Game over!")
在检查完是梯子还是蛇的语句执行完,开始摇色子,玩家按照摇出的点数走格子。循环体继续执行,直到循环结束。
条件检查语句和前一个例子一样,区别就是这个语句要在循环体执行完后才会被执行。在这个do-while循环中,square += board[square]会在循环条件判断完成后立即执行。它将条件检查后移了。
条件语句
两种条件语句:if和switch,前者使用简单的情况;后者适用多分枝的情况。
if
当它之后的条件语句为真时,才会执行一系列语句。
var temperatureInFahrenheit = 30 if temperatureInFahrenheit <= 32 { println("It's very cold. Consider wearing a scarf.") } // prints "It's very cold. Consider wearing a scarf."
这个例子检查温度师傅低于华氏32度(水的冰点)。如果为真,打印;否则不做任何事。继续执行if括号后的内容。
if语句提供了一个选项,就是else。当条件为假的时候,执行else部分的内容:
temperatureInFahrenheit = 40 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语句一起使用,以使用条件的判断:
temperatureInFahrenheit = 90 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."
这里,一个if语句被添加以应对特定热的温度情况。结束的else被保留,打印的内容是不是特别冷夜不是特别热的情况。
最后的一个else是可选的,根据需要决定是否使用:
temperatureInFahrenheit = 72 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.") }
在这个例子中,温度不冷不热的情况下,没有任何信息输出。
switch
switch会判断一个值,对应若干匹配项,然后对应的内容会被执行。只匹配第一个合适的内容。switch语句是多分支情况下if的另一种写法。
简单的写法下,switch语句拿一个值和另外一个或多个值作比较,当然它们必须是同一类型的:
switch some value to consider { 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以一个关键字开始。为了达到匹配特定数值的目的,Swift提供了多种方式匹配内容。下面将会一一讲到。
每个switch的case内容都是一个代码分支,这和if语句相似。Switch决定了哪个分支会被执行。
每个switch语句必须是彻底细分的。就是每个可能值的类型都要有对应的case匹配,如果没有case匹配的情况,需要定义一个默认匹配所有情况的分支。这样做需要使用关键字:default,一般它会出现在最后。
下面的例子评估一个小写字符(someCharacter):
let someCharacter: Character = "e" 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,所以提供了default来匹配所有其他情况。这样确保了switch是彻底细分的。
没有默认的接续执行(No Implicit Fallthrough)
和C语言和OC不同,Swift的switch不在默认依次执行每个case,而是只执行最初的匹配到的case,这样省得要写break了。这让switch变得更安全和简单,避免了多于一个的case会被执行。
尽管不用在case的执行结束写break了,但是break还是有用的,用于忽略特定的case或者在case没有完全执行完之前跳出。具体的描述后文有述。
每个case中至少要包含一条语句,否则不能通过编译:
let anotherCharacter: Character = "a" switch anotherCharacter { case "a": case "A": println("The letter A") default: println("Not the letter A") }
// this will report a compile-time error
上例中和C语言的不同之处在于,不能同时匹配“a”和“A”,反而会出现编译错误:case a没有包含任何可执行的语句。这样的设定会避免默认顺序执行,安全而且意图更加明确。
多个内容可以匹配同一个case,只要用逗号分隔他们,像下面这样:
switch some value to consider { case value 1,value 2: statements }
如果想要case顺序执行,可以使用关键字“fallthrough”,后文有述。
范围匹配
switch的case可以按照一个范文匹配值。下面的例子将一个数字转换到一个范围内:
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."
元组
你可以使用元组在一个case中尝试匹配多个值。每个元组的元素可以和一个值或者一个范围做比较。或者你可以使用”_”做通配符匹配任意值。
下面的例子使用一个元组表示一个点,然后对这个点进行归类:
let somePoint = (1,1) 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的cse对应几种情况:原点、x轴、y轴、在以远点为中心的4*4正方形中、在正方形之外。
和C语言不同的是,Swift允许Switch的不同case中存在相同的一个或多个值。实际上,原点可以匹配所有的4个case,如果有这样多个case都满足的情形,只有第一个case生效。(0,0)匹配到了原点后,其他的case被忽略了。
值绑定
switch的case可以绑定值给临时的常量或者变量,这些临时敞亮或者变量可以在case块内使用。这被称作value binding,因为值给绑定到了常量或者变量之上,在case中使用。
下面的例子用一个元组表示一个点,然后在图表中对其进行归类:
let anotherPoint = (2,0) 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)匹配所有y值是0的点并且将该点的横坐标赋值给常量x。类似的第二个case中,case (0,let y)匹配所有x值是0的点并且将改点的纵坐标赋值给常量y。
一旦常量被定义,他们就可以在case块中使用。例子中将他们的值打印了出来。
这个例子中switch没有default分支,最后的case中, case let (x,y),定义了一个元组并且将会匹配任何点。结果就是这个case会匹配所有剩下的情况,所以没有必要使用default确保switch包含了全部情形。
上面例子中定义x和y使用let关键字,这是因为在case中不需要对其做修改。当然也可以使用var将他们定义为变量,case块中就可以对其进行修改了。
where
可以在switch的case语句中使用where添加附加的判断条件。
下面的例子还是对点在图形上做归类:
let yetAnotherPoint = (1,-1) 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判断点是在绿色的线上、 紫色的线上还是其他。
case语句中定义了x和y常量,分别绑定yetAnotherPoint元组中的两个元素。这两个常量被用在了where语句中,作为动态过滤条件。只有当过滤条件满足的时候,才会匹配到对应的case。
上面例子中,最后一个case匹配所有情况了,所以default就不需要了。
控制转移语句
四个转移语句:
continue
break
fallthrough
return
continue,break,和fallthrough 会在下文说明。return语句参见Function一节。
continue
continue告诉循环要停止现在做的事情,返回循环体开始,开始下一个循环。也就是说:我做完了本次循环。不离开循环。
NOTE
for循环有条件和自增,循环的自增在调用了cntinue之后仍会被执行。循环自身继续执行,只有循环体内的代码会被忽略。
下面的例子移除了一个小写字符串中的元音字母和空格,形成了一个字谜:
let puzzleInput = "great minds think alike" var puzzleOutput = "" for character in puzzleInput { switch character { case "a","u"," ": continue default: puzzleOutput.append(character) } } println(puzzleOutput) // prints "grtmndsthnklk"
上面的代码在匹配到元音字母和空格时使用了continue关键字,导致当前循环直接进行到最后并且返回到循环体的开始进行下一次循环。这样让switch块匹配到元音字母和空格,不必将其他字母输出。
break
break会立即结束完整的控制流。break可以在switch语句或者循环语句中使用,当你在上述结构中想要终止继续执行的时候。
循环中的break
循环中使用break,直接结束循环,循环结束标记最后一个}后的第一行将被继续执行。当前循环的后续内容不被执行,后续循环的内容也不被执行。
switch中的break
switch中使用break会导致switch内容不再继续被执行,控制权交由switch末尾}后的第一行。
这个行为可以在想要忽略一个或多个case的时候使用。 因为Swift的switch要包含全部情形而且不允许有空的case存在,所以在需要直接表述特意匹配或者忽略某个case的时候特别有用。这样的具体作法是在case块中写break。当这个case被匹配到后,直接结束switch。
NOTE
switch的case中如果至于一条注释将会报错。注释并不是语句而且不能让这个case被忽略。要忽略一个case需要使用break。
下面的例子检查一个字符的值,判断是否返回一个数字标记(对应四种语言)。多个值在switch中就这样被简介的处理了:
let numberSymbol: Character = "三" // Simplified Chinese for the number 3 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的一个case会给一个叫做possibleIntegerValue 的Int可选类型赋予恰当的值。
在switch执行完毕后,这个例子采用了可选绑定判断是不是找到了一个值。possibleIntegerValue 变量被默认赋值为nil,如果possibleIntegerValue 被赋予了值,则可选绑定成功。这个赋值操作是在switch的case中进行的。
因为没有应对所有的字符情况,所以一个default case被采用了,用来匹配所有其他的字符。这个default里面不需要执行任何语句,所以写了一个break。当default被匹配到,break就结束了整个switch,后续的if let 语句被执行。
接续执行(Fallthrough)
Swift的switch不依次接续执行case,而是指执行匹配到的第一个case就结束了整个switch。而C语言中你需要在每个case的结束添加break阻止默认接续执行。避免默认接续执行意味着Swift的switch比C语言更加简明,这样避免了不小心执行多个case的情况。
如果你想要C风格的接续执行行为,你可以选择在swift的switch中使用 fallthrough 关键字。下面就是一个例子:
let integerToDescribe = 5 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."
这个例子定义了一个字符串类型的变量并且给他赋值了。然后用switch语句检查integerToDescribe 的值。如果integerToDescribe 是一个素数,integerToDescribe 会被追加素数的标记内容。接下来使用了fallthrough关键字,接续执行default case。default里面给integerToDescribe 追加了一些值。switch就执行完了。
如果integerToDescribe 的值不是列表中的素数,根本就不会走第一个case。因为没有其他 的情况,所以integerToDescribe 必然会匹配defalult。
所有的switch被执行,数字的表述被使用println方法打印出来。这个例子中5被正确的作为一个素数对待了。
NOTE
fallthrough关键字不检查接续执行的case的条件。fallthrough关键字能做到和C语言中的switch一样,执行完毕接续执行下一个case。
标签语句
用Swift你可以在循环和switch中嵌套使用循环和switch,构建复杂的控制流结构。循环和switch语句都可以使用break语句提前结束。然而更有用的是明确在break的对象到底是哪个循环或者switch。类似的在一个多重循环中,表明continue语句影响的是哪个循环也是有用的。
为了获得上述目标,你可以给循环或者switch添加标签,然后是使用这个标签在break或者continue语句中。
被标签的语句在开始使用标签的名字,后面跟冒号。这里有一个while循环的例子,switch也类似:
label name: while condition { statements }
下面的例子使用break和continue以及添加了标签的while循环。案例还是前述的蛇和梯子的游戏。这次有了额外的规则:
你必须准确的到达25号格子才能赢。
如果你超过了25号格子,那么你必须重新来过直到你恰好到达25号格子。
游戏面板和以前一样:
finalSquare,and diceRoll 的值初始化方式和先前一样: let finalSquare = 25 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号格子才行:
gameLoop: while square != finalSquare { if ++diceRoll == 7 { diceRoll = 1 } switch square + diceRoll { case finalSquare: // diceRoll will move us to the final square,so the game is over break gameLoop case let newSquare where newSquare > finalSquare: // diceRoll will move us beyond the final square,so roll again continue gameLoop default: // this is a valid move,so find out its effect square += diceRoll square += board[square] } } println("Game over!")
每次循环都会首先掷色子。没有直接移动玩家,而是用一个switch检查移动的结果,检查移动是否合法:
- 如果移动到最后一个格子,那么游戏结束。break gameLoop语句跳出while循环,开始执行其后第一句。
- 如果移动后超过了最大格子,那么移动无效,玩家需要重新掷色子。continue gameLoop 结束当前的while循环,开始下一次循环。
- 如果不是上面所述情况,移动就是有效的。玩家按照 色子点数移动,游戏逻辑会判断执行碰到梯子或是蛇的情形。这样下来当前的循环结束,返回 while的条件检查语句是否要继续下去。
NOTE 如果上面的break语句不使用gameLoop标签,将会跳出switch语句,而不是while语句。使用gameLoop标签使得要结束的语句更加明确。 这里其实并不一定要使用gameLoop标签,当使用continue gameLoop来跳转到下一次循环时。在这个游戏中只有一个层循环,所以只使用continue并不会有任何歧义。然而这里并不妨碍使用continue时使用gameLoop标签。这样的好处都是使得逻辑清晰易读易懂。