Даже далекие от программирования люди ответят: «Все же очевидно!», ведь значения переменных мы можем изменять, а вот значение констант задается лишь единожды.
Действительно, это так. Рассмотрим на примере языка Swift (в других языках логика сохраняется, однако синтаксис может быть другим).
var a: Int = 10
//Мы создали переменную целочисленного типа с именем "a" и присвоили значение 10
let b: Int = 15
//Мы создали константу целочисленного типа с именем "b" и присвоили значение 15
Если мы с вами попытаемся изменить значение переменной "a", то у нас все успешно получится, в отличие от аналогичного действия с константой — мы получим ошибку:
b = 20 (!Cannot assign to value: 'b' is a 'let' constant)
Окей, мы поняли, что переменная может менять значение с течением времени, а вот константа — нет.
Есть еще один интересный трюк: инициализация константы, а потом передача значения в нее.
// Объявим константу строкового типа
let greeting: String
...
greeting = "Hello, World!"
greeting // Получим значение "Hello, World!"
Почему же так произошло? Нам нужно разобраться, каким образом хранятся данные и каким образом компилятор их преобразует.
Процесс компиляции
Компиляция — это процесс перевода нашего кода в машинный код, наиболее понятный компьютеру. Сейчас мы работаем на языках высокого уровня, которые далеки от низкоуровневых языков (например, Assembler).
Для большего понимания происходящего добавим некоторую математическую операцию в наш код:
a = a + b
a // Мы получим сумму 25, произведение a + b было записано в переменную "a"
Сейчас логика действий максимально примитивна и понятна. Давайте рассмотрим аналогичный пример на языке программирования Assembler:
MOV R1, #10
MOV [0x1000], R1
MOV R1, [0x1000]
ADD R1, #15
MOV [0x1000], R1
Сложно? Давайте разберем этот код построчно:
1. Этап инициализации:
В первой строке мы загружаем значение 10 в регистр R1, а во второй строке выполняем операцию записи нашего значения по условному адресу 0x1000.
2. Этап проведения операции (инструкции)
Загружаем значение из памяти (по адресу: 0x1000) в регистр R1, а затем оператором ADD осуществляем сложение значений из регистра R1 и 15.
3. Этап записи результата работы инструкции
Последняя строка — запись нового значения (полученного в результате операции сложения) в память (по адресу: 0x1000).
Мы с вами записали значение переменной в ячейку памяти, а константу — нет. Дело в том, что при компиляции нет уверенности в конечности значения переменной (это правда, так как временная переменная может изменить свое значение в любое время). Именно поэтому значение переменной хранится в ячейке памяти, и при обращении к ней данные переносятся в регистр. Константы устроены иначе: при компиляции мы уверены в том, что значение не будет изменено, и можем использовать его напрямую в инструкции. Переменные требуют выделенной памяти (для обращения и видоизменения данных процессором), а константы, если их значение на момент компиляции известно, могут быть встроены в машинный код напрямую. Если переводить на «человеческий» язык, то компьютер видит нашу инструкцию следующим образом:
Для проведения операции между значениями "a" и 15 необходимо выполнить следующие шаги:
- Обращаюсь к указанной ячейки памяти, записываю значение в регистр;
- Выполняю операцию сложения: между значениями 10 и 15;
- Записываю новое значение в указанную ячейку памяти.
Вернемся к примеру со строковой константой "greeting". Изначально мы создали объект, но не передали ему никакого значения. Что будет, если мы не присвоим значение и отправим наш код на компиляцию? Компилятор выделит память под хранилище типа String, однако доступ к нему будет заблокирован до тех пор, пока мы не присвоим значение в эту область памяти.
Почему лучше использовать в коде константы, а не переменные?
Перед проведением какой-либо операции нам необходимо совершить несколько шагов: записать значения переменных в ячейки оперативной памяти, а при необходимости каждый раз обращаться к ОЗУ и записывать данные из ячейки в регистр, который находится в ЦП. Константы сразу же записываются в инструкции и загружаются в процессор. Таким образом, используя константы, мы повышаем производительность нашего кода.
Однако не везде нужно использовать константы. Используйте их там, где значение гарантированно не будет изменяться: например, для времени, возраста пользователя, уровня заряда и так далее.