===== 1.6 Переменные и константы =====
==== 1. Типы данных ====
Под понятием //Тип данных// скрывается //семантическое свойство// информации. Для компьютера все байты информации представляют из себя //пакеты// из 8 бит. Каждый из битов может быть //включенным// или //выключенным//. В зависимости от того, как принято понимать эти состояния (//прямая// или //инверсная логика//) — эти значения принимаются равными "0" или "1". Больше о содержимом ячеек памяти компьютер не знает //ничего//.
Но для программиста даже один байт может содержать различные типы информации. Например, 8 бит (= 1 байт), с точки зрения программиста — может содержать //числа// от 0 до 255. А может и //код символа// в однобайтовой кодировке ("а", "б", "в"...). А может и вообще //что-то иное//. Но все байты одинаковы, у них нет признака того, что они хранят. В какой-то момент, программист вполне может решить, что вот эта ячейка памяти хранит число от 0 до 255((Вообще, существуют компьютеры, которые контролируют типы данных на этапе исполнения программы и обработки данных. Например, см. [[https://ru.wikipedia.org/wiki/%D0%AD%D0%BB%D1%8C%D0%B1%D1%80%D1%83%D1%81-3%D0%9C1|Эльбрус-3М1]], [[https://ru.wikipedia.org/wiki/%D0%9E%D0%A1_%D0%AD%D0%BB%D1%8C%D0%B1%D1%80%D1%83%D1%81|ОС Эльбрус]])). А на самом деле, в самом начале, программист задумывал хранить в этой ячейке памяти код символа для последующей печати. Более того, вполне может быть, что программист решил использовать под свои нужды не одну, а //сразу две// (или даже //тысячу//) ячеек? Помнить где расположена //каждая// ячейка? Такое программирование превратится в ад! И такие ошибки встречаются и у новичков, и у опытных программистов. Как решить эту проблему?
Проблема решается тем, что помнить о том, где расположена каждая ячейка памяти, и что она хранит — должен **Компонентный Паскаль**. Этой информации нет в самой ячейке, зато есть у языка программирования. Это и есть //семантическое свойство информации//, в данном случае — её //тип//. Таким образом описанная ячейка называется "переменная". Свой тип она //не меняет// с момента её описания, до момента окончания выполнения программы. Исключений не бывает((Из-за того, что тип переменных не меняется — Компонентный Паскаль и является языком со статической типизацией. А такой язык, как [[https://ru.wikipedia.org/wiki/Python|python]] позволяет менять типы переменных, поэтому он относится к языкам программирования с динамической типизацией.)).
==== 2. Целые числа ====
=== 2.1 Логический тип ===
//Логический тип//, в полном смысле этого слова //не является// целым. Но и к дробным числам этот тип можно отнести ещё меньше. Переменная такого типа может принимать только //два значения//. В **Компонентном Паскале** эти два значения определены как ''TRUE'' ("Истина") и ''FALSE'' ("Ложь"). Этот тип переменных используется даже шире, чем об этом задумываются многие программисты. Логический тип можно использовать //явно// (через переменную), а можно и //неявно// (например, через сравнение двух чисел, строк, результатов вызовов процедур). Обозначается такой тип через ключевое слово ''BOOLEAN''. Пример:
бНоль: BOOLEAN; (* флаг признака нуля *)
бСепар: BOOLEAN; (* сепаратор; отвечает за разделение глав *)
Стоит обратить внимание на выравнивание двоеточий и ключевых слов. Двоеточие, если проводить аналогию с русским языком может выступать как указатель на обстоятельство, как во фразе: "Итак: всё плохо!". Только в случае с **КП** этот разделитель служит для //указания типа// (справа) для переменной (что слева).
==== 2.2 Байтовый тип ====
Или просто байт. Обозначается ключевым словом ''BYTE''. Переменная такого типа может принимать значения от ''0'' до ''255''. Это совсем не много, но для многих целей может оказаться вполне достаточно. Например, //не существует// минут и секунд более 60. Или например //не бывает// дня в месяце с номером 32. Пример описания переменной типа ''BYTE'':
день : BYTE;
уровень: BYTE;
Важно не забывать ставить //точку с запятой// после всех определений переменных (после определения последней переменной точку с запятой тоже нужно ставить). Кроме того, следует помнить, что особенности современных компьютеров: они не работают с одним байтом, они работают, скажем, сразу с 4-мя байтами. Поэтому возможны такие эффекты, как неэкономное расходование памяти. Как избежать таких эффектов будет рассмотрено в дальнейших частях.
==== 2.3 Короткое целое ====
Короткое целое число в **Компонентном Паскале** определено как 2 байта. В редакции 1.7 сказано, что размеры типов //не зависят// от аппаратной платформы, но в текущей реализации **Компонентного Паскаля**, которая была скомпилирована под архитектуру 32 бита, короткое целое именно 2 байта. Диапазон чисел, которые умещаются в эти 2 байта составляет примерно от -32000 до +32000. Короткое целое обозначается ключевым словом ''SHORTINT''.
Пример объявления коротких целых:
кцПачк_всего : SHORTINT; (*счётчик пачек *)
кцПалет_всего: SHORTINT; (*всего палет*)
Как видно, в целом, определение переменных базовых типов однообразно и их легко запомнить. Загадочные буквы впереди имён -- это префиксы, для напоминания типа переменной программисту. Подставлять их совсем не обязательно, но автор рекомендует это делать.
==== 2.4 Целое ====
//Целое число// является //основным// типом целых чисел для машин с 32 битами на машинное слово. Для **Компонентного Паскаля** это именно тот случай. Целое число занимает в памяти 4 байта. Такого количества памяти хватает на описание числа примерно от -2,1 млрд. до +2,1 млрд. Не часто встречаются числа с таким динамическим размахом. Целый тип описывается ключевым словом ''INTEGER'':
цСчётчик1: INTEGER;
цСчётчик2: INTEGER;
И здесь ничего нет такого, чтобы потребовало особого способа описания переменных.
==== 2.5 Длинное целое ====
Самый широкий диапазон целых чисел, который встроен в Компонентном Паскале. Занимает 8 байт, представляет целые числа в диапазоне примерно от -9,2×1018 до 9,2×1018. Даже сложно представить, где такие числа вообще могут потребоваться обычным людям. Обозначаются такие переменные как ''LONGINT'':
дцСолнце_дист: LONGINT; (* расстояние до Солнца *)
дцПлутон_дист: LONGINT; (* расстояние до Плутона *)
Следует помнить, что сборка **BlackBox Intron** (впрочем, как и другие) оптимизированы под 32-х битную архитектуру, поэтому работа с такими числами будет //существенно медленней//, чем с типом ''INTEGER''.
==== 3. Вещественные числа ====
//Вещественные// (дробные, рациональные) числа называются так потому, что в окружающем мире редко встречаются "целые" объекты. Например, слоны. Они вроде все слоны. Но слонёнок по массе — это целый слон? Если нет, то как отразить его массу через целого слона? Кроме того, очень часто приемлемо записывать числа с //заданной точностью//. Они для этого подходят как никто. Таким образом, вещественные числа находят более чем широкое применение в промышленности.
==== 3.1 Короткое вещественное ====
Такие числа соответствуют вещественным числам в языке **Си**. В памяти они занимают 4 байта, но в отличии от целых чисел они имеют //особый формат// при хранении. Это приводит к тому, что точность таких чисел ограничивается в 7-8 десятичных цифр. На зато диапазон этих чисел раздвигается до –3,4×1038...–10–38 в отрицательной области, и до 10–38...3,4*1038 в области положительных чисел. Даже по сравнению с типом ''LONGINT'' это оооочень много. Но есть и обратная сторона медали. Если в типе ''LONGINT'' точность до последнего знака, то в данном случае (как уже было выше упомянуто) //только до 7-8//. Поэтому, если в вычислениях важна //точность//, надо помнить о том, что точность больших чисел //огрубляет// точность малых чисел. Это правило определяет порядок работы с вещественными числами: "сначала маленькие, потом большие" при увеличении, и "сначала большие, потом маленькие" при уменьшении. Такие числа обозначаются ключевым словом ''SHORTREAL'':
вЗил_масса : SHORTREAL; (* масса автомобиля ЗиЛ *)
вКамаз_масса: SHORTREAL; (* масса автомобиля КамАЗ *)
Отдельно стоит упомянуть то, что вещественные числа обрабатываются на математическом сопроцессоре, и обычно, работа с вещественными числами происходит медленней, чем с целыми.
==== 3.2 Вещественное число ====
Этот тип чисел занимает в памяти //в 2 раза ячеек больше//, чем короткое вещественное — все 8 байт и соответствует числу с //двойной точностью// стандарта [[https://ru.wikipedia.org/wiki/IEEE_754-2008|"IEEE 754"]]. Диапазон, который охватывают такие числа, если записывать от руки без научного формата — утомит очень быстро (примерно -10^308...10^308). Точность составляет 15-17 десятичных знаков. Если через штуки записывать количество атомов во Вселенной — такой точности как раз должно хватить((По форматам вещественных чисел стоит посмотреть в Википедии материал отдельно: [[https://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%BE_%D0%BE%D0%B4%D0%B8%D0%BD%D0%B0%D1%80%D0%BD%D0%BE%D0%B9_%D1%82%D0%BE%D1%87%D0%BD%D0%BE%D1%81%D1%82%D0%B8|«Число одинарной точности»]], [[https://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%BE_%D0%B4%D0%B2%D0%BE%D0%B9%D0%BD%D0%BE%D0%B9_%D1%82%D0%BE%D1%87%D0%BD%D0%BE%D1%81%D1%82%D0%B8|«Число двойной точности»]])). Переменная вещественного типа описывается ключевым словом ''REAL'':
двАндром_масса: REAL; (* масса галактики Андромеды *)
двГалак_масса : REAL; (* масса нашей галактики *)
==== 4. Литерные типы ====
//Литерными типами// называют такие типы данных, которые имеют какое-либо отношение к отображению чего-либо. Например, буквы, строки, тексты, цифры, управляющие символы (перевод строки, новая строка, гудок и т.д.). Дело в том, что такие символы //крайне важны// для человека (и к ним совершенно равнодушен компьютер). Но на экране, принтере, плоттере — любой из этих символов состоит из множества точек (матрицы). И такая матрица может достигать размера 2400х4800 точек. Да ещё и они могут быть цветные (разных цветов все точки), и таким образом потребуется непомерное количество байтов для хранения всех возможных изображений литер и их цветов. И это ещё не говоря о всяких графических пиктограммах (смайлики, флажки, стрелки, дома и т. д.). Поэтому в своё время был предложен компромиссный вариант для хранения литер. Суть идеи состояла в том, что печатной (служебной) литере должен соответствовать свой код-число. А уж если потребуется, потом можно добавить различные способы вывода кода этой литеры на экран, принтер, плоттер и т. д. Тогда хранение литеры в памяти компьютера становится компактным и универсальным.
==== 4.1 Литеры набора Latin-1 ====
Эти //литеры// занимают в памяти ПК всего 1 байт. Если речь идёт исключительно о //латинском алфавите// (22 буквы), то им вполне можно пользоваться. Но вот проблема: если будет желание выводить символы на //национальном// алфавите, вместо ожидаемого результата будет //непонятно что//. А суть этой проблемы в том, что этот набор литер принимался как стандарт на заре компьютерной эпохи. Мало кто задумывался об этой проблеме, поэтому литеры набора **Latin-1** даны скорее для //обратной совместимости со старыми программами//, чем для реального использования. Переменные такого типа описываются ключевым словом ''SHORTCHAR'':
клЛат_а: SHORTCHAR; (* латинская литера "а" *)
клЛат_б: SHORTCHAR; (* латинская литера "б" *)
Стоит добавить, что кроме букв и цифр в литерах **Latin-1** есть ещё и разные интересные значки, которые, иногда, могут и пригодиться ((Также набор символов **Latin-1** известен как [[https://ru.wikipedia.org/wiki/ISO_8859-1|ISO_8859-1]], первая часть этой таблицы — [[https://ru.wikipedia.org/wiki/ASCII|ASCII]])).
==== 4.2 Литеры набора Unicode ====
Этот набор литер по сравнению с предыдущим является более прогрессивным. Он лишён недостатков **Latin-1**, но у каждой медали две стороны. Да, теперь в этот набор **Unicode** помещаются литеры //всех языков мира// существующих, или когда-либо существовавших. Туда же помещаются различного рода //пиктограммы// из всех сфер жизни (значки Солнца, Луны, Земли и даже "Серп и молот"). Но, если байтовые литеры было легко сравнивать, так как они располагались в алфавитном порядке, то как понять, какой код меньше и на каком основании: английская литера "а" или русская литера "а"? А это //совершенно разные// литеры. К счастью, все (или почти все) процедуры для работы с литерами **Unicode** написаны, и сомнительно, что программисту придётся писать что-то своё (с высокой степенью вероятности это будет //велосипед//, как говорят программисты). Такой тип переменных описывается ключевым словом ''CHAR'':
лАнг_а: CHAR; (* английская литера "a" *)
лРус_а: CHAR; (* русская литера "а" *)
Ещё раз стоит обратить внимание — в данном примере (в наборе **Unicode**) русские и английские литеры кодируются //различными кодами//, хотя внешне и выглядят //одинаково//((Кодировка **Unicode** (Юнико́д или Унико́д) весьма сложна, убедиться в этом можно прочитав статью в Википедии — [[https://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4|«Юникод»]])). Именно из-за того, что в **Unicode** буквы кодируются разной длиной, (и не только буквы, но и значки, пиктограммы, иероглифы) правильно говорить не ''буква'', а ''литера''.
==== 5. Константы ====
//Константой// называется такая переменная, которую //нельзя// изменять в ходе выполнения программы. Преимущества констант перед переменными можно выразить следующими положениями:
- Тип констант определяется //автоматически//. Т. е. программисту //не нужно думать// при описании константы о том, какой тип данных должен наилучшим образом соответствовать именно этой константе.
- Преимущество констант перед переменными также в том, что если программист забудется, и попытается работать с константой, как с переменной — компилятор **КП** настойчиво напомнит программисту о том, что он сам //запретил// менять константу. И это приведёт к избежанию ошибок разработки и исполнения.
- Также константы заметно //быстрее// обрабатываются компьютером, чем переменные.
Форма их определения существенно отличается от формы определения переменных:
_отказ = 1;
_добро = 2;
_хор = 3;
_оч_хор = 4;
_вау = 5;
Форма описания //констант// определена не через двоеточие, а через знак "равно". И в данном случае это вполне соответствует законам логики и математики. Также стоит обратить внимание, что константа ''_оч_хор'' и её знак "равно" не выровнены со всеми константами. Это допущение вполне приемлемо при оформлении кода. Ну что делать, если имя переменной, такое длинное? В данном примере все константы начинаются с символа подчёркивания. Такого требования //нет// в стиле по кодированию (code style), но авторы рекомендуют именно такой подход, чтобы было легче различать константы.
==== 6. Преобразования типов ====
Язык **Компонентный Паскаль** был //спроектирован//, а не //сочинён//. Поэтому правила преобразования типов просты, понятны и предсказуемы.
==== 6.1 Преобразования числовых типов ====
Как уже было выше описано, самым мощным диапазоном представления чисел является тип ''REAL''. В случае преобразований при необходимости, этот тип преобразуется в более ограниченный — ''SHORTREAL''(8 байт с плавающей запятой в 4 байта с плавающей запятой). Если этот тип придётся преобразовывать, он в свою очередь сужается до ''LONGINT'' (4 байта с плавающей запятой в 8 байта целочисленного значения). Тип //длинное целое// при сжатии переходит в тип ''INTEGER'' (8 байт в 4 байта). После типа //целого// сжатие диапазона идёт в сторону ''SHORTINT'' (2 байта). Короткое целое тоже может быть сжато до ''BYTE''. Дальше диапазон числа уменьшить нельзя. Тип ''BOOLEAN'', строго говоря числовым не является, хотя и содержит логические "0" и "1". Обратное преобразование также верно. Например, если разделить два целых числа 3 и 2 — результат будет //вещественное// число. **КП** прекрасно понимает, что без этого результат будет очень неточным. Поэтому, если результат такого деления попытаться присвоить целочисленной переменной — такой модуль даже //не удастся// скомпилировать — **Компонентный Паскаль** просто //не позволит// это сделать! В то же время, если сумма двух целых превышает динамический диапазон целого — **КП** на стадии компиляции //постарается// выяснить этот факт, и, по возможности, потребует результат присваивать длинному целому((Но стоит помнить, что какой бы компилятор умный не был -- он просто не знает, какими числами ему придётся оперировать во время исполнения)). Если выяснить на этапе компиляции это невозможно, **КП** во время исполнения //остановит// программу, и //не позволит// проскочить момент переполнения и "улететь программе в космос". Те же самые правила касаются и остальных преобразований типов. Коротко схему преобразования типов можно отобразить так:
REAL => SHORTREAL => LONGINT => INTEGER => SHORTINT => BYTE
Если нужно пройти в обратном направлении, то **КП** сначала попытается привести результат к более //мощному// типу, и если динамического диапазона обоих типов не хватает для хранения результата — **КП** потребует присвоения результата переменной, с заведомо более мощным типом.
Отдельно следует отметить, что в **КП** -- //неявное// приведение типов //запрещено// (такое приведение может привести к отрицательным побочным последствиям, типичный пример -- Си).
==== 6.2 Преобразования литерных типов ====
То, что выше было написано про числовые типы, применимо и к литерным типам. Также надо учитывать, что приведение ''SHORTCHAR'' к ''CHAR'' будет затруднено, так как **КП** просто //не будет знать//, какая была //национальная кодировка// типа ''SHORTCHAR''. Коротко схему преобразования типов можно отобразить так:
CHAR => SHORTCHAR
==== 7. Использование переменных и констант ====
В этом разделе будет приведён пример, показывающий как использовать переменные различных типов. Прежде чем будет приведён полный текст программы, кое-какие пояснения:
- Описание переменных всегда в **КП** выносится в отдельную секцию модуля, которая обозначается ключевым словом ''VAR'' (variable, переменная).
- Описание констант всегда в **КП** выносится в отдельную секцию модуля, которая обозначается ключевым словом ''CONST'' (constante, постоянная/неизменяемая).
Hello02.odc
MODULE КнигаПривет2;
(* это вторая программа на языке
Компонентный Паскаль. Она выполняет
кое-какие математические операции *)
IMPORT Log;
CONST
_конст = 2;
VAR
цПерем1: INTEGER;
цПерем2: REAL;
PROCEDURE Старт*;
VAR
BEGIN
цПерем1 := 3;
цПерем2 := _конст / цПерем1;
Log.String('Результат программы: ');
Log.Real(цПерем2);
Log.Ln
END Старт;
END КнигаПривет2.
Вывод программы:
компилируется "КнигаПривет2" 88 12
Результат программы: 0.6666666666666666
Для того, чтобы скомпилировать программу и выгрузить необходимо традиционно нажать **** и запустить программу на исполнение через **КОММАНДЕР** (вставляется ****):
(^)TestHello02.Start
Необходимо обратить внимание, что константе ''_конст'' не нужно присваивать значение, а её //тип// (судя по всему) компилятор определил, как ''INTEGER''. Наоборот, переменной ''цПерем'' необходимо присвоить значение, так как при запуске программы, в ней может находиться //мусор// (если это //локальная// переменная, но в нашем случае — //глобальная// — а значит, содержится 0). //Мусор// — это //случайные// значения, оставшиеся от работы предыдущей программы (которая пользовалась этим участком памяти). Переменной ''вПерем'' также не нужно присваивать начальное значение, так как нам оно — не интересно. Переменная ''вПерем'' получает своё значение в результате вычислений.
Если всё сделано правильно, то можно будет увидеть результат, примерно такой, как на врезке выше. Удивляемся размеру программы (88 байт), убеждаемся, что деление **двух целых чисел** привело к результату ''REAL'', вспоминаем как вставить **КОММАНДЕР** и использовать его.
Кроме того, надо обратить внимание как на этот раз был выполнен импорт модуля ''Log'' - вместо его родного имени, теперь используется ''мЛог''. По русски, с префиксом "м"("модуль"), что делает код чуть более понятным. Этим полезным способом будем пользоваться и далее.
==== 8. Немного о присваивании ====
В коде представленном выше используется знак равно в двух вариантах:
CONST
_конст = 2;
............................
цПерем := 3;
вПерем := c/i;
Здесь могут возникнуть вопросы, поэтому ниже приводятся необходимые пояснения:
- В первом случае, при присвоении константе ''_конст'' значения ''3'' — стоит знак равно. И это правильная математическая форма записи.
- Во втором случае, переменная ''вПерем'' содержит //мусор//, который никак не может быть равен ''_конст/цПерем'' (нет, конечно может, но вероятность такого совпадения асимптотически стремится к нулю), и чтобы подчеркнуть этот факт, что это //не математическое уравнение//, а инструкция //присваивания// в **КП** принято в инструкциях использовать символ '':='', как не нарушающий математические соглашения.
- Как видно из последней строки, выполнено неявное преобразование типов. Тип ''INTEGER'' приведён к типу ''REAL''. **Компонентный Паскаль** соблюдает разумную необходимость с достаточностью в таких вопросах. Существует приличное число языков, где вообще //не требуется// выполнять //явное приведение типов//, или наоборот: на каждый чих необходимо вмешательство программиста. Едва ли оба подхода, как крайности — рациональны.
В ряде языков (в том числе, таком популярном, как **Си**) знак "равно" используется и для сравнения чисел //в условиях//, и это очень часто является источником ошибок. В **Компонентном Паскале** такие ситуации исключены. Ведь этот язык (в том числе) и для //промышленного программирования//.
Также необходимо обратить внимание, что может возникнуть соблазн, с целью ускорения исполнения программы приводить числа к более компактному виду, например ''REAL'' к ''SHORTREAL'', полагая, что //менее мощный// тип обрабатывается //быстрее//. На самом деле, компилятор **КП**, как того требует программист, приведёт число к более компактному типу, для вычислений //опять// переведёт в более мощный тип, и чтобы согласовать результат вычислений с конечной переменной — ещё раз преобразует к более компактному виду. Таким образом, не только //не будет// прироста скорости вычислений, но и заметная её //потеря//. Основными типами чисел в **КП** являются ''INTEGER'' и ''REAL''. Именно ими и стоит пользоваться. Все остальные базовые типы чисел нужны только для //межмашинного обмена данными//, либо экономии оперативной памяти.
==== 9. Заключение ====
Эта глава довольна важна для понимания того, что //базовые типы// (или, как иногда говорят //фундаментальные//) не зря различаются на четыре группы. В природе их представления и обработки есть //коренные отличия//. Конечно, можно было обойтись одним-двумя базовыми типами, но на практике такое ограничение бывает неудобно. Раздувать число базовых типов тоже смысла не имеет — это лишь усложнит овладение языком и снизит надёжность программ.