Swiftの制御転送(Control Transfer Statements)

制御転送(Control Transfer Statements)

制御転送(control transfer statements)は、ループや条件分岐の実行順序を変更する機能です。ある制御によって実行される処理を、別の実行文に「転送」させることから、「制御転送」という名前になっているようです。名前は難しいですが、機能としては、以下で説明している使用例をご覧になって頂ければ理解しやすいと思います。

Continue

continueは、ループ(for-in文やwhile文)の中でのみ使用できる制御転送文です。continueの機能を日本語にすると、

以降の処理をスキップして、ループの先頭に戻る

という制御になります。実際の使用例で見た方が分かりやすいです。

// continueの使用
var someCharacters = ["あ","か","さ","た","な"]
for item in someCharacters {
    switch item {
    case "あ","い","う","え","お":
        print("\(item)は母音です")
    case "か","き","く","け","こ":
        print("\(item)は子音です")
    case "さ"..."そ":
        continue
    default:
        print("\(item)は母音でも子音でもありません")
    }
}
//結果:
//あは母音です
//かは子音です
//たは母音でも子音でもありません
//なは母音でも子音でもありません

条件分岐のswitchの項で紹介した例を拡張したものです。少し複雑ですが、順番に見ていきます。

上記の例は2つの制御構文が入れ子になっています。1つ目は外側のfor-inループです。

// continueの使用(外側のfor in)
var someCharacters = ["あ","か","さ","た","な"]
for item in someCharacters {
  ....
}

これは配列arraysomeCharactersの要素を、itemで順番に取り出しているだけです。

2つ目はswitch文です。

// continueの使用(内側のswitch)
switch item {
case "あ","い","う","え","お":
    print("\(item)は母音です")
case "か","き","く","け","こ":
    print("\(item)は子音です")
case "さ"..."そ":
    continue
default:
    print("\(item)は母音でも子音でもありません")
}

これはCharacteritemにどんな文字が入っているのかを調べる条件分岐です。

アウトプットを見て頂くと分かりますが、「さ」の場合だけ出力がありません。これは

// さ行をcontinueでスキップ
case "さ"..."そ":
    continue

によって、さ行の処理をスキップしているからです。さらに、「さ」の後の配列要素「た」と「な」はdefaultで処理されていることが分かります。

一番最初にcontinueの日本語による表現を書きましたが、上記の例はまさに「以降の処理をスキップして、ループの先頭に戻る」という制御を実行していることが分かります。continueは、それ以降の処理はスキップしますが、ループ自体には留まります。

Break

break

ループや条件分岐を直ちに抜ける

という制御文です。これも実際の例で見ると分かりやすいと思いますので、ループと条件分岐の場合でサンプルを見てみます。

ループでのbreak使用例

For-inループでの使用例です。

// For-inループでのbreak使用例
for id in 0...5 {
    if id == 3 { break }
    print("id = \(id)")
}
//id = 0
//id = 1
//id = 2

ここではid == 3という条件の時にbreak(ループを抜ける)という制御をfor-in文に差し込みました。したがって、print()関数はid2までしか実行されず、その後のループ処理を無視してループを離脱しています。

もう一つwhileループでの使用例です。

// Whileループでのbreak使用例
var sum = 0
while sum < 5 {
    print("和 = \(sum)")
    sum += 1

    if sum == 3 {
        print("ループを抜けます")
        break
    }
}
//和 = 0
//和 = 1
//和 = 2
//ループを抜けます

for-in文の場合と同じですね。ここでは分かりやすいように、breakの直前に「ループを抜けます」という文字列を出力するようにしています。

Switch文でのbreak使用例

switchcaseでは、実行文を少なくとも1つ記述しなければコンパイルエラーになります。しかし、実行文なしに特定の条件をスキップさせたい場合もあるかもしれません。そのような場合にはbreakを使うことができます。

// Switch文でのbreak使用例
var someCharacter = "た"
switch someCharacter {
case "あ","い","う","え","お":
    print("\(someCharacter)は母音です")
case "か","き","く","け","こ":
    print("\(someCharacter)はか行です")
case "さ"..."そ":
    print("\(someCharacter)はさ行です")
case "た"..."と":
    break
default:
    print("\(someCharacter)は母音でも子音でもありません")
}
//何も表示されない

この例では「た行」の文字の場合、何もしない(breakする)という制御をしています。continueの場合ループ内でしか実装できませんが、breakはswitch文の条件をスキップしたい場合に使用できるので便利です。

Fallthrough

fallthroughはswitch文でのみ使える制御転送文です。これは

caseの条件判定を無視して、次の条件判定を実行する

という制御をします。つまり、switchの制御をC言語仕様に戻すような制御転送文になります。

C言語に馴染みがないと分かりにくいかもしれませんが、使用例を見て頂くと納得できると思います。

// Fallthroughの使用例
var someCharacter = "う"
var description = "文字「\(someCharacter)」は"
switch someCharacter {
case "あ"..."ん":
    description += "ひらがなで、"
    fallthrough
default:
    description += "日本語です"
}
print(description)
//"文字「う」はひらがなで、日本語です"と表示

手抜きの例ですが、入力した文字の説明を付与するswitch文です。

通常caseの条件を満たすと、以降の条件を無視してswitchを抜けます(no implicit fallthrough)。ところが、fallthroughを挿入すると、その直後の条件(上記例の場合default)も実行します。

試しにカタカナや漢字を入力すると、fallthroughの動きが分かると思います。アルファベットだと出力が矛盾しますが、手抜き例ですのでご了承下さい。

ラベル付き制御構文

ラベル付き制御構文の基本構造

ループや条件分岐を入れ子にしていくと、特にbreakの制御がどのループや条件分岐を抜けるのか明確でない場合があります。そのような場合、ループや条件分岐にラベルを付けることによって、breakcontinueがどのループ・条件分岐を指すのかをはっきりさせることができます。

ラベル付き制御構文の基本構造は

label name: while condition {
    statements
}

のようになります。ループや条件分岐の前に「ラベル名label name」を付けて、コロン:で分けます。

ラベル付き制御構文の使用例

whileのページで作った超簡易版人生ゲームを改良したもので、ラベル付き制御構文の使用例を見てみます。公式マニュアルのボードゲームでも行っているように、「ぴったり最後のマス(32マス目)に止まった時のみゲーム終了」というルールを加えてみます。

// 超簡易版人生ゲーム改良版、ラベル付き
// ぴったり最後のマスに止まった時のみ終了

// ボードの総数
let maxBoardNumber = 33
let finalBoard = maxBoardNumber-1
// 各ボードのスコア
var board = Array(repeating: 1000, count: maxBoardNumber)

// スタートだけは0
board[0] = 0

var position = 0
var score = 0
print("スタート!")

jinseiGame: while position != finalBoard   {
    // サイコロをふる 1...6
    let diceRoll = Int(arc4random_uniform(6))+1
    
    switch position + diceRoll {
    case finalBoard:
        // ぴったり最後のマスに止まったのでゲーム終了
        print("最後のマスです(\(position+diceRoll)マス目)")
        break jinseiGame
    case let newBoard where newBoard > finalBoard:
        print("\(finalBoard)マスを超えたので、サイコロを振り直します(サイコロの目:\(diceRoll), 現在地:\(position))")
        continue jinseiGame

    default:
        // 出た目の数だけススム
        position += diceRoll
        
        // 得点を加算
        score += board[position]
    }
    
    // サイコロの目、現在の位置、スコアを表示
    print("サイコロの目:\(diceRoll)| 今は\(position)マス目です| スコア=\(score)")
}
print("ゴール! あなたのスコアは\(score)点です\n")

jinseiGameというラベルを付けてみました。

ゲーム終了の条件

先ほど書いたように、ゲーム終了の条件は「最後のマス(32マス目)にぴったり止まった」時ですから

// ループを止める条件
jinseiGame: while position != finalBoard   {
....
}

となります。finalBoardは新しく用意した定数です。毎回maxBoardNumber-1と書くのが面倒で作っただけですので必須ではありません。

条件分岐1 | ぴったり最後のマスに止まった場合

今回の肝はswitch内の条件判定部分です。

// switchで条件判定
....
    switch position + diceRoll {
    case finalBoard:
        // ぴったり最後のマスに止まったのでゲーム終了
        print("最後のマスです(\(position+diceRoll)マス目)")
        break jinseiGame
    case let newBoard where newBoard > finalBoard:
        print("\(finalBoard)マスを超えたので、サイコロを振り直します(サイコロの目:\(diceRoll), 現在地:\(position))")
        continue jinseiGame
....

最初のcaseがゲーム終了の条件判定です。ここでbreak jinseiGameとラベル指定してbreakしています。こうすることで、ラベルを付けたwhileループに対してbreakで制御しています。

もしラベルなしでbreakすると、単にswitch文を抜けてwhileループの先頭に戻るだけなので、いつまで経ってもゲームが終わりません。

条件分岐2 | 最後のマスを超えた場合

次のcaseはサイコロを振った後に、最後のマスを超えた場合の条件判定になっています。

    case let newBoard where newBoard > finalBoard:

ちょっと複雑ですが、まずlet newBoardというのはlet newBoard = position + diceRollと等価です。つまり、現在地を新しい定数で再定義しています。さらに、where newBoard > finalBoardで、今いるマス目が最後のマスを超えているかをチェックしています。

今回の例ですと、continue jinseiGameはラベルなしのcontinueでも構いません。continueはループに対する制御で、今ループは1つしかありませんから、continueが制御するループは自明だからです。

ただ、公式マニュアルにも書いてありますが、ラベルを付けることで可読性が上がり、ユーザに対して伝えたい意図が明確になります。ラベルが付いているループを取り扱う場合は、常にラベルを意識すると良いかもしれません。

Guard(Early Exit)

Early exitというのは「早期離脱」とか「早期脱出」とか訳せます。guard文を使用することによって、確実にプログラム(スコープ)から「離脱」する制御を実装できます。公式マニュアルではそれを「Early exit」と表現したのではないかと思います。

Guardの基本構文

guardは一言でいうと「elseが必須のif文」です。基本的な構文は

// guard文の基本構造
guard condition else {
    statements
}

となります。これはconditiontrueの時は何もせずプログラムを続行し、falseの時にstatementsを実行します。Optionalsの処理などに使えそうです。

次の使用例で具体的な使い方を説明しますが、guard文は

指定した条件を満たさなければプログラムを続行しない

という処理をしたい場合に使う制御です。If文でも実装可能ですが、elseの使用は任意ですから、書き方によっては意図しない制御になる可能性があります。guard文を利用することによって、可読性の向上と確実な条件指定、の両方を実装することができます。

Guardの使用例

タイトルをマークダウン形式にする関数で、guard制御を使ってみます。

// タイトルをマークダウン形式で
func titleMarkdown(text: [String: String]) {
    guard let title = text["title"] else {
        return
    }
    print("# \(title)")
    
    guard let contents = text["contents"] else {
        return
    }
    
    print("\(contents)")
}
titleMarkdown(text: ["title": "サンプルサイト1"])
titleMarkdown(text: ["title": "サンプルサイト2", "contents": "これはサンプルサイトです。"])
// 結果
//# サンプルサイト1
//# サンプルサイト2
//これはサンプルサイトです。

この例では、タイトルがhtmlでの<h1>に対応するとして、マークダウン形式の#を付けています。

関数(functions)は「機能をひとまとめにする箱」ようなものです。今、関数に入力されているのは連想配列Dictionaryです。最初のguardでは

guard let title = text["title"] else {
    return
}

というOptional Bindingによって「"title"というkeyを持つ要素を要求」しています。この条件を満たさないDictionaryが入力された場合は、このguard文でプログラムを止めます(厳密には関数を離脱するだけです)。returnは、おおざっぱには「関数の場合に使えるbreak」です。return制御に関しては、別のページで詳しく紹介します。

同様に、2番目のguard文ではcontentsというkeyがない場合には、そこで関数を抜ける仕様になっています。

まとめ

  • continueは「制御構文の処理をスキップし、先頭に戻る」という機能。ループ内でのみ使える
  • breakは「制御構文を直ちに抜ける」という機能。ループ・条件分岐のどちらでも使える
  • fallthroughswitchをC言語仕様にする(caseの条件判定を無視)
  • ラベル付き制御構文はループ・条件分岐が入れ子になっている場合に有効
  • guardは「指定した条件を満たさない限りプログラムを続行しない」という場合に有効

「Swiftの制御構文」に戻る