1. Тернарный оператор
Когда вы только начинаете писать условия, кажется, что if/else закрывает вообще всё: захотел — проверил, захотел — вывел одно, захотел — другое. И это правда. Но иногда мы не хотим «выполнить набор действий», а хотим быстро выбрать одно значение из двух и присвоить его переменной, подставить в строку или передать дальше. Тут и появляется тернарный оператор ?: — как маленькая «развилка на один шаг».
Важно сразу договориться о роли тернарного оператора: он не «заменяет if», он просто даёт более короткую запись для очень конкретного случая. Если пытаться им заменить все ветвления, код начнёт выглядеть как шифровка, а вы — как человек, который «оптимизировал» читаемость до нуля.
Синтаксис condition ? valueIfTrue : valueIfFalse
Тернарный оператор в Swift читается так: «если условие истинно — возьми первое значение, иначе — второе». И это именно оператор выбора значения. Он похож на мини-if, только в формате выражения (expression), то есть его результат можно присвоить переменной.
Синтаксис выглядит так:
condition ? valueIfTrue : valueIfFalse
?: в Swift
Условие (condition) должно иметь тип Bool. То есть туда нельзя поставить число «0/1», как иногда делают в других языках. Swift в этом месте строгий и прав: если вы хотите «да/нет», пишите «да/нет», а не «ну примерно да».
Мини-пример:
let age = Int(readLine() ?? "") ?? 0
let status = age >= 18 ? "adult" : "minor"
print(status) // при вводе 20: adult
Здесь age >= 18 — это Bool, а status получает одну из двух строк.
?: — про значение, а не про действия
Самая частая ошибка новичка — пытаться использовать ?: как «короткий if, чтобы сделать что-то». Но ?: лучше воспринимать как «выбор результата», а не «выбор поведения». Если вам нужно сделать две разные последовательности действий (например, два print(), две проверки, два ввода) — это зона if/else, и не надо делать себе мозги.
Хороший пример «выбора значения»:
let n = Int(readLine() ?? "") ?? 0
let parity = n % 2 == 0 ? "even" : "odd"
print("n is \(parity)") // при вводе 7: n is odd
А вот пример того, где тернарный оператор уже выглядит подозрительно: когда внутри веток начинается «мини-сценарий» (особенно если там несколько операций). Даже если это технически компилируется, читателю придётся «распаковывать» в голове обе ветки, и в этот момент выигрыша по краткости уже нет.
Обе ветки должны быть совместимого типа
В if/else вы можете в одной ветке сделать print("Hi"), а в другой — print("Bye"), и всё нормально, потому что if — это конструкция управления потоком выполнения, а не «вычислитель значения». У тернарного оператора всё иначе: он должен вернуть одно значение, значит Swift обязан понимать, какого оно типа.
Поэтому правило простое: обе ветки должны быть одного типа (или хотя бы совместимого), иначе компилятор скажет: «Извини, я не телепат».
Рабочий пример (везде строки):
let command = readLine() ?? ""
let response = command == "exit" ? "Bye!" : "Unknown command"
print(response)
Рабочий пример (везде числа):
let a = Int(readLine() ?? "") ?? 0
let b = Int(readLine() ?? "") ?? 0
let maxValue = a > b ? a : b
print("max = \(maxValue)")
А теперь типичная «не-а»:
// Так делать нельзя: в одной ветке String, в другой Int
let x = condition ? "text" : 10
Если вы вдруг упёрлись в такое, это обычно сигнал, что либо вы выбрали неправильный тип результата, либо вам лучше подойдёт if/else и отдельные print()/переменные.
2. Практические сценарии
?: внутри интерполяции строк
Одна из самых приятных областей применения ?: — это формирование короткого фрагмента текста внутри строки. Мы часто хотим написать «доступ разрешён/запрещён», «чётное/нечётное», «OK/FAIL», не раздувая код дополнительными переменными и ветками.
Например, делаем маленький «статус» для пользователя:
let score = Int(readLine() ?? "") ?? 0
print("Result: \(score >= 60 ? "PASS" : "FAIL")")
// при вводе 75: Result: PASS
Это выглядит нормально, потому что обе ветки короткие и легко читаются. Здесь тернарный оператор работает как «вставка одного слова».
Ещё пример — аккуратное сообщение про диапазон:
let x = Int(readLine() ?? "") ?? 0
let label = (x >= 1 && x <= 10) ? "in range" : "out of range"
print("x is \(label)")
Скобки вокруг условия не обязательны, но иногда полезны просто для визуальной группировки, особенно если условие длиннее, чем «одна проверка».
Когда ?: уместен, а когда лучше if/else
Когда вы учитесь, хочется иметь простое правило вроде «тернарный — всегда плохо» или «тернарный — всегда круто». Но реальность скучнее: почти любой инструмент становится плохим, если им пользоваться как молотком, когда перед вами винт.
Ниже — компактная таблица для ориентира:
| Ситуация | Как лучше | Почему |
|---|---|---|
| Нужно выбрать одно короткое значение (слово/число) | |
Код короче, смысл не теряется |
| Нужно выполнить несколько действий в ветке | |
Тернарный превращается в кашу |
| Условие длинное и сложное | чаще или вынести |
Читаемость важнее компактности |
| Ветки длинные (несколько операций/сложные вычисления) | |
Проще отлаживать и читать |
Хочется вложить в |
почти всегда |
Вложенность плохо читается |
Эта таблица — не «закон Swift», а здравый смысл. Если ваш код читается быстро и однозначно — вы молодец. Если нет — значит, пора упрощать.
Мини-пример для CLI-приложения
Чтобы примеры были не отдельными «обрывками», давайте продолжим развивать условный каркас нашего будущего CLI-приложения. Пока без циклов, без массивов и без функций — просто один запуск, один ввод, одна реакция. Представим, что это прототип LibraryCLI, который позже научится большему, а сейчас хотя бы здоровается и понимает команду выхода.
print("Enter command:")
let command = readLine() ?? ""
let message = command == "exit" ? "Shutting down..." : "Unknown command: \(command)"
print(message)
Здесь ?: очень уместен: мы выбираем одну строку для вывода. Никаких побочных эффектов, никаких трюков — просто выбор текста.
Теперь добавим маленькую «валидацию» для пустого ввода:
print("Enter command:")
let command = readLine() ?? ""
let normalized = command.isEmpty ? "<empty>" : command
print("You typed: \(normalized)")
Пока это выглядит как «косметика», но в реальных CLI-программах такие мелочи делают вывод понятнее, а диагностику — дружелюбнее. И да, строка "<empty>" — это не магия, просто удобная метка.
Вычисляется только выбранная ветка
В прошлых лекциях мы обсуждали short-circuit у && и ||: правая часть может не вычисляться. У тернарного оператора похожая идея: вычисляется только одна ветка — та, которую выбрало условие.
Это полезно, когда одна из веток потенциально «опасная» (например, может привести к ошибке деления на ноль), а вы хотите, чтобы она вообще не трогалась, если условие не подошло.
Небольшой пример с делением:
let a = Int(readLine() ?? "") ?? 0
let b = Int(readLine() ?? "") ?? 0
let resultText = (b == 0) ? "division by zero" : "a/b = \(a / b)"
print(resultText)
Если b == 0, правая ветка с a / b не вычисляется, и программа не падает. Тернарный оператор здесь выступает как «вежливый охранник на входе».
3. Читаемость и стиль
Вложенный ?: почти всегда ухудшает понимание
В какой-то момент кто-то обязательно попробует написать «красиво в одну строку»: три варианта ответа, значит два ?: подряд. Технически Swift это позволяет, но для чтения это часто катастрофа: мозгу приходится держать в памяти сразу несколько условий и веток, и ошибка в скобках превращается в баг, который выглядит «логично», но делает не то.
Представим задачу: по score вернуть "A", "B" или "C". Вложенный ?: возможен:
let score = Int(readLine() ?? "") ?? 0
let grade = score >= 90 ? "A" : (score >= 70 ? "B" : "C")
print(grade)
Это компилируется, но читается хуже, чем нормальная «лестница»:
let score = Int(readLine() ?? "") ?? 0
let grade: String
if score >= 90 {
grade = "A"
} else if score >= 70 {
grade = "B"
} else {
grade = "C"
}
print(grade)
Здесь выигрывает if/else if/else, потому что это уже не «выбор одного значения из двух», а «классификация по нескольким диапазонам». Тернарный оператор можно заставить это делать, но читателю будет некомфортно.
Приоритет операторов и скобки
«Компилируется» не значит «имеет тот смысл, который вы хотели». У тернарного оператора тоже есть приоритет, и он может смешиваться с &&, ||, +, - и так далее. Сейчас нам не нужно запоминать таблицу приоритетов, но стоит запомнить практическое правило: если выражение визуально становится двусмысленным — ставьте скобки или разбивайте на промежуточные let.
Например, вместо «героизма в одну строку» лучше так:
let age = Int(readLine() ?? "") ?? 0
let isAdult = age >= 18
let label = isAdult ? "adult" : "minor"
print("status: \(label)")
Так и приоритет не вызывает вопросов, и отладка проще: можно вывести isAdult отдельно, если что-то пошло не так.
Микро-рефакторинг: сначала Bool, потом ?:
Часто код становится лучше, если вы сначала даёте имя проверке, а уже потом выбираете значение. Это особенно полезно новичкам: вы учитесь мыслить не «кашей из операторов», а маленькими понятными шагами.
Сравните два варианта.
Вариант «всё в одну строку»:
let n = Int(readLine() ?? "") ?? 0
print(n % 2 == 0 ? "even" : "odd")
Вариант «с именем для смысла»:
let n = Int(readLine() ?? "") ?? 0
let isEven = n % 2 == 0
let text = isEven ? "even" : "odd"
print(text)
Второй вариант длиннее, но лучше объясняет себя сам. А если вы читаете код через две недели, вы скажете себе «спасибо», и это будет редкий момент гармонии между вами и вашим прошлым.
4. Типичные ошибки при использовании ?:
Ошибка №1: попытка заменить тернарным оператором полноценный if/else с действиями.
Когда в ветках появляются несколько строк кода, несколько print() или какая-то подготовка данных, тернарный оператор перестаёт быть «коротким выбором значения» и превращается в трюк ради трюка. В таких ситуациях if/else читается проще и отлаживается быстрее, потому что каждая ветка — это нормальный блок { ... }.
Ошибка №2: ветки разных типов.
Новички часто удивляются, почему нельзя написать «если true — строка, иначе — число». Причина в том, что ?: должен вернуть одно значение одного типа. Если вы столкнулись с такой проблемой, обычно стоит либо привести обе ветки к одному типу (например, обе сделать строками), либо перепроектировать логику: возможно, вам нужен if/else и разные действия.
Ошибка №3: вложенные ?: ради “красоты в одну строку”.
Вложенный тернарный оператор быстро становится нечитаемым: вы вынуждаете читателя держать в голове несколько условий и следить за скобками. Даже если вы уверены, что «и так понятно», через неделю это станет непонятно уже вам. Для трёх и более вариантов почти всегда лучше использовать if/else if/else.
Ошибка №4: слишком длинное условие прямо внутри ?:.
Если условие содержит несколько сравнений, &&, ||, отрицания ! и скобки, у читателя резко растёт шанс понять его не так. В таких ситуациях выигрывает подход «вынесли условие в let is...», а тернарный оператор оставили только для финального выбора значения.
Ошибка №5: использование ?: там, где важнее объяснить смысл, чем сократить код.
Тернарный оператор хорош, когда он делает код короче без потери ясности. Но если смысл условия неочевиден или ветки несут важную бизнес-логику (пусть даже в учебных примерах), лучше написать на 3–5 строк больше и сделать код «читаемым как текст». Swift вообще любит читаемость, так что это будет не просто «красиво», а по-идиоматически правильно.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ