===== 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. Заключение ==== Эта глава довольна важна для понимания того, что //базовые типы// (или, как иногда говорят //фундаментальные//) не зря различаются на четыре группы. В природе их представления и обработки есть //коренные отличия//. Конечно, можно было обойтись одним-двумя базовыми типами, но на практике такое ограничение бывает неудобно. Раздувать число базовых типов тоже смысла не имеет — это лишь усложнит овладение языком и снизит надёжность программ.