Найти в Дзене
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;

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