Переменные и типы

Предпосылки: Ассемблер (регистры, адреса памяти, боль ручного управления).

Ассемблер | вывод

В ассемблере программист запоминает: «в регистре 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.

Ассемблер | вывод