美文网首页
5 Control Flow 控制流

5 Control Flow 控制流

作者: 微笑中的你 | 来源:发表于2019-03-28 18:05 被阅读0次

    Swift提供多种控制流语句。
    其中包括多次执行任务的while循环;
    if、guard和switch语句根据特定条件执行不同的代码分支;语句break和continue将执行流转移到代码中的另一个点。

    Swift还提供了一个for-in循环,可以轻松地遍历array、dictionary、range、string和其他序列。

    Swift的switch声明比它在许多c语言中的对应声明要强大得多。用例可以匹配许多不同的模式,包括间隔匹配、元组和对特定类型的强制转换。
    switch 用例中的匹配值可以绑定到临时常量或变量,以便在用例主体中使用,并且可以用where子句为每种用例表示复杂的匹配条件。

    For-In Loops

    您可以使用for-in循环遍历序列,例如数组中的项、数字范围或字符串中的字符。

    例如:遍历数组中的字符串

    let names = ["Anna", "Alex", "Brian", "Jack"]
    for name in names {
        print("Hello, \(name)!")
    }
    // Hello, Anna!
    // Hello, Alex!
    // Hello, Brian!
    // Hello, Jack!
    

    例如:遍历字典中的键值对,以元组形式返回

    let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
    for (animalName, legCount) in numberOfLegs {
        print("\(animalName)s have \(legCount) legs")
    }
    // ants have 6 legs
    // cats have 4 legs
    // spiders have 8 legs
    

    字典的内容本质上是无序的,遍历它们并不能保证检索它们的顺序。特别是,将项插入字典的顺序并不定义它们迭代的顺序。


    例如: 遍历数字范围

    for index in 1...5 {
        print("\(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
    

    如果不需要序列中的每个值,可以使用下划线代替变量名来忽略这些值。

    let base = 3
    let power = 10
    var answer = 1
    for _ in 1...power {
        answer *= base
    }
    print("\(base) to the power of \(power) is \(answer)")
    // Prints "3 to the power of 10 is 59049"
    

    在某些情况下,您可能不希望使用封闭区间(包括两个端点)。考虑在手表表盘上画上每分钟的刻度。你想要画60个勾,从0分钟开始。使用半开区间运算符(..<)来包含下界,而不包含上界。

    let minutes = 60
    for tickMark in 0..<minutes {
        // render the tick mark each minute (60 times)
    }
    

    间断执行: 半开区间-右开

    let minuteInterval = 5
    for tickMark in stride(from: 0, to: 60, by: minuteInterval) {
        print(tickMark)
    }
    

    输出结果:

    0
    5
    10
    15
    20
    25
    30
    35
    40
    45
    50
    55
    

    数值闭区间还可以这样表示:

    let hours = 12
    let hourInterval = 3
    for tickMark in stride(from: 3, through: hours, by: hourInterval) {
        print(tickMark)
    }
    

    输出结果

    3
    6
    9
    12
    

    While Loops while 循环

    while循环执行一组语句,直到条件变为false。当在第一次迭代开始之前迭代的次数未知时,最好使用这种循环。Swift提供了两种while循环:

    • while :在每次循环开始时计算其条件。
    • repeat-while :每次循环结束时计算其条件。

    while

    while循环先判断条件语句开始。如果为true,则执行一次语句,然后继续判断条件语句,直到条件变为false停止执行。

    while condition {
    statements
    }

    简单例子

    var n = 1
    while n<10  {
        print(n)
        n += 5
    }
    

    输出结果

    1
    6
    

    显然执行了 2次,第一次判断 n=1 时条件为true,第二次判断 n = 6 时条件为true,第三次判断 n = 11 条件为false就不执行了

    repeat-while

    先执行一次代码块中的语句,然后判断条件,条件成立true,则再次执行代码块中的语句,直到条件不成立false时,就不再执行代码块中的语句

    repeat {
    statements
    } while condition

    同样来个简单例子

    var m = 1
    repeat {
        print(m)
        m += 5
    } while m < 10
    

    输出结果:

    1
    6
    

    执行代码时m=1,第一次执行循环体后 m 变为了6,判断条件成立 true,第二次执行循环体后 m 变为了 11,判断条件不成立 false 不再执行循环体中代码

    Conditional Statements 条件语句

    Swift提供了两种向代码添加条件分支的方法:if语句和switch语句。通常,您使用if语句来评估只有少数可能结果的简单条件。switch语句更适合具有多种可能排列的更复杂的条件,在模式匹配可以帮助选择要执行的适当代码分支的情况下非常有用。

    if

    单个 if 语句

    let n = 5
    if n < 10 {
        print(n)
    }
    输出 5
    

    if else 语句简单例子

    var age = 16
    if age < 18 {
        print("未成年人,禁止进入网吧!")
    } else {
        print("成年人,请付费上网!")
    }
    

    多重 if 语句简单例子:考试成绩等级

    let score = 80
    if score < 60 {
        print("考试成绩:不及格")
    } else if score > 80 {
        print("考试成绩: 优秀")
    } else {
        print("考试成绩: 良好")
    }
    

    嵌套 if

    if score < 60 {
        print("考试成绩:不及格")
    } else {
        if score < 80 {
            print("考试成绩: 良好")
        } else {
            print("考试成绩: 优秀")
        }
    }
    

    当然可以多重嵌套,和更多的多重if,此时推荐使用 switch。

    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
    }
    

    如果case中包含了所有可能性,那么就不用default了。
    当多种情况,执行相同操作时,在 case 中使用 逗号 分隔。

    同样写个简单例子,考试成绩等级

    let score = 80
    let l1 = 80...100
    let l2 = 60..<80
    let l3 = 0..<60
    
    switch score {
    case l1:
        print("考试成绩优秀")
    case l2:
        print("考试成绩良好")
    case l3:
        print("考试成绩不及格")
    default:
        print("缺考")
    }
    //打印: 考试成绩优秀
    

    上面成绩为 80 分,在l1等级。
    注意:swift中 switch语句中不需要使用 break,使用也不会报错。其他语言中都有使用break,以防止进入下一个case。swift 中并不会进入下一个case,但是每个case中至少有一条语句。

    Tuples 元组匹配

    您可以使用元组在同一个switch语句中测试多个值。元组的每个元素都可以针对不同的值或值间隔进行测试。或者,使用下划线(_)(也称为通配符模式)匹配任何可能的值。

    下面的示例采用(x, y)点,表示为类型(Int, Int)的简单元组,并在随后的图中对其进行分类。

    let somePoint = (1, 1)
    switch somePoint {
    case (0, 0):
        print("\(somePoint) is at the origin")
    case (_, 0):
        print("\(somePoint) is on the x-axis")
    case (0, _):
        print("\(somePoint) is on the y-axis")
    case (-2...2, -2...2):
        print("\(somePoint) is inside the box")
    default:
        print("\(somePoint) is outside of the box")
    }
    // Prints "(1, 1) is inside the box"
    
    coordinateGraphSimple_2x.png

    Value Bindings 值绑定

    switch用例可以将它匹配的值或值命名为临时常量或变量,以便在用例主体中使用。这种行为称为值绑定,因为值绑定到案例主体中的临时常量或变量。

    下面的例子取一个(x, y)点,表示为类型(Int, Int)的元组,并在下图中对其进行分类:

    let anotherPoint = (2, 0)
    switch anotherPoint {
    case (let x, 0):
        print("on the x-axis with an x value of \(x)")
    case (0, let y):
        print("on the y-axis with a y value of \(y)")
    case let (x, y):
        print("somewhere else at (\(x), \(y))")
    }
    // Prints "on the x-axis with an x value of 2"
    
    coordinateGraphMedium_2x-2.png

    switch语句确定该点是在红色的x轴上、橙色的y轴上,还是在其他地方(两个轴都没有)。

    这三个开关用例声明占位符常量x和y,它们临时接受来自另一个点的一个或两个元组值。第一种情况,(x, 0),匹配任何点的y值0和分配点的x值暂时不变。同样的,第二种情况下,例(0,让y)匹配任何点的x值0和分配点的y y值暂时不变。

    在声明临时常量之后,可以在case的代码块中使用它们。在这里,它们被用来打印点的分类。

    此switch语句没有默认情况。最后一个case, case let (x, y)声明了一个由两个占位符常量组成的元组,它可以匹配任何值。因为另一个point总是由两个值组成的元组,所以这种情况匹配所有可能的剩余值,并且不需要使用缺省情况来使switch语句详尽。

    where

    swift 用例可以使用where子句检查附加条件。

    下面的例子在下图中对一个(x, y)点进行了分类:

    let yetAnotherPoint = (1, -1)
    switch yetAnotherPoint {
    case let (x, y) where x == y:
        print("(\(x), \(y)) is on the line x == y")
    case let (x, y) where x == -y:
        print("(\(x), \(y)) is on the line x == -y")
    case let (x, y):
        print("(\(x), \(y)) is just some arbitrary point")
    }
    // Prints "(1, -1) is on the line x == -y"
    
    coordinateGraphComplex_2x.png

    switch语句确定该点是在绿色对角线上(x == y),还是在紫色对角线上(x == -y),或者两者都不是。

    这三个开关用例声明占位符常量x和y,它们临时接受来自yetAnotherPoint的两个元组值。这些常量用作where子句的一部分,用于创建动态过滤器。只有当where子句的条件为true时,switch case才匹配point的当前值。

    与前面的示例一样,最后一个用例匹配所有可能的剩余值,因此不需要使用缺省用例来使switch语句详尽。

    Compound Cases 复合case

    switch中共享同一主体的多个case, 可以通过在case之后编写几个模式组合起来,每个模式之间用逗号隔开。如果其中任何模式匹配,则认为案例匹配。如果列表很长,可以将模式写在多行上。例如:

    let someCharacter: Character = "e"
    switch someCharacter {
    case "a", "e", "i", "o", "u":
        print("\(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":
        print("\(someCharacter) is a consonant")
    default:
        print("\(someCharacter) is not a vowel or a consonant")
    }
    // Prints "e is a vowel"
    

    switch语句的第一个大小写匹配英语中所有五个小写元音。同样,它的第二个大小写匹配所有小写的英语辅音。最后,默认情况匹配任何其他字符。

    复合情况还可以包括值绑定。复合案例的所有模式必须包含相同的值绑定集,并且每个绑定必须从复合案例中的所有模式中获得相同类型的值。这确保无论复合案例的哪一部分匹配,案例主体中的代码始终可以访问绑定的值,并且该值始终具有相同的类型。

    let stillAnotherPoint = (9, 0)
    switch stillAnotherPoint {
    case (let distance, 0), (0, let distance):
        print("On an axis, \(distance) from the origin")
    default:
        print("Not on an axis")
    }
    // Prints "On an axis, 9 from the origin"
    

    上面的例子有两种模式:(let distance, 0)匹配x轴上的点,(0,let distance)匹配y轴上的点。这两种模式都包含一个用于距离的绑定,而距离在这两种模式中都是整数——这意味着案例主体中的代码总是可以访问一个用于距离的值。

    Control Transfer Statements 控制转移语句

    控件转移语句通过将控件从一段代码转移到另一段代码来更改代码执行的顺序。Swift中有五个控制转移语句:

    • continue
    • break
    • fallthrough
    • return
    • throw

    下面将描述continue、break和fallthrough语句。return语句在函数中描述,throw语句在使用抛出函数传播错误中描述。

    Continue

    continue语句告诉循环停止正在执行的操作,并在循环的下一个迭代开始时重新开始。它说“我已经完成了当前循环迭代”,而没有完全离开循环。

    简单例子:

    for i in 1...10 {
        if i%2 == 0 {
            continue
        }
        print("只打印奇数:\(i)")
    }
    

    输出结果:

    只打印奇数:1
    只打印奇数:3
    只打印奇数:5
    只打印奇数:7
    只打印奇数:9
    

    Break

    break语句立即终止整个控制流语句的执行。当您希望提前终止switch或loop语句的执行时,可以在switch或loop语句中使用break语句。

    Break in a Loop Statement

    当在循环语句中使用break时,它会立即结束循环的执行,并在循环的右大括号(})之后将控制权转移到代码中。不再执行当前循环迭代中的代码,也不再启动循环的进一步迭代。

    Break in a Switch Statement

    当在switch语句中使用break时,break会导致switch语句立即结束执行,并在switch语句的右括号(})之后将控制权转移到代码中。

    此行为可用于匹配和忽略switch语句中的一个或多个案例。因为Swift的switch声明是详尽的,并且不允许空案例,所以有时有必要故意匹配和忽略一个案例,以便使您的意图明确。通过将break语句作为要忽略的整个案例体来实现这一点。当这种情况与switch语句匹配时,case中的break语句立即终止switch语句的执行。

    只包含注释的切换用例被报告为编译时错误。注释不是语句,不会导致忽略开关用例。始终使用break语句来忽略开关情况。

    下面的示例打开一个字符值,并确定它是否用四种语言之一表示数字符号。为了简单起见,一个开关用例包含多个值。

    let numberSymbol: Character = "三"  // Chinese symbol 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 {
        print("The integer value of \(numberSymbol) is \(integerValue).")
    } else {
        print("An integer value could not be found for \(numberSymbol).")
    }
    // Prints "The integer value of 三 is 3."
    

    本例检查数字符号,以确定数字1到4是拉丁、阿拉伯、汉语还是泰国符号。如果找到匹配项,switch语句的一个用例将设置一个可选的Int?一个名为possibleIntegerValue的变量,将其转换为适当的整数值。

    switch语句执行完成后,示例使用可选绑定来确定是否找到了一个值。由于是可选类型,possible leintegervalue变量的初始值隐式为nil,因此,只有在switch语句的前四种情况之一将possible leintegervalue设置为实际值时,可选绑定才会成功。

    因为在上面的例子中不可能列出所有可能的字符值,所以默认情况下处理任何不匹配的字符。这个默认情况不需要执行任何操作,因此它的主体是一个break语句。一旦匹配了默认情况,break语句将结束switch语句的执行,代码执行将从if let语句继续。

    Fallthrough

    在Swift中,switch语句不会从每个case的底部进入下一个case。也就是说,当第一个匹配的用例完成时,整个switch语句就完成了它的执行。相反,C要求您在每个switch case的末尾插入一个显式的break语句,以防止漏接。避免默认错误意味着Swift switch语句比C语言中的对应语句更加简洁和可预测,因此它们避免了错误地执行多个开关用例。

    如果您需要c风格的fallthrough行为,您可以使用fallthrough关键字逐个案例地选择该行为。下面的示例使用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."
    }
    print(description)
    

    输出结果为:

    The number 5 is a prime number, and also an integer.
    

    本例声明了一个名为description的新字符串变量,并为其赋值。然后,函数使用switch语句考虑integerToDescribe的值。如果integerToDescribe的值是列表中的质数之一,则该函数将文本附加到描述的末尾,以注意该数字是质数。然后,它还使用fallthrough关键字“陷入”默认情况。默认情况下,在描述的末尾添加一些额外的文本,switch语句就完成了。

    除非integerToDescribe的值在已知素数列表中,否则第一个开关情况根本不匹配它。因为没有其他特定的情况,integerToDescribe与默认情况匹配。

    fallthrough关键字不检查导致执行陷入的切换用例的用例条件。fallthrough关键字只是导致代码执行直接移动到下一个case(或默认case)块中的语句,就像在C的标准switch语句行为中一样。

    Labeled Statements 标签申明语句

    在Swift中,您可以将循环和条件语句嵌套在其他循环和条件语句中,以创建复杂的控制流结构。然而,循环和条件语句都可以使用break语句提前结束它们的执行。因此,有时显式说明希望终止哪个循环或条件语句是有用的。类似地,如果您有多个嵌套循环,那么显式地说明continue语句应该影响哪个循环是很有用的。

    要实现这些目标,可以使用语句标签标记循环语句或条件语句。对于条件语句,可以使用带有break语句的语句标签来结束已标记语句的执行。对于循环语句,您可以使用带有break或continue语句的语句标签来结束或继续执行带标签的语句。

    带标签的语句通过将标签放在与语句的介绍人关键字相同的行上,后跟冒号来表示。下面是while循环的一个语法例子,尽管所有循环和switch语句的原则都是相同的:
    labelName: while condition {
    statements
    }

    下面的例子使用了break和continue语句,游戏规则为
    如果一个骰子掷出的点数超过了25平方,你必须再掷一次,直到你掷出的点数达到25平方的点数为止。

    这个版本的游戏使用while循环和switch语句来实现游戏的逻辑。while循环有一个名为gameLoop的语句标签,表示它是snake和ladder游戏的主游戏循环。

    while循环的条件是while square != finalSquare,为了反映您必须精确地降落在square 25上。

    let finalSquare = 25
    var board = [Int](repeating: 0, count: finalSquare + 1)
    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
    
    gameLoop: while square != finalSquare {
        diceRoll += 1
        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]
        }
    }
    print("Game over!")
    

    骰子在每个循环的开始滚动。循环不是立即移动玩家,而是使用switch语句来考虑移动的结果,并确定是否允许移动:

    • 如果掷骰子将玩家移到最后一个方块,游戏就结束了。break gameLoop语句将控制权转移到while循环之外的第一行代码,从而结束游戏。

    • 如果掷骰子将玩家移出最后一个方块,则该移动无效,玩家需要再次掷骰。continue gameLoop语句结束当前while循环迭代并开始循环的下一个迭代。

    • 在所有其他情况下,掷骰子都是有效的。玩家通过双棋盘向前移动,游戏逻辑检查是否有蛇和梯子。然后循环结束,控制返回到while条件,以决定是否需要进行另一轮。

    如果上面的break语句没有使用gameLoop标签,它将跳出switch语句,而不是while语句。使用gameLoop标签可以清楚地表明应该终止哪个控制语句。

    在调用continue gameLoop以跳转到循环的下一个迭代时,并不一定要使用gameLoop标签。游戏中只有一个循环,因此对于continue语句将影响哪个循环没有歧义。但是,使用带有continue语句的gameLoop标签并没有什么害处。这样做与标签和break语句的使用是一致的,并有助于使游戏的逻辑更清晰地阅读和理解。

    Early Exit 提前退出

    与if语句类似,guard语句根据表达式的布尔值执行语句。使用guard语句要求条件必须为true,以便在执行guard语句之后执行代码。与if语句不同,guard语句总是有一个else子句——如果条件不为真,则在else子句中执行代码。

    func greet(person: [String: String]) {
        guard let name = person["name"] else {
            return
        }
    
        print("Hello \(name)!")
    
        guard let location = person["location"] else {
            print("I hope the weather is nice near you.")
            return
        }
    
        print("I hope the weather is nice in \(location).")
    }
    
    greet(person: ["name": "John"])
    // Prints "Hello John!"
    // Prints "I hope the weather is nice near you."
    greet(person: ["name": "Jane", "location": "Cupertino"])
    // Prints "Hello Jane!"
    // Prints "I hope the weather is nice in Cupertino."
    

    如果满足了guard语句的条件,则在guard语句的右括号之后继续执行代码。使用可选绑定作为条件的一部分为值赋值的任何变量或常量都可用于guard语句出现的代码块的其余部分。

    如果不满足该条件,则执行else分支中的代码。该分支必须转移控件才能退出出现guard语句的代码块。它可以通过控制传输语句(如return、break、continue或throw)来实现这一点,也可以调用不返回的函数或方法,如fatalError(_:file:line:)。

    与使用if语句进行相同的检查相比,为需求使用guard语句可以提高代码的可读性。它允许您编写通常执行的代码,而不需要将其包装在else块中,并且允许您将处理违反需求的代码放在需求旁边。

    Checking API Availability 检查api可用性

    Swift内置了对API可用性检查的支持,这确保您不会意外地使用在给定部署目标上不可用的API。

    编译器使用SDK中的可用性信息来验证代码中使用的所有api在项目指定的部署目标上都是可用的。如果你试图使用一个不可用的API, Swift会在编译时报告一个错误。

    您可以在if或guard语句中使用可用性条件来有条件地执行代码块,这取决于您希望使用的api在运行时是否可用。当编译器验证该代码块中的api可用时,它使用来自可用性条件的信息。

    if #available(iOS 10, macOS 10.12, *) {
        // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
    } else {
        // Fall back to earlier iOS and macOS APIs
    }
    

    上面的可用性条件指定在iOS中,if语句的主体只在ios10及以后版本中执行;在macOS中,只有在macOS 10.12或更高版本中。最后一个参数*是必需的,并指定在任何其他平台上,if的主体在目标指定的最小部署目标上执行。

    if #available(platform name version, ..., *) {
    statements to execute if the APIs are available
    } else {
    fallback statements to execute if the APIs are unavailable
    }

    <<返回目录

    相关文章

      网友评论

          本文标题:5 Control Flow 控制流

          本文链接:https://www.haomeiwen.com/subject/ylzxbqtx.html