Переменные и типы
Предпосылки: Ассемблер (регистры, адреса памяти, боль ручного управления).
В ассемблере программист запоминает: «в регистре EAX лежит зарплата, в ячейке 200 — бонус, в ячейке 204 — налоговая ставка». При десяти переменных это ещё терпимо. При пятидесяти — невозможно: голова не держит, какой адрес что хранит, а одна ошибка в номере ячейки ломает программу без каких-либо предупреждений.
В конце 1950-х люди решили: пусть машина сама выбирает адреса и регистры, а программист работает с именами. Вместо MOV EAX, [200] пишут salary = 1500, и специальный переводчик решает, куда положить число. Он читает текст с именами и подбирает для него инструкции и ячейки. Как именно он это делает, разберём в последней заметке серии. Сейчас важно, что такой переводчик существует и берёт эту работу на себя.
Одним из первых массовых языков был FORTRAN (1957) — сокращение от FORmula TRANslation, «перевод формул». Он был создан для математиков и инженеров: вместо ассемблерных инструкций они могли писать привычные формулы вроде TOTAL = BASE + BONUS.
Переменная
Переменная — это имя, за которым стоит значение. Программист придумывает имя, а машина сама находит для значения место в памяти.
salary = 1500
tax_rate = 0.13
bonus = 300Три строки вместо трёх пар MOV из ассемблера. Адреса, регистры, количество ячеек на число — всё это теперь забота переводчика, не программиста.
Имя salary существует для человека: оно подсказывает, что хранится в этой переменной. Процессору имя не нужно — он по-прежнему работает с адресами. Но программист больше не обязан их знать.
С переменными можно делать то же, что и с числами — складывать, вычитать, сравнивать:
net_salary = salary - (salary * tax_rate) + bonusВ ассемблере для этой строки потребовалось бы 5-6 инструкций с жонглированием регистрами. Здесь — одна строка, которую может прочитать даже тот, кто видит код впервые.
Типы данных
Процессор не различает зарплату и температуру — для него и то, и другое просто числа в ячейках памяти. Но программисты работают с разными видами данных: целыми числами, дробными, текстом, значениями «да/нет». Тип данных — это указание, как интерпретировать содержимое переменной.
Целые числа (integer) — числа без дробной части: количество сотрудников, номер заказа, год. Как именно компьютер хранит целые и отрицательные числа — целые числа.
employee_count = 150
order_number = 40871Дробные числа (float — от floating point, «плавающая точка»: десятичная точка может сдвигаться между разрядами) — числа с точкой: налоговая ставка, температура, координаты. Почему 0.1 + 0.2 не равно 0.3 — числа с плавающей точкой.
tax_rate = 0.13
temperature = 36.6Строки (string — «нить, цепочка»: текст как цепочка символов) — текст, заключённый в кавычки. Не число, хотя может содержать цифры.
name = "Alice"
phone = "555-0142" # это текст, не число — складывать его нельзяЛогические значения (boolean) — только два варианта: true (истина) или false (ложь). Названы в честь математика Джорджа Буля (George Boole), создателя алгебры логики.
is_active = true
has_bonus = falseЗачем типы нужны? Они защищают от бессмысленных операций. Складывать два числа — осмысленно. Складывать число и текст — скорее всего ошибка. Без договорённости о типах код может считать одни и те же байты не так, как ожидается. Тип — это способ сообщить: «это телефон, не число; складывать его нельзя».
Как разные языки обращаются с типами
Типы существуют во всех языках программирования, но языки по-разному решают, когда и как их проверять. Оба подхода — для людей, но для разных ситуаций.
Динамическая типизация (Ruby, Python). Программист не указывает тип — язык определяет его сам по значению. Переменная может сменить тип в процессе работы:
x = 5 # целое число
x = "hello" # теперь строка — Ruby не возражаетМеньше церемоний — код короче, быстрее писать и менять. Это помогает разработчику двигаться быстро, особенно на ранних этапах, когда задача ещё не до конца понятна.
Статическая типизация (C, Rust, Java). Программист указывает тип, а компилятор проверяет его до запуска. В некоторых языках тип вычисляется автоматически:
int salary = 1500; // целое число
salary = "hello"; // ошибка! переводчик не допуститБольше текста при написании — зато ошибки ловятся раньше, и код становится самодокументированным: другой программист видит int salary и сразу понимает, что здесь целое число. Это помогает команде: когда код читают десять человек, явные типы экономят время каждому.
Один и тот же смысл — «создать переменную с числом 5» — на разных языках выглядит по-разному:
Ruby: x = 5
Python: x = 5
C: int x = 5;
Rust: let x: i32 = 5;
Java: int x = 5;Синтаксис отличается, но идея одна: дать имя значению. Разница — в том, сколько деталей язык требует от программиста и когда проверяет правильность.
Адреса и регистры больше не проблема. Зато программа по-прежнему выполняет все шаги подряд — и посчитает зарплату уволенному сотруднику с тем же усердием, что и работающему. Для управления ходом выполнения нужны условия.
Sources
- Sebesta, R., 2019, Concepts of Programming Languages. Pearson.
- Backus, J. et al., 1957, The FORTRAN Automatic Coding System. Western Joint Computer Conference.