Найти тему
ZDG

Языки программирования: В чём смысл коротких операций?

Это материал для тех, кто только начал изучать программирование.

Типичная операция в языках программирования выглядит примерно так:

a = 5

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

a = a + 5

Здесь мы к переменной a прибавляем 5.

Во многих языках программирования (C, C++, Python, Java, PHP, PERL, JavaScript...) мы можем встретить укороченную запись такого вида:

a += 5

Она делает то же самое, что a = a + 5. В чём отличие, и зачем она?

Самая очевидная причина – так пишется короче и быстрее. Особенно это будет заметно, если переменная будет называться не одной буквой. Достаточно сравнить:

totalTicketsSold = totalTicketsSold + 5
и
totalTicketsSold += 5

Если же вы прибавляете не 5, а 1, то именно для единицы существует ещё более короткая операция (язык Python здесь придется вычеркнуть):

totalTicketsSold++

И чтобы вычесть 1:

totalTicketsSold--

Ситуация становится интересной – почему есть дополнительная операция специально для единицы? Только ли в краткости дело? Нет.

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

Давайте ещё раз посмотрим на выражение

a = a + 5

Это двуместное (для особо грамотных: бинарное) выражение, то есть у него есть левая часть (a) и правая часть (a + 5). Правая часть присваивается левой части. Правая часть, однако – это тоже выражение. Поэтому сначала надо вычислить её. Если весь этот процесс разбить на шаги и перевести в машинные инструкции, то получим:

  1. Скопировать переменную a во временный регистр
  2. Прибавить число 5 к временному регистру
  3. Записать в переменную a содержимое временного регистра

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

Теперь посмотрим на шаги для выражения a += 5:

  1. Прибавить к переменной a число 5

Всё! Результат получен за 1 шаг и без промежуточных результатов.

Теперь посмотрим на шаги для выражения a++:

  1. Увеличить переменную a на 1

Да! Есть отдельная, специальная машинная инструкция, чтобы делать именно увеличение (инкремент) или уменьшение (декремент) на 1. Это самая короткая и быстрая инструкция.

Использование укороченных инструкций было важно для оптимизации кода, но как я сказал выше, современные трансляторы языков сами справляются с оптимизацией, поэтому остаётся только одно применение для таких инструкций – более короткий код.

Короткая запись существует для почти всех операций:

a -= 5
a *= 5
a /= 5
a %= 5
a &= 5

и т.д.

Осталось рассмотреть особенности операций инкремента на 1 (++) и декремента на 1 (--). Это одноместные (для особо грамотных: унарные) операции, то есть у выражения нет правой части. Операция совершается непосредственно над переменной и сразу же получается результат в виде значения этой переменной.

Это значит, что данное выражение можно вычислять и сразу присваивать, без промежуточных результатов:

b = a++

То есть можно одновременно присвоить b = a, и увеличить a на 1. Есть, однако, один нюанс: при записи в таком виде у переменной a сначала берется значение, а потом она увеличивается на 1. Если a = 5, то после выполнения

b = a++

получим: b = 5, a = 6. Переменной b присвоилось 5, а не 6, потому что переменная a стала 6 уже после этого.

Но есть и другая запись:

b = ++a

Здесь, как видим, операция ++ написана не после, а перед переменной. Это значит, что переменная сначала увеличится, а потом уже возьмется её значение. И после такой операции получится уже b = 6, a = 6.

Давайте повторим: b = a++ это то же самое, что

b = a
a++

А b = ++a это то же самое, что

a++
b = a

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

i = 0;
while (i < a.length) a[i++] = 0;

В этом примере элементу массива с адресом i присваивается 0, после чего над i делается операция ++, то есть i переходит на следующий элемент массива.

Для тренировки можно подумать над тем, что получится здесь:

i = 0;
while (i < 5) a[i++] = i;

Ну и пример реального кода – сколько здесь одноместных операций?