Базовые операторы
Оператор — это специальный символ или выражение для проверки, изменения или сложения величин. Например, оператор сложения (+) суммирует два числа let i = 1 + 2, а логический оператор И && объединяет два логических значения if enteredDoorCode && passedRetinaScan.
Язык Swift поддерживает большинство стандартных операторов C, а также ряд возможностей для устранения типичных ошибок в коде. Оператор присваивания (=) не возвращает значение, что позволяет избежать путаницы с оператором проверки на равенство (==). Арифметические операторы (+, -, *, /, % и т. д.) могут обнаруживать и предотвращать переполнение типа, чтобы числовой переменной нельзя было присвоить слишком большое или слишком маленькое значение. Контроль переполнения типа включается в Swift специальными операторами, которые описаны в разделе Операторы переполнения.
Также в Swift имеются два сокращенных оператора интервала (a..<b и a...b), которых нет в C.
В этой главе описываются стандартные операторы Swift. Более сложные операторы Swift рассмотрены в главе Продвинутые операторы, где описано, как объявить пользовательские операторы и реализовать стандартные операторы для пользовательских типов.
Терминология
Операторы делятся на унарные, бинарные и тернарные:
- Унарные операторы применяются к одной величине (например, -a). Унарные префиксные операторы ставятся непосредственно перед величиной (например, !b), а унарные постфиксные операторы — сразу за ней (например, c!).
- Бинарные операторы применяются к двум величинам (например, 2 + 3) и являются инфиксными, так как ставятся между этими величинами.
- Тернарные операторы применяются к трем величинам. Как и в языке C, в Swift есть только один такой оператор, а именно — тернарный условный оператор (a ? b : c).
Величины, к которым применяются операторы, называются операндами. В выражении 1 + 2 символ + является бинарным оператором, а его операндами служат 1 и 2.
Оператор присваивания
Оператор присваивания (a = b) инициализирует или изменяет значение переменной a на значение b:
let b = 10
var a = 5
a = b
// теперь a равно 10
Если левая часть выражения является кортежем с несколькими значениями, его элементам можно присвоить сразу несколько констант или переменных:
let (x, y) = (1, 2)
// x равно 1, а y равно 2
В отличие от C и Objective-C оператор присваивания в Swift не может возвращать значение. К примеру, следующее выражение недопустимо:
if x = y {
// это неверно, так как x = y не возвращает никакого значения
}
Эта особенность не позволяет разработчику спутать оператор присваивания (=) с оператором проверки на равенство (==). Благодаря тому, что выражения типа if x = y некорректны, подобные ошибки при программировании на Swift не произойдут.
Арифметические операторы
Язык Swift поддерживает четыре стандартных арифметических оператора для всех числовых типов:
- сложение (+)
- вычитание (-)
- умножение (*)
- деление (/)
1 + 2 // равно 3
5 - 3 // равно 2
2 * 3 // равно 6
10.0 / 2.5 // равно 4.0
В отличие от C и Objective-C арифметические операторы Swift по умолчанию не допускают переполнения типа. Контроль переполнения типа включается в Swift специальными операторами (например, a &+ b). Подробнее см. в главе Операторы переполнения.
Оператор сложения служит также для конкатенации, или же склейки, строковых значений (тип String):
"hello, " + "world" // равно "hello, world"
Оператор целочисленного деления
Оператор целочисленного деления (a % b) показывает, какое количество b помещается внутри a, и возвращает остаток деления a на b.
Заметка
Оператор целочисленного деления (%) в некоторых языках называется оператором деления по модулю. Однако учитывая его действие над отрицательными числами в Swift, этот оператор, строго говоря, выполняет деление с остатком, а не по модулю.
Оператор целочисленного деления работает следующим образом. Для вычисления выражения 9 % 4 сначала определяется, сколько четверок содержится в девятке:
В одной девятке содержатся две четверки, а остатком будет 1 (выделено оранжевым цветом).
На языке Swift это записывается так:
9 % 4 // равно 1
Чтобы получить результат деления a % b, оператор % вычисляет следующее выражение и возвращает остаток:
a = (b × множитель) + остаток
где множитель показывает, сколько раз целых b содержится в a.
Подставляя в это выражение 9 и 4, получим:
9 = (4 × 2) + 1
Точно так же рассчитывается остаток, когда a отрицательно:
-9 % 4 // равно -1
Подставляя в наше выражение -9 и 4, получим:
-9 = (4 × -2) + -1
причем остаток будет равен -1.
Если b отрицательно, его знак отбрасывается. Это означает, что выражения a % b и a % -b всегда будут давать одинаковый результат.
Оператор унарного минуса
Для изменения знака числового значения служит префиксный минус -, который называется оператором унарного минуса:
let three = 3
let minusThree = -three // minusThree равно -3
let plusThree = -minusThree // plusThree равно 3, т. е. "минус минус три"
Оператор унарного минуса (-) ставится непосредственно перед значением, без пробела.
Оператор унарного плюса
Оператор унарного плюса (+) просто возвращает исходное значение без каких-либо изменений:
let minusSix = -6
let alsoMinusSix = +minusSix // alsoMinusSix равно -6
Хотя оператор унарного плюса не выполняет никаких действий, он придает коду единообразие, позволяя зрительно отличать положительные значения от отрицательных.
Cоставные операторы присваивания
Как и в языке C, в Swift имеются составные операторы присваивания, совмещающие простое присваивание (=) с другой операцией. Одним из примеров может служить оператор присваивания со сложением (+=):
var a = 1
a += 2
// теперь a равно 3
Выражение a += 2 является краткой формой записи a = a + 2. Таким образом, один и тот же оператор выполняет одновременно операцию сложения и присваивания.
Заметка
Составные операторы присваивания не возвращают значение. К примеру, нельзя написать так: let b = a += 2.
Для получения полного списка операторов присваивания, предусмотренных стандартной библиотекой Swift, см. Operator Declarations.
Операторы сравнения
Язык Swift поддерживает все стандартные операторы сравнения из C:
- равно (a == b)
- не равно (a != b)
- больше (a > b)
- меньше (a < b)
- больше или равно (a >= b)
- меньше или равно (a <= b)
Заметка
В языке Swift есть также два оператора проверки на идентичность/тождественность (=== и !==), определяющие, ссылаются ли два указателя на один и тот же экземпляр объекта. Дополнительную информацию см. в главе Классы и структуры.
Каждый оператор сравнения возвращает значение типа Bool, указывающее, является ли выражение истинным:
1 == 1 // истина, так как 1 равно 1
2 != 1 // истина, так как 2 не равно 1
2 > 1 // истина, так как 2 больше чем 1
1 < 2 // истина, так как 1 меньше 2
1 >= 1 // истина, так как 1 больше либо равно 1
2 <= 1 // ложь, так как 2 не меньше либо равно 1
Операторы сравнения часто используются в условных выражениях, включая инструкцию if:
let name = "world"
if name == "world" {
print("hello, world")
} else {
print("Мне жаль, \(name), но я тебя не узнаю")
}
// напечатает "hello, world", так как name очевидно равно "world"
Подробнее об инструкции if см. в главе "Управление потоком".
Вы так же можете сравнивать кортежи, которые имеют одно и то же количество значений, которые, в свою очередь, должны быть сравниваемыми, что означает, что кортеж типа (Int, String) может быть сравнен с кортежем такого же типа.
Кортежи сравниваются слева направо, по одному значению за раз до тех пор, пока операция сравнения не найдет отличия между значениями. Если все значения кортежей попарно равны, то и кортежи так же считаются равными. Например:
(1, "zebra") < (2, "apple") // true, потому что 1 меньше 2, "zebra" и "apple" не сравниваются
(3, "apple") < (3, "bird") // true , потому что 3 равно 3, а "apple" меньше чем "bird"
(4, "dog") == (4, "dog") // true , потому что 4 равно 4 и "dog" равен "dog"
В примере выше, в первой строке вы можете видеть сравнение слева направо. Так как 1 меньше 2, то (1, “zebra”) меньше (2, “apple”), несмотря на остальные значения кортежа, потому что это неравенство было определено первыми членами. Не важно, что “zebra” не меньше, чем “apple”, потому что сравнение уже определено первыми элементами кортежей. Однако, когда первые элементы кортежей одинаковые, то сравниваются вторые элементы и так далее.
Кортежи могут сравниваться, только в том случае, если оператор сравнения можно применить ко всем членам кортежей соответственно. Например, как показано в коде ниже, вы можете сравнить два кортежа типа (String, Int) потому что и String, и Int могут сравниться оператором <. И наоборот, кортеж типа (String, Bool) сравниваться не может, так как к значениям типа Bool операторы сравнения не применяются.
("blue", -1) < ("purple", 1) // OK, расценивается как true
("blue", false) < ("purple", true) // Ошибка так как < не может применяться к значениям типа Bool
Заметка
Стандартная библиотека Swift включает в себя операторы сравнения кортежей, которые имеют менее семи значений. Если вам нужны операторы, которые могут сравнивать кортежи с более, чем шестью элементами, то вам нужно реализовать их самостоятельно.
Тернарный условный оператор
Тернарный условный оператор — это специальный оператор из трех частей, имеющий следующий синтаксис: выражение ? действие1 : действие2. Он выполняет одно из двух действий в зависимости от того, является ли выражение true или false. Если выражение равно true, оператор выполняет действие1 и возвращает его результат; в противном случае оператор выполняет действие2 и возвращает его результат.
Тернарный условный оператор является краткой записью следующего кода:
if выражение {
действие1
} else {
действие2
}
Ниже приведен пример расчета высоты строки в таблице. Если у строки есть заголовок, то она должна быть выше своего содержимого на 50 точек, а если заголовка нет, то на 20 точек:
let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
// rowHeight равно 90
В развернутом виде этот код можно записать так:
let contentHeight = 40
let hasHeader = true
var rowHeight = contentHeight
if hasHeader {
rowHeight = rowHeight + 50
} else {
rowHeight = rowHeight + 20
}
// rowHeight равно 90
В первом примере с помощью тернарного условного оператора величине rowHeight в одну строку присваивается правильное значение. Этот вариант не только короче второго примера, но и позволяет объявить величину rowHeight константой, так как в отличие от конструкции if ее значение не нужно изменять.
Тернарный условный оператор — это короткая и удобная конструкция для выбора между двумя выражениями. Однако тернарный условный оператор следует применять с осторожностью. Избыток таких коротких конструкций иногда делает код трудным для понимания. В частности, лучше не использовать несколько тернарных условных операторов в одном составном операторе присваивания.
Оператор объединения по nil
Оператор объединения по nil (a ?? b) извлекает опционал a, если он содержит значение, или возвращает значение по умолчанию b, если a равно nil. Выражение a может быть только опционалом. Выражение b должно быть такого же типа, что и значение внутри a.
Оператор объединения по nil является краткой записью следующего кода:
a != nil ? a! : b
В вышеприведенном коде тернарный условный оператор и принудительное извлечение (a!) используются для обращения к значению внутри a, если a не равно nil, или для возвращения b в противном случае. Оператор объединения по nil — это более элегантный, короткий и понятный способ одновременно проверить условие и извлечь значение.
Заметка
Если a не равно nil, выражение b не анализируется. Такой подход называется краткой проверкой условия (short-circuit evaluation).
В следующем примере оператор объединения по nil выбирает между стандартным значением цвета и пользовательским:
let defaultColorName = "red"
var userDefinedColorName: String? // по умолчанию равно nil
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName равен nil, поэтому colorNameToUse получит значение по умолчанию — "red"
Переменная userDefinedColorName объявлена как строковый (String) опционал и по умолчанию равна nil. Так как userDefinedColorName является опционалом, ее значение можно анализировать посредством оператора объединения по nil. В вышеприведенном примере этот оператор задает начальное значение для строковой (String) переменной colorNameToUse. Так как userDefinedColorName равно nil, выражение userDefinedColorName ?? defaultColorName возвратит значение defaultColorName, т. е. "red".
Если переменной userDefinedColorName присвоить отличное от nil значение и снова передать ее в оператор объединения по nil, вместо значения по умолчанию будет использовано значение внутри userDefinedColorName:
userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName не равно nil, поэтому colorNameToUse получит значение "green"
Операторы диапазона
В языке Swift есть два оператора диапазона, которые в короткой форме задают диапазон значений.
Оператор замкнутого диапазона
Оператор замкнутого диапазона (a...b) задает диапазон от a до b, включая сами a и b. При этом значение a не должно превышать b.
Оператор замкнутого диапазона удобно использовать при последовательном переборе значений из некоторого диапазона, как, например, в цикле for-in:
for index in 1...5 {
print("\(index) умножить на 5 будет \(index * 5)")
}
// 1 умножить на 5 будет 5
// 2 умножить на 5 будет 10
// 3 умножить на 5 будет 15
// 4 умножить на 5 будет 20
// 5 умножить на 5 будет 25
Подробнее о циклах for-in см. в главе Управление потоком.
Оператор полузамкнутого диапазона
Оператор полузамкнутого диапазона (a..<b) задает диапазон от a до b, исключая значение b. Такой диапазон называется полузамкнутым, потому что он включает первое значение, но исключает последнее. Так же, как и для оператора замкнутого диапазона, значение a не должно превышать b. Если значение a равно значению b, то итоговый диапазон будет пустым.
Операторы полузамкнутого диапазона особенно удобны при работе с массивами и другими последовательностями, пронумерованными с нуля, когда нужно перебрать элементы от первого до последнего:
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
print("Person \(i + 1) будет \(names[i])")
}
// Person 1 будет Anna
// Person 2 будет Alex
// Person 3 будет Brian
// Person 4 будет Jack
Хотя в массиве четыре элемента, диапазон 0..<count доходит только до 3 (т. е. до номера последнего элемента в массиве), так как это оператор полузамкнутого диапазона. Подробнее о массивах см. в главе Массивы.
Односторонние диапазоны
Операторы замкнутого диапазона имеют себе альтернативу - это диапазон, который продолжается насколько возможно, но только в одну сторону, например, диапазон, который включает все элементы массива, начиная от 2 и до последнего индекса. В этих случаях вы можете пропустить значение с одной стороны оператора диапазона. Этот тип диапазона называется односторонним, потому что оператор имеет значение только с одной стороны. Например:
for name in names[2...] {
print(name)
}
// Brian
// Jack
for name in names[...2] {
print(name)
}
// Anna
// Alex
// Brian
Оператор полузамкнутого диапазона так же имеет одностороннюю форму, которая записывается только с одним конечным значением. Точно так же как и в случае, когда вы включаете значение в обе стороны, конечное значение не является частью самого диапазона. Например:
for name in names[..<2] {
print(name)
}
// Anna
// Alex
Односторонние диапазоны могут быть использованы в разных контекстах, а не только в сабскриптах. Вы не можете итерировать по одностороннему диапазону, который пропускает первое значение, потому что становится не очевидным, где должна начинаться итерация. Вы можете итерировать по одностороннему диапазону, который пропускает последнее значение, однако, так как диапазон длится бесконечно, убедитесь, что вы добавили условие окончание итерации в цикл. Вы так же можете проверить имеет ли односторонний диапазон конкретное значение, что показано ниже:
let range = ...5
range.contains(7) // false
range.contains(4) // true
range.contains(-1) // true
Логические операторы
Логические операторы изменяют или комбинируют логические значения типа Boolean (булево) — true и false. Язык Swift, как и другие C-подобные языки, поддерживает три стандартных логических оператора:
- логическое НЕ (!a)
- логическое И (a && b)
- логическое ИЛИ (a || b)
Оператор логического НЕ
Оператор логического НЕ (!a) инвертирует булево значение — true меняется на false, а false становится true.
Оператор логического НЕ является префиксным и ставится непосредственно перед значением, без пробела. Как видно из следующего примера, его можно воспринимать как "не allowedEntry":
let allowedEntry = false
if !allowedEntry {
print("ACCESS DENIED")
}
// Выведет "ACCESS DENIED"
Конструкция if !allowedEntry означает "если не allowedEntry". Идущая за ней строка будет выполнена, только если "не allowedEntry" является истиной, т. е. если allowedEntry равно false.
Как видно из этого примера, удачный выбор булевой константы и имен переменных делает код коротким и понятным, без двойных отрицаний и громоздких логических выражений.
Оператор логического И
Оператор логического И (a && b) дает на выходе true тогда и только тогда, когда оба его операнда также равны true.
Если хотя бы один из них равен false, результатом всего выражения тоже будет false. На самом деле, если первое значение равно false, то второе даже не будет анализироваться, так как оно все равно не изменит общий результат на true. Такой подход называется краткой проверкой условия (short-circuit evaluation).
В следующем примере проверяются два значения типа Bool, и если они оба равны true, программа разрешает доступ:
let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// Выведет "ACCESS DENIED"
Оператор логического ИЛИ
Оператор логического ИЛИ (a || b) является инфиксным и записывается в виде двух вертикальных палочек без пробела. С его помощью можно создавать логические выражения, которые будут давать true, если хотя бы один из операндов равен true.
Как и описанный выше оператор логического И, оператор логического ИЛИ использует краткую проверку условия. Если левая часть выражения с логическим ИЛИ равна true, то правая не анализируется, так как ее значение не повлияет на общий результат.
В приведенном ниже примере первое значение типа Bool (hasDoorKey) равно false, а второе (knowsOverridePassword) равно true. Поскольку одно из значений равно true, результат всего выражения тоже становится true и доступ разрешается:
let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// Выведет "Welcome!"
Комбинирование логических операторов
Можно также составлять и более сложные выражения из нескольких логических операторов:
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// Выведет "Welcome!"
В этом примере с помощью нескольких операторов && и || составляется более длинное и сложное выражение. Однако операторы && и || по-прежнему применяются только к двум величинам, поэтому все выражение можно разбить на три простых условия. Алгоритм работы будет следующим:
если пользователь правильно ввел код дверного замка и прошел сканирование сетчатки или если он использовал действующую ключ-карту или если он ввел код экстренного доступа, то дверь открывается.
Исходя из значений enteredDoorCode, passedRetinaScan и hasDoorKey первые два подусловия дают false. Однако был введен код экстренного доступа, поэтому все составное выражение по-прежнему равно true.
Заметка
Логические операторы Swift && и || являются лево-ассоциированными, что означает, что составные выражения с логическими операторами оценивают в первую очередь выражения слева направо.
Явное указание круглых скобок
Иногда имеет смысл использовать дополнительные круглые скобки, чтобы сложное логическое выражение стало проще для восприятия. В примере с открытием двери можно заключить в круглые скобки первую часть составного выражения, что сделает его нагляднее:
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// Выведет "Welcome!"
Круглые скобки показывают, что первые две величины составляют одно из возможных значений всего логического выражения. Хотя результат составного выражения не изменится, такая запись сделает код понятнее. Читаемость кода всегда важнее краткости, поэтому желательно ставить круглые скобки везде, где они облегчают понимание.