В предыдущих выпусках я долго и нудно писал о том, как хранятся данные в памяти (в каждом выпуске есть ссылка на предыдущую и на следующую часть, так что читайте, пока не прочитаете всё). Всё потому, что это краеугольная тема программирования, которую надо очень хорошо понимать. Надеюсь, что это помогло.
Вернемся к простейшей задаче: нужно сохранить в памяти число 5.
Как я бы действовал, если бы не было вообще никаких языков программирования? Мне бы, типа, сказали: вот тебе компьютерная память. Она уже частично чем-то занята, там не твоё, не трогай. Вот тут, начиная с адреса 100 – можешь ею пользоваться.
Вообще говоря, я мог бы прямо своими руками менять данные в памяти, буквально трогать её. Это если бы память была в виде тумблеров или кнопок (кстати, она такая и была).
Но задача в другом. Программирование нужно как раз для того, чтобы я ничего не делал руками. Чтобы за меня вкалывал робот. А кто вкалывает в компьютере? Процессор. Поэтому мне нужно скомандовать процессору, что он должен сделать.
Я хочу дать процессору такую команду: "запиши в байт по адресу 100 число 5". Как нетрудно догадаться, команды для процессора – это тоже числа, потому что в компьютере нет ничего, кроме чисел. То есть команду, которую я написал текстом, нужно специальным образом закодировать в определенное число, в котором будет содержаться и сама операция "запиши", и адрес 100, и пояснение, что нужен именно байт, и моё число 5. И только после этого такую команду поймет и выполнит процессор.
Вот это является программированием непосредственно в машинных кодах. И самой главной проблемой здесь является сложность преобразования команд в машинный код, практически полная нечитаемость машинного кода, большая вероятность ошибки и очень трудный поиск ошибок.
Языки программирования призваны облегчить эту задачу. Но нужно понимать: всё, что мы пишем и видим на языке программирования – не существует и не выполняется так, как написано. Это не прямые инструкции для процессора, это всего лишь текст для кого-то другого, кто умеет переводить его в машинный код. И этот кто-то другой прочитает этот текст и создаст машинный код, который уже и будет исполнен процессором. Этот кто-то другой называется транслятор (то есть переводчик), но пока это всё, что нужно знать.
Возьмем для начала язык ассемблера. Это самый примитивный язык, который максимально близок к машинным инструкциям, но посмотрим, насколько он облегчает жизнь:
mov a, 5
...
a db 0
Что делает транслятор языка ассемблера, когда получает такой текст?
"Ага, я вижу, что ты написал здесь mov a, 5. Это значит записать (mov), куда записать? В что-то под названием "a". Что записать? 5. Да без проблем, сейчас составлю машинную инструкцию... Стоп, а что такое "a"? А вот, вижу, ты дальше выделил один байт памяти (db – data byte) и назначил ему метку "a". Я сам вычислю адрес этого байта памяти, не парься. И теперь везде, где ты пишешь "a", я буду знать, что ты имеешь в виду этот адрес. Короче, вот твоя готовая машинная инструкция."
Разница – уже колоссальная. Во-первых, мы можем писать инструкции для процессора в более понятном человеку виде: mov a, 5. Если не совсем понятно, то mov это от слова move, "переместить". Во-вторых, мы можем писать число 5 в привычной десятичной системе, а не в двоичной. В-третьих, мы можем назначать адресам любые человеческие метки (a, b, cat, dog, Eyjafjallajokull и т.п.). В-четвертых, для сохранения чего-либо не нужно даже знать адрес. Нужно только застолбить место (db) и присвоить ему метку, а транслятор сам вычислит, какой там получится адрес.
Писать программы мгновенно стало в тысячи раз легче.
Посмотрим на более развитый язык, C:
char a = 5;
Что делает транслятор языка C:
"Вижу, что ты хочешь зарезервировать один байт памяти. Потому что ты написал char (сокращение от character, т.е. символ), а это символьный тип, то есть 1 байт. Ты дал этому байту метку "a" и хочешь записать туда 5. Всё ясно, я сам зарезервирую 1 байт в свободной памяти и буду знать, что у него метка "а", поэтому везде, где ты напишешь "a", я буду обращаться к этому байту. Вот твоя готовая машинная инструкция."
По сравнению с ассемблером почти ничего не изменилось. Но запись стала короче и понятнее. А также нам не нужно отдельно резервировать байт памяти, как в ассемблере - это произошло автоматически, когда мы написали char a.
Посмотрим на JavaScript:
var a = 5;
Что делает транслятор языка JavaScript:
"Вижу, что ты хочешь создать в памяти новую переменную (var – variable) c меткой "a". Какого она типа, сколько байт занимает? Неважно, потом разберусь. А, вот вижу, что ты хочешь записать туда 5. Это целое число, тогда я буду считать, что это переменная целочисленного типа. Значит, выделю на неё... ВОСЕМЬ байт."
Отличие от предыдущих языков здесь в том, что JavaScript не только резервирует для нас память, но и самостоятельно угадывает, какой у нее должен быть тип. Но, как видно, мы уже немного потеряли контроль над памятью, так как вместо 1 байта выделилось 8. Почему? Потому что он заранее не знает, какие ещё числа вы туда захотите записать, и поэтому выделит памяти по максимуму. Это одна из проблем "легких" языков программирования, которые делают всё за нас.
Продолжать примеры нет смысла, потому что все оставшиеся языки работают так или иначе похоже на то, что показано выше. Просто посмотрите, как это пишется в разных языках, и вы наверняка всё поймете, даже не зная этих языков:
BASIC:
LET A = 5
Pascal:
var a: byte;
a := 5;
Python:
a = 5
ActionScript:
var a:Number = 5;
PHP, PERL:
$a = 5;
Go:
var a byte = 5
В следующих выпусках мы разберем, как языки программирования справляются с более сложными данными, такими как строки, массивы и структуры.