首页 > 代码库 > 7.Swift教程翻译系列——控制流之循环

7.Swift教程翻译系列——控制流之循环

英文版PDF下载地址http://download.csdn.net/detail/tsingheng/7480427

Swift提供了类C语言类似的控制流结构。包括for循环和while循环来多次执行任务,if和switch语句根据不同的条件执行不同的分支代码,break和continue语句将执行流程跳转到其他语句。

除了C里面传统的for-条件-递增循环,Swift还增加了for-in循环使得遍历数组,字典,范围,字符串或者其他序列都很简单。

Swift的switch语句也要比C语言的switch更强大。Swift里面某个case语句执行完以后不会再去执行后面的case,避免C语言中由于忘记写break而引起的错误。case可以匹配多种不同的类型模式,包括范围匹配,元组或者某个特定的类型(casts to a specific type)。case当中被匹配的值可以与临时常量或者变量绑定以便在case里面可以使用,复杂的匹配条件可以使用where来表示。

1.for循环

for循环反复执行语句指定次数。Swift提供两种形式的for循环:

  • for-in在某个范围,序列,集合或者系列中对每个元素执行一次。
  • for-condition-increment反复执行直到满足某个特殊条件,一般在每次循环之后计数器递增。

for-in

你可以使用for-in循环来遍历集合的元素,比如一个范围的数字,某个数组中的元素或者字符串中的字符。

下面的例子输出乘法表中乘5的那一行:

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的值使用范围的第二个值2更新,输出语句又被执行了。这个过程一直范围的最后一个值。

上面资历中,index是常量,在每次进行循环的时候自动被赋值。这种情况index没有必要再使用前就声明。在for-in当中直接使用index就相当于是隐式声明了常量index,都不需要再使用let关键字。

NOTE 常量index的作用范围仅仅是循环体,如果你想在循环结束以后查看index的值,或者你想让index作为变量而不是常量,那你必须在循环之前自己去声明index。

如果你并不是需要范围中的所有值,你可以通过使用在变量的位置使用下划线来忽略元素的值。

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"
这个例子计算base的power次方(例子中是3的10次方)。例子从1乘3开始,乘了10次,使用了从0到9的半闭区间(这里有问题吗,原文是using a half-closed loop that start with 0 and ends with9,但是例子没有半闭区间也没有0到9啊)。这个计算不需要知道每次循环计数器的值,只需要每次循环的之后给answer再乘base就行了。循环变量的位置使用的下划线使得每次循环过程都忽略了循环变量的值,也不提供获取循环变量值的途径了。

使用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对。字典的每个元素以元组(key, value)的形式返回,你可以将其成员解析到指定的常量中以便循环中使用。下面例子将字典的key解析到常量animalName,字典的值被解析到常量legCount:

let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
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循环遍历字符串中的字符:

for character in "Hello" {
    println(character)
}
// H
// e
// l
// l
// o


for-condition-increment

除了for-in循环,swift还支持传统的C风格的带有条件和递增语句的for循环:

for var index = 0; index < 3; ++index {
    println("index is \(index)")
}
// index is 0
// index is 1
// index is 2
这种循环的一般格式是这样的:

for initialization ; condition ; increment {
     statements
}

和C一样分号分隔了循环定义的三个部分,但是和C不一样的是Swift不需要在initialization ; condition ; increment外面加括号。

循环的执行顺序如下:

  1. 当第一次进入循环的时候,初始化表达式(initialization)被执行,创建循环可能会用到的常量或者变量。
  2. 条件表达式被执行。如果条件表达式结果为false,循环结束,程序执行循环体以后的代码。如果条件表达式为true,就执行循环体。
  3. 循环体中所有语句都执行完了以后,递增语句才被执行。这条语句一般用来递增或者递减计数器,或者根据语句输出来给已经初始化过的变量更新值。当递增语句执行完以后,程序回到第二步,条件语句又被执行一次。

循环格式和执行过程可以使用下面等价的形式来表达:

initialization
while condition {
    statements
    increment
}

在初始化表达式中声明(比如var index = 0)的常量和变量只能在这个for循环中能够使用,要在循环结束后使用index,必须在循环之前声明index。

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被执行,index变为3,然后index<3等于false,循环结束。

2.while循环

while循环反复执行循环体直到条件变为false。这种循环非常适用于在循环体执行之前循环次数未知的情况。Swift提供两种while循环形式:

  • while:每次执行循环体之前先计算条件的值。
  • do-while:每次执行完循环体之后计算条件的值。

while
while循环以计算条件表达式开始。如果条件为true,循环体就被反复执行知道条件为false,下面是while循环的一般形式

while condition {

statements

}

下面玩一个蛇与梯子的游:



游戏规则:

  • 棋盘有25个方格,游戏的目的是要到达或者超过第25格。
  • 每次一轮,你需要摇色子来决定你走几格,路线如上图虚线所示。
  • 如果这一轮结束的时候你到了梯子的底部,就顺着梯子上去。
  • 如果你这轮结束的时候碰到了舌头,就顺着射移到下面去。

棋盘可以使用一个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,上面整数前面带加号知识为了看起来更清楚,下标小于10的前面加0只是为了代码整齐。(加号和多余的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("通关!")
上面例子里使用很简单的方式来掷色子。让diceRoll从0开始,每次循环给他加1,++diceRoll返回的是+1后的值,所以当diceRoll+1等于7的时候再把diceRoll设置为1,所以色子的结果是1,2,3,4,5,6,1,2,3...而不是用随机数。

色子投出来以后,玩家移动diceRoll对应的格数。如果玩家移动以后的格数大于或者等于25就赢了。考虑这种情况,代码在移动以后检测当前位置是不是比棋盘格数要小,如果比棋盘格数小才加上那一格中的值,如果不小,就说明过关了。那一格中存储的值就是顺着梯子往上爬或者顺着蛇往下爬的格数。如果不加这个判断,board[square]就有可能要去获取数组board范围外的值,将会引起运行错误。如果现在squre等于26,代码会去获取board[26],这个下标超过数组board的范围了。

当前循环体执行完了以后,执行循环条件语句,看循环是否继续执行。如果玩家已经移动到或者超过第25格,循环条件结果就是false,游戏就结束了。

这个例子使用while循环是比较合适的,因为在循环开始之前是不知道游戏要进行的次数的。相反,循环是一直执行到某个特定的条件满足才停止。

do-while

另一种while循环do-while先执行一次循环体,然后才去判断循环条件。然后循环执行循环体直到循环条件为false。

下面是do-while的一般形式:

do {
statements
} while condition

下面还是蛇与梯子的游戏,这次使用do-while而不是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格,所以玩家不可能通过梯子移动就直接通关。所以循环第一步直接检查蛇或者梯子是安全的。(这里读起来挺别扭的好像没有解释清楚,应该是说用do-while的时候循环第一步就可以直接加上当前格子对应的数字,而不是先掷色子,因为掷色子移动然后就可能到达25格,那你再爬梯子之前就需要前面那个if了,但是如果先爬梯子,就不用判断接着就掷色子,因为从图上看出来爬梯子不可能会通关的。)

游戏开始的时候玩家在第0格上,board[0]还是0,没有什么影响:

do {
<span style="white-space:pre">	</span>// move up or down for a snake or ladder
<span style="white-space:pre">	</span>square += board[square]
<span style="white-space:pre">	</span>// roll the dice
<span style="white-space:pre">	</span>if ++diceRoll == 7 { diceRoll = 1 }
<span style="white-space:pre">	</span>// move by the rolled amount
<span style="white-space:pre">	</span>square += diceRoll
} while square < finalSquare
println("Game over!")
代码检查过梯子或者蛇以后,再通过掷色子移动,然后一轮循环结束。

循环条件跟前面的是一样的,但是这次循环条件只有当第一次循环结束的时候才会被第一次计算。对于这个游戏来说do-while循环要比while循环更合适。上面do-while的循环里,当循环条件确定squre还在棋盘上的时候开始下一次循环,并且进入循环一开始就执行squre+=board[square]。这样就免去了前面对于square边界的检查。