Основы Swift / 7.3. Инструкции передачи управления


Видео


Инструкции передачи управления
Инструкции передачи управления изменяют порядок, в котором Ваш код выполняется, путём передачи управления из одной части кода в другую. Swift содержит пять инструкций передачи управления:
  • continue
  • break
  • fallthrough
  • return
  • throw
Инструкции continue, break и fallthrough описаны в данной главе.
continue
Инструкция continue приказывает циклу остановить своё выполнение и начать заново с новой итерации. Он означает дословно следующее: "Я завершил текущий шаг цикла." без выхода из цикла в целом.

Пример демонстрирует удаление всех гласных и пробелов из строки нижнего регистра для создания криптографически зашифрованной фразы:
@{7.3\1}
let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]
for character in puzzleInput.characters {
    if charactersToRemove.contains(character) {
        continue
    } else {
        puzzleOutput.append(character)
    }
}
print(puzzleOutput)
// Выведет "grtmndsthnklk"
Код вызывает ключевое слово continue, когда происходит совпадение с гласной буквой или пробелом, вызывая немедленное прекращение текущей итерации и переход непосредственно в начало следующей итерации.
break
Инструкция break заканчивает исполнение всей инструкции немедленно. Инструкция break может быть использована внутри switch или цикла, когда Вы хотите терминировать их выполнение раньше, чем это должно было случиться.

Будучи использованным в циклах, break заканчивает выполнение цикла немедленно и передаёт управление в код сразу после закрывающей фигурной скобки (}). Никакой дальнейший код из текущей итерации не будет выполнен, а никакие будущие итерации цикла не будут выполнены.

Будучи использованным внутри инструкции switch, break вызывает завершение выполнения инструкции switch немедленно и передают управление в код сразу после закрывающей фигурной скобки инструкции switch (}).

Это поведение может быть использовано для выделения и игнорирования одного или нескольких кейсов в инструкции switch. Так как инструкция switch в Swift исчерпывающа и не допускает пустых кейсов, то иногда бывает необходимым принудительно выделить и проигнорировать кейс, дабы сделать Ваши намерения явными. Вы делаете это с помощью написания инструкции break в качестве всего тела кейса, который Вы хотите проигнорировать. Когда этот кейс выбран инструкцией switch, инструкция break внутри кейса прекращает выполнение всей инструкции switch немедленно.

Кейс switch, содержащий лишь комментарий, будут помечен в качестве ошибки времени выполнения. Комментарии не являются инструкциями и не могут вызвать игнорирование кейса switch. Всегда используйте инструкцию break, чтобы проигнорировать кейс switch.

Следующий пример использует значение Character и определяет, когда он представляет числовой символ в одном из четырёх языков. Для краткости различные значения покрываются одним кейсом switch.
@{7.3\2}
let numberSymbol: Character = "三"  // Цифра 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).")
}
// Выведет "The integer value of 三 is 3."
В этом примере проверяется numberSymbol для определения того, является ли он Латинским, Арабским, Китайским или Тайским символом для чисел от 1 до 4. Если совпадение найдено, то один из кейсов инструкции switch устанавливает опциональной переменной типа Int? с названием possibleIntegerValue подходящее целое значение.

После того, как инструкция switch завершает своё выполнение, в примере используется опциональная привязка для определения того, найдено ли значение. possibleIntegerValue имеет неявно заданное изначальное значение nil ввиду опциональности типа, так что опциональное связывание будет имеет успех, только если possibleIntegerValue получит какое-то значение в одном из первых четырёх кейсов инструкции switch.

Так как это не очень практично перечислять все возможные значения типа Character в примере выше, то кейс default обрабатывает все символы, которые не имеют совпадения с другими кейсами. Кейсу default нет необходимости выполнять какие-либо действия, так что он записывается в качестве единственной инструкции break в качестве тела. Как только кейс default получен, инструкция break заканчивает выполнение всей инструкции switch, а выполнение кода продолжится с инструкции if let.
Инструкция fallthrough
В Swift инструкция switch не проваливается внизу каждого кейса и в следующий кейс. Это значит, что вся инструкция switch завершает своё выполнение, как только первый подходящий кейс буде завершён. Напротив, С требует от Вас явной вставки инструкции break в конце каждого кейса switch для препятствования такому поведению. Избежание провала по-умолчанию означает, что инструкции switch в Swift более краткие и предсзкауемые, чем их аналоги в C, а также они позволяют избегать выполнения нескольких кейсов по ошибке.

Если Вам нужно C-подобное поведение провала, Вы можете добиться его с помощью ключевого слова fallthrough. Пример демонстрирует использование falltrough для создания текстового описания числа:
@{7.3\3}
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."
В этом примере объявляется новая переменная типа String под названием description, и ей присваивается изначальное значение. Затем функция определяет значение integerToDescribe с использованием инструкции switch. Если значение integerToDescribe является одним из простых чисел в списке, то функция прибавляет текст в конец description, чтобы отметить, что число простое. Здесь используется ключевое слово fallthrough для "падения в" кейс default. Кейс default добавляет некоторый дополнительный текст в конец описания, и выполнение инструкции switch завершается.

Как только значение integerToDescribe оказывается в списке известных простых чисел, то оно не совпадёт с первым кейсом switch в принципе. Так как не определено никаких других специальных кейсов, то integerToDescribe попадёт в кейс default.

После того, как инструкция switch завершила своё выполнение, то описание числа печатается с использованием функции print(_:separator:terminator:). В этом примере число 5 корректно идентифицируется в качестве простого числа.

Ключевое слово fallthrough не проверяет условия кейса switch, а просто передаёт управление вниз. Ключевое слово fallthrough просто переводит выполнение кода прямо внутрь инструкций следующего кейса (или кейса default), что является стандартным поведением инструкции switch в C.
Маркированные инструкции
В Swift Вы можете вкладывать циклы и условные инструкции внутрь других циклов и условных инструкций для создания более сложных структур управления ходом выполнения. Однако, и циклы, и условные инструкции могут использовать инструкцию break для принудительного завершения их выполнения. Поэтому иногда бывает полезным явно знать, какой цикл или условная инструкция будет терминирована инструкцией break. Аналогично, если Вы имеете несколько вложенных циклов, то может быть полезным явно знать, на какой цикл инструкция continue окажет эффект.

Для достижения этих целей Вы можете пометить инструкцию цикла или условную инструкцию с помощью инструкции ярлыка. С условной инструкцией Вы можете использовать инструкцию ярлыка с инструкцией break для завершения выполнения помеченной инструкции. С инструкцией цикла Вы можете использовать инструкцию пометки с инструкциями break или continue для завершения выполнения или продолжения выполнения помеченных инструкций.

Помеченная инструкция определяется размещением ярлыка на той же строчке, что и вводное слово инструкции, отделённое двоеточием. Здесь представлен пример этого синтаксиса для цикла while, хотя этот принцип и одинаков для всех циклов и инструкций switch.

Следующий пример демонстрирует использование инструкций break и continue с маркированным циклом while в адаптированной версии игры Змеи и лестницы, которую Вы уже видели ранее в этой главе. В этот раз игра будет иметь одно дополнительное правило:

Чтобы победить, Вы должны приземлиться непосредственно на клетку 25.

Если отдельный бросок кубика переместит Вас за квадрат 25, то Вы должны бросать кубик заново до тех пор, пока на нём не выпадает точное число, необходимое для перемещения в клетку 25.

Игровое поле осталось таким же, как прежде.
Рисунок 7.3.1
@{7.3\4\1}
Значения 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
@{7.3\4\2}
gameLoop: while square != finalSquare {
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    switch square + diceRoll {
    case finalSquare:
        // diceRoll передвинет нас на финальную клетку, так что игра окончена
        break gameLoop
    case let newSquare where newSquare > finalSquare:
        // diceRoll передвинет нас за пределы игрового поля, так что бросим ещё раз
        continue gameLoop
    default:
        // это корректный ход, так что узнаем его эффект
        square += diceRoll
        square += board[square]
    }
}
print("Game over!")
Эта версия игры использует цикл while и инструкцию switch для реализации логики игры. Цикл while имеет инструкцию маркера с названием gameLoop для индикации того, что это главный цикл игры.

Условие цикла while имеет вид while square != finalSquare для отражения того, что Вы должны оказаться ровно на квадрате 25.

Кубик бросается в начале каждого шага цикла. Вместо немедленного передвижения игрока цикл использует инструкцию switch для определения результата перемещения и определения, когда разрешено движение:
1. Если бросок кубика передвинет игрока на финальную клетку, то игра будет окончена. Инструкция break gameLoop передаёт управление первой строчке кода за пределами цикла while, который завершает игру.
2. Если бросок кубика, переместит игрока за пределы финального квадрата, то такой ход некорректен и игроку требуется делать бросок заново. Инструкция continue gameLoop завершает текущую итерацию цикла while и начинает новую.
3. Во всех остальных случаях бросок кубика равен корректному ходу. Игрок перемещается вперёд на число квадратов, равное diceRoll, и логика игры производит проверку на наличие змей и лестниц. Затем цикл завершается, а управление возвращается условию цикла while для определения того, нужен ли ещё один ход.

Если инструкция break выше не использовала бы маркер gameLoop, то она бы вышла за пределы инструкции switch, а не while. Использование маркера gameLoop делает более понятным, какая инструкция управления должна быть терминирована.

Нет строгой необходимости использовать маркер gameLoop при вызове continue gameLoop для перехода на следующую итерацию цикла. В игре есть всего один цикл, а значит не существует и неопределенности, на какой цикл повлияет инструкция continue. Однако, нет ничего плохого в использовании маркера gameLoop с инструкцией continue. Делая так, мы делаем нашу логику игры понятнее для чтения и понимания.