Зачем нужны операторы
Операторы — это привычные значки вроде +, -, > или &&, которые превращают одно или два значения в новое. Вы уже пользовались арифметикой и делением, но у Swift есть ещё несколько операторов, без которых не обойтись почти ни в одной программе: сравнение чисел, проверка сразу нескольких условий и работа с диапазонами значений. В этом уроке разберём операторы, которые понадобятся дальше — при ветвлении и циклах, — ещё до того, как вы впервые напишете if.
Составное присваивание
Часто нужно не просто задать переменной значение, а изменить то, что в ней уже лежит: прибавить к счётчику единицу, увеличить сумму заказа. Писать это в два действия неудобно и легко ошибиться, поэтому у Swift есть короткая запись — составное присваивание:
var score = 10
score += 5 // то же самое, что score = score + 5
print(score)
Здесь напечатается 15. Точно так же работают -=, *= и /= — каждый берёт текущее значение переменной, применяет к нему операцию и сразу сохраняет результат обратно. Обратите внимание: составное присваивание работает только с var — у let изменить значение после создания нельзя, компилятор откажется собирать такой код.
Остаток от деления и отрицательные числа
Оператор % вы уже видели: он даёт остаток от целочисленного деления. С положительными числами всё интуитивно — 17 % 5 равно 2. С отрицательными числами Swift ведёт себя не так, как в математике на бумаге: знак результата совпадает со знаком делимого, а не всегда положителен.
let a = -17
let b = 5
print(a % b)
Здесь напечатается -2, а не 3, хотя в школьной математике «правильным» остатком от деления -17 на 5 иногда называют именно 3. Swift считает иначе: он ищет такое частное, при делении на которое с отбрасыванием дробной части и последующим умножением обратно получится остаток того же знака, что и делимое. Если вам нужен остаток, который всегда лежит в диапазоне от нуля до делителя, его придётся посчитать самостоятельно — этим как раз займётесь в одной из задач урока.
Операторы сравнения
Сравнение двух значений всегда даёт результат типа Bool — true или false, и ничего третьего. Doступны шесть операторов: == (равно), != (не равно), <, >, <=, >=.
let age = 17
let isAdult = age >= 18
let sameAge = age == 17
print(isAdult)
print(sameAge)
Здесь isAdult получит false, а sameAge — true. Легко перепутать = и ==: одиночный знак равенства — это присваивание, двойной — сравнение. В Swift такая путаница почти всегда приведёт к ошибке компиляции, а не к тихому багу, потому что присваивание не возвращает значение, которое можно было бы использовать как условие.
Логические операторы и короткое замыкание
Когда одного сравнения мало и нужно объединить несколько условий, используются логические операторы: && (И — верно, только если верны оба условия), || (ИЛИ — верно, если верно хотя бы одно) и ! (НЕ — переворачивает значение на противоположное).
let hasTicket = true
let age = 15
let canEnter = hasTicket && age >= 18
print(canEnter)
canEnter получит false, потому что второе условие не выполняется, хотя билет есть. Важная особенность && и || — короткое замыкание: Swift вычисляет операнды слева направо и останавливается, как только результат уже понятен, даже не трогая то, что справа.
func isPositive(_ n: Int) -> Bool {
print("проверяю \(n)")
return n > 0
}
let result = false && isPositive(5)
Строка «проверяю 5» не напечатается вообще: && увидел слева false и сразу понял, что весь результат — false, независимо от того, что справа, поэтому isPositive даже не вызывается. Аналогично для ||: если слева уже true, правый операнд пропускается — результат всё равно true. Это не просто оптимизация — на этом поведении часто специально строят код, например сначала проверяют, что значение существует, и только потом, зная, что оно безопасно, обращаются к нему во втором условии.
Диапазоны как значения
Диапазон — это готовое значение, которое описывает набор чисел от одной границы до другой, без перечисления их вручную. Замкнутый диапазон a...b включает обе границы, а полуоткрытый a..<b включает нижнюю границу, но не включает верхнюю:
let closed = 1...5
let halfOpen = 1..<5
print(closed.contains(5))
print(halfOpen.contains(5))
Первая строка напечатает true — пятёрка входит в замкнутый диапазон. Вторая напечатает false — полуоткрытый диапазон заканчивается перед пятёркой. Пока вы не проходили циклы, поэтому диапазон здесь используется не для перебора, а как самостоятельное значение: его можно сохранить в переменную и спросить методом contains, попадает ли в него число — это ровно то, что вам пригодится в задачах этого урока. Обратите внимание: у диапазона a...b и a..<b нижняя граница не может быть больше верхней — такой диапазон Swift не даст создать, программа упадёт во время выполнения.
Приоритет операций и когда скобки обязательны
Как и в математике, у операторов есть приоритет: умножение и деление выполняются раньше сложения и вычитания, сравнение — раньше логических операторов, а && — раньше ||.
let temperature = 22
let isComfortable = temperature > 18 && temperature < 26
print(isComfortable)
Здесь сначала посчитаются оба сравнения, а уже потом — && между ними, скобки вокруг сравнений не нужны, порядок и без них однозначен. Но как только в одном выражении смешиваются && и ||, полагаться на память о приоритетах рискованно — код становится трудно читать, и разработчик, который откроет его через месяц, потратит время на то, чтобы понять порядок:
let hasDiscount = true
let isWeekend = false
let isVip = false
let finalPrice = hasDiscount || isWeekend && isVip
Формально это выражение равно hasDiscount || (isWeekend && isVip), потому что && связывает операнды крепче, чем ||. Но угадывать это по памяти не нужно: скобки здесь не меняют результат, зато делают его очевидным с первого взгляда. Правило простое — если в выражении встретились одновременно && и ||, ставьте скобки вокруг вложенной части, даже если формально они не обязательны.
В Swift нет неявного приведения к Bool
Отдельно стоит закрепить то, что уже упоминалось раньше: условие обязано быть значением типа Bool, и никакой автоматической замены не существует. Число, строка или диапазон сами по себе условием быть не могут, даже если очень хочется:
let attempts = 0
// let canRetry: Bool = attempts // ошибка компиляции: Int — не Bool
let canRetry = attempts > 0
Вторая строка компилируется, потому что оператор сравнения сам возвращает Bool — именно так и получают логическое значение из числа: не приведением типа, а явной проверкой.
Частые ошибки
- Ждать, что
%с отрицательным числом всегда даст положительный результат — знак остатка совпадает со знаком делимого. - Перепутать
=и==— присваивание и сравнение выглядят похоже, но делают разное; впрочем, компилятор Swift обычно ловит эту ошибку сам. - Считать, что правый операнд
&&или||вычисляется всегда — из-за короткого замыкания это не так, и на это нельзя полагаться, если правая часть должна выполниться в любом случае. - Перепутать
...и..<— из-за этого граничное значение либо лишний раз входит в диапазон, либо неожиданно выпадает из него. - Смешивать
&&и||без скобок в сложном условии — код формально верный, но трудно проверяемый на глаз. - Пытаться подставить число или строку вместо
Boolтам, где ожидается условие.
Резюме
- Составное присваивание
+=,-=,*=,/=сокращает запись «взять значение, изменить, сохранить обратно»; работает только сvar. - Остаток
%для отрицательных чисел берёт знак делимого, а не всегда положителен. - Операторы сравнения
==!=<><=>=всегда возвращаютBool. - Логические
&&,||,!объединяют условия;&&и||вычисляются с коротким замыканием — правый операнд пропускается, если результат уже ясен по левому. - Диапазон
a...bвключает обе границы,a..<bне включает верхнюю; диапазон — это значение, у него есть методcontains. - При смешивании
&&и||в одном выражении ставьте скобки — они не обязательны формально, но обязательны для читаемости. - Условие всегда должно быть типом
Bool— неявного приведения чисел или строк кBoolв Swift не существует.
Проверьте себя
4 вопроса
Всегда неотрицательный остаток
В Swift оператор % может вернуть отрицательное число, если делимое отрицательное. Напишите функцию positiveRemainder(_ n: Int, _ m: Int) -> Int, которая возвращает остаток от деления n на m, но всегда в диапазоне от 0 до m - 1 включительно (можно считать, что m всегда больше нуля). Используйте только арифметические операторы и %, без циклов и условных операторов — они ещё не изучены.
Проверка процента
Напишите функцию isValidPercentage(_ value: Int) -> Bool, которая возвращает true, если value лежит в диапазоне от 0 до 100 включительно, и false в противном случае. Решите задачу через сравнение и логический оператор && либо через диапазон и его метод contains — без циклов и условных операторов.