Сюрпризы унарного минуса

Особенности использования унарных операторов в языке программирования Swift
28 июня 2017F0a70d847660e5d6f039bc885e4d0c203ae64fe4max mas2939427

Другие статьи из серии «Конспекты начинающего программиста»:

Небольшая предыстория

При изучении темы "Операторы" унарные операторы обычно рассматриваются в начале или где-то бегло по ходу, поскольку они вроде простые и излагать там особо нечего. В данном же случае обнаружились некоторые сюрпризы, о которых не говорится в учебнике по Swift. Поэтому представляется удобным выделить их в отдельный текст.

Всё началось с того, что захотелось вживую в XCode прописать примеры из учебника. И вот что из этого вышло.

Что такое унарный минус?

Для начала напомним, что такое унарный минус, а точнее - что о нём говорится в учебнике. Унарный минус - это унарный оператор, который служит для изменения знака числового значения.

Пример 1: Каждый раз новые константы

И далее в учебнике даётся следующий пример, который цитируется во многих других текстах:

let three = 3
let minusThree = -three // minusThree равно -3
let plusThree = -minusThree // plusThree равно 3, т. е. "минус минус три"
 

Всё красиво и вроде понятно. Берём константу с числовым положительным значением, присваиваем ей унарный минус, получается отрицательное число. Далее, добавляем ещё один унарный минус и получаем снова плюс, т.е. по известному из арифметики правилу - минус на минус, получаем плюс.

Но есть одна особенность в этом примере, а именно - на каждом шаге объявляется новая константа! Вроде бы ничего такого, но, наверное, авторы учебника что-то знали! И решили пока не загружать умы учеников сложной информацией. Но ученики тоже, бывает, замечают, что что-то не вписывается в гладкую последовательность изложения.

Пример 2: Меняем знак на одной и той же переменной

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

  1. var a = 1 // Переменная а равна 1
  2. -a // Переменная а равна -1

Пока всё идёт в штатном режиме. Но если попробуем пойти дальше и ещё раз сменим знак, то получим сюрприз:

  1. var a = 1 // Переменная а равна 1
  2. -a // Переменная а равна -1
  3. -a // Переменная а равна -1

Т.е. второго изменения знака - с минуса на плюс - не происходит! Правило "минус на минус даёт плюс" в данном случае не срабатывает! 

Почему это происходит?

Гипотез было несколько. Первая уже названа: что в первом примере на каждое применение унарного минуса создавалась новая константа, а во втором примере унарный минус использовался на одной и той же переменной. Но что потом делать с этой гипотезой не понятно, потому что она просто закрывает все дальнейшие вопросы, ничего не объясняя.

Вторая гипотеза: унарный минус действует только один раз, а второе его применение использует значение не из предыдущей строки, а из строки, где переменная объявляется. Кстати, если продолжать ставить минусы, то значение не меняется, везде будет минус 1. Это тоже такая гипотеза методом "научного тыка": а вдруг что-то произойдет, если ставить минусы дальше? Не происходит!

Следующее экспериментальное действие: а если просто указать переменную без минуса, что тогда будет? По идее в песочнице должно появиться значение переменной на данный момент. Т.е. мы его меняли, меняли, а теперь хотим посмотреть, какое текущее значение у этой переменной. Смотрим:

  1. var a = 1 // Переменная а равна 1
  2. -a // Переменная а равна -1
  3. -a // Переменная а равна -1
  4. -a // Переменная а равна -1
  5. a // Переменная а равна 1

И снова сюрприз: мы-то ожидали, что значение переменной изменилось во 2-й строке (а потом и в 3-й, и в 4-й), что оно теперь минус один, а на самом деле оно просто один, без минуса! И опять непонятно почему это происходит?

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

Следующее предположение: а что если нужно поставить второй минус? Может один минус это просто повторение операции смены положительного числа на отрицательное, а чтобы сменить отрицательное на положительное надо ещё один минус? По ходу вспоминается, что где-то упоминались двойные операторы, типа два плюса или два минуса, но сейчас пока не до них, просто пробуем:

  1. var a = 1 // Переменная а равна 1
  2. -a // Переменная а равна -1
  3. -a // Переменная а равна -1
  4. -a // Переменная а равна -1
  5. a // Переменная а равна 1
  6. //--a //  XCode выдает ошибку ‘--‘ is unavailable: it has been removed in Swift 3

Ничего не получилось. Но хотя бы по ходу узнаём, что двойные минусы совсем удалены в третьем Swift. Т.е. получаем облом, который толкает на следующую авантюру: а что если взять переменную с первым минусом в скобки, типа соорудить конструкцию похожую на логическую - "минус на минус…". Делаем (см. строку 7):

  1. var a = 1 // Переменная а равна 1
  2. -a // Переменная а равна -1
  3. -a // Переменная а равна -1
  4. -a // Переменная а равна -1
  5. a // Переменная а равна 1
  6. //--a //  XCode выдает ошибку ‘--‘ is unavailable: it has been removed in Swift 3
  7. -(-a) // Переменная а равна 1

Бинго!

Мы своего добились! Применили два минуса и на выходе получили плюс! На всякий случай для страховки проверяем, т.е. указываем просто переменную a:

  1. var a = 1 // Переменная а равна 1
  2. -a // Переменная а равна -1
  3. -a // Переменная а равна -1
  4. -a // Переменная а равна -1
  5. a // Переменная а равна 1
  6. //--a //  XCode выдает ошибку ‘--‘ is unavailable: it has been removed in Swift 3
  7. -(-a) // Переменная а равна 1
  8. a // Переменная а равна 1

Отлично! Всё сохраняется, значение нашей переменной снова положительное. Вопросы конечно оставались: почему унарный минус так себя ведёт? Единственное объяснение: он почему-то действует только тогда, когда  применяется, но само изменённое значение дальше не сохраняется.

Пример 3. Гештальт

И вот, как это часто бывает, долгие раздумья над каким-то вопросом вдруг приводят к новому повороту. Как говорится, гештальт созрел. Сначала была мысль, что один и тот же знак, минус, используется и как унарный оператор, и как бинарный арифметический. Разница только в синтаксисе: бинарному нужны 2 операнда и он пишется с пробелами, унарному нужен только один операнд и он пишется без пробела от операнда.

Затем настойчивые мысли о том, почему значение не сохраняется, вывели на оператор присвоения. Т.е. унарный минус значение меняет, но оно не сохраняется, т.е. оно НЕ ПРИСВАИВАЕТСЯ переменной! Отсюда логично напрашивается использовать оператор присвоения.

Тут же вспоминаются бинарные составные операторы, где используются знаки минуса и плюса и знак присвоения (=). Но это не совсем то, потому как здесь минус и плюс используются именно как бинарные арифметические операторы. Но направление мысли они задают, как оказалось, правильное. А именно - попробовать не только изменить знак числа, но и присвоить это изменённое значение переменной.

Делаем простой развёрнутый пример со сменой знака и сразу же проверяем текущее значение переменной (9-я и 10-я строки):

  1. var a = 1 // Переменная а равна 1
  2. -a // Переменная а равна -1
  3. -a // Переменная а равна -1
  4. -a // Переменная а равна -1
  5. a // Переменная а равна 1
  6. //--a //  XCode выдает ошибку ‘--‘ is unavailable: it has been removed in Swift 3
  7. -(-a) // Переменная а равна 1
  8. a // Переменная а равна 1
  9. a = -a // Переменная а равна минус 1
  10.  a // Переменная а равна минус 1

Ура ещё раз!

Значение переменной изменилось в 9-й строке, а в 10-й видно, что оно сохранилось! Т.е. впервые, когда мы просто указываем название переменной, её значение в строке 10 отличается от значения в первой строке!

Ещё раз подчеркнём: раньше по ходу примера, когда просто указывалось название переменной, то её значение всегда было таким, как и в первой строке, т.е. единица. А в 10-й строке мы впервые получаем при той же записи другое значение - единицу в минусе.

Таким образом, приходим к выводу, что эта последняя гипотеза правильная. А именно - унарный минус только меняет значение с плюса на минус, но он это новое значение автоматически не сохраняет, не присваивает переменной. Чтобы сохранить новое значение надо специально использовать оператор присвоения.

И кстати, с этой точки зрения можно заметить, что в 8-й строке мы не можем утверждать, что новое значение переменной сохранилось, как мы радовались этому немного ранее. Скорее всего в этой строке показывалось не изменённое с плюса на минус и потом с минуса на плюс значение переменной, а всё то же значение из первой строки. Т.е. все предыдущие изменения знака в переменной не сохранялись, потому что не использовался оператор присвоения. И только когда мы используем оператор присвоения, только тогда изменённое значение сохраняется и число в переменной остаётся с другим знаком.

Резюме по унарному минусу

В ходе своего учебного расследования :) мы выяснили как минимум 3 вещи:

  • Если мы захотим изменить знак числа в переменной с плюса на минус с помощью унарного минуса, а потом ещё раз изменить с минуса на плюс с помощью того же унарного минуса, то у нас это не получится.
  • Если же все-таки надо это сделать, т.е. вторым минусом изменить минус на плюс, тогда следует использовать конструкцию со скобками:
-(-a)
 

Не берусь судить, бывают ли такие ситуации, когда такое нужно сделать, но в программировании всякое возможно. Так что пусть будет, может когда и пригодится.

  • Унарный минус меняет знак числа в переменной, но новое измененное значение не сохраняется. Оно существует только в момент применения унарного минуса. А дальше по ходу программы будет использоваться исходное значение переменной до применения унарного минуса.

Если же нам нужно сохранить измененное значение, тогда следует использовать ещё и оператор присвоения. Т.е. изменить знак числа и присвоить это измененное число переменной.

Это можно сделать либо способом, где изменённое значение присваивается той же переменной:

a = -a
 

либо так, как показано в учебнике, т.е. присваивая изменённые значения новым переменным / константам:

let three = 3
let minusThree = -three // minusThree равно -3
let plusThree = -minusThree // plusThree равно 3, т. е. "минус минус три"
 

Пару слов об унарном плюсе

Об унарном плюсе говорить действительно почти нечего. Он просто возвращает исходное значение без изменений. Например:

var b = -20 // Переменная b равна минус 20
+b // Переменная b равна минус 20
var c = 30 // Переменная c равна 30
+c // Переменная c равна 30
 

Как сказано в учебнике: хотя оператор унарного плюса не выполняет никаких действий, он придаёт коду единообразие, позволяя зрительно отличать положительные значения от отрицательных. Но что-то мне сдаётся по типу нововведений в Swift 3, что потом с этим унарным плюсом может что-то и произойти для единообразия.

Унарный логический оператор

Унарный логический оператор в учебнике рассматривается в разделе "Логические операторы". С одной стороны это логично, с другой - не очень, поскольку вначале задана классификация на унарные, бинарные и тернарный операторы, а потом она нарушается. Поэтому здесь пробуем расположить унарный логический оператор в разделе именно "Унарные операторы" в соответствии с заданной в начале главы классификацией.

Итак, есть один унарный логический оператор. Его полное название - оператор логического НЕ. Обозначается этот оператор восклицательным знаком - !.

Его действие: он изменяет, инвертирует значение логической переменной с true на false или с false на true.

Синтаксис: унарный логической оператор также как и унарный минус ставится перед названием переменной без пробела.

  1. var k = true // Переменная k есть true
  2. !k // Переменная k есть false

В учебнике предлагается описывать действие унарного оператора логического НЕ как "не есть такая-то переменная". Отсюда, в нашем примере смысл второй строки можно описать так: "!k не есть переменная k". Или так - "значение !k не есть истинное значение переменной k, оно фальшивое, ненастоящее".

И далее, также как в случае с унарным минусом, новое значение логической переменной не сохраняется и действует только в момент использования унарного логического оператора. Т.е. если мы попытаемся ещё раз изменить значение переменной с false на true, то у нас ничего не получится.

  1. var k = true // Переменная k есть true
  2. !k // Переменная k есть false
  3. !k // Переменная k есть false

Чтобы поменять значение переменной второй раз, т.е. от false перейти к true, нужно использовать круглые скобки:

  1. var k = true // Переменная k есть true
  2. !k // Переменная k есть false
  3. !k // Переменная k есть false
  4. !(!k) // Переменная k есть true

А если мы хотим не только изменить значение логической переменной на противоположное, но ещё и сохранить его, то нам нужно изменённое значение присвоить или той же переменной, или новой (см. строки с 5-ой по 8-ю):

  1. var k = true // Переменная k есть true
  2. !k // Переменная k есть false
  3. !k // Переменная k есть false
  4. !(!k) // Переменная k есть true
  5. k = !k // Переменная k есть false, измененное значение сохранено
  6. k // Проверка. Результат - переменная k есть false
  7. var l = !k // Переменная k есть true и это значение присвоено новой переменной l
  8. l // Проверка. Результат - переменная l есть true

Ещё одно интересное наблюдение: унарный логический оператор также можно использовать не только по отношению к переменной, но и по отношению к голому значению. Т.е. также, как мы можем к 2 добавить минус и получить -2, так и к булевым значениям мы можем применять логический унарный оператор и изменять его на противоположное. Например:

true // Песочника показывает true
!true // Песочница показывает false
 

И последнее по унарному логическому оператору: он используется только в префиксном варианте, т.е. ставится только перед названием переменной или перед булевым значением.

Помнится раньше в Swift были и постфиксные операторы, но сейчас, начиная с версии 3, их уже похоже нет. И хотя в информации об обновлениях об этом прямо не сказано, но если попытаться поставить ! после переменной, то выдаёт ошибку, что не может сделать force unwrap, поскольку переменная с логическом типом данных не содержит опционального значения. Т.е. скорее всего, постфиксный  восклицательный знак отдан под опционалы.

В принципе всё логично, постфиксные операторы как-то выбивались из общего строя. Теперь без них будет проще: есть префиксные и всё понятно и единообразно. А вы как считаете?