Данному образовательному сайту пришлось несколько раз менять свое имя. С 2011 года доступ к нему обеспечивается по URL
http://educomp.runnet.ru

emc.km.ru (2001-2007) ==> educomp.org.ru (2007-2011) ==> educomp.runnet.ru (2011-...)
Более подробно об истории сайта можно прочитать здесь.


Учебные модели компьютера



Модели (software):

"Е14" (parallel !!!)

Модели (hardware):






Награды сайта
Награды сайта

Представление различных типов данных в Турбо Паскале

Как известно, одним из важных вопросов информатики является представление информации в ЭВМ. Именно с этой точки зрения автор предлагает сегодня взглянуть на различные типы данных, имеющиеся в языке Паскаль.

В статье описано, в каких формах может Паскаль хранить в ОЗУ числовую и текстовую информацию, как она объединяется в более сложные типы данных (строки, массивы, записи), а также как представляются более специализированные виды информации, такие как перечисление, множество и другие. Проведенное рассмотрение убедительно подтверждает фундаментальный тезис о том, что любая информация в ЭВМ имеет двоичное представление, а также позволяет познакомиться с существующими способами кодирования информации.

Изложенные в статье сведения тщательно сверены с официальным руководством по Паскалю фирмы Borland [1] и проверены экспериментально. Вместе с тем автор старался, чтобы статья была интересна не только с точки зрения программирования на Паскале, но и как описание современных принципов хранения информации в ЭВМ. Если это удалось, о чем судить предоставляю возможность читателям, материал будет полезен всем преподавателям информатики, а также тем, кто хотел бы поглубже изучить этот современный и динамически развивающийся предмет.

В качестве помощника в изучении представления информации в памяти ЭВМ мы возьмем несложную программу на языке Паскаль, которую назовем DATA EXPLORER - ИССЛЕДОВАТЕЛЬ ИНФОРМАЦИИ. Ее полный, достаточно короткий текст приводится ниже.

PROGRAM DataExlorer(input,output);

CONST DataLen=2;     {количество байт}
TYPE  exp=integer;   {исследуемый тип}
VAR   s,o,i:integer; {рабочие переменные}
          e:exp;     {исследуемая переменная}

FUNCTION HexPrint(n:integer):string; {16-ричный вывод байта}
VAR d,j:integer; r:string[4];        {рабочие переменные}
BEGIN r:='';
      FOR j:=1 TO 2 DO
          BEGIN d:=n and $f;  {лог. И: выделить последнюю цифру}
                IF d<10 THEN r:=chr(ord('0')+d)+r     {'0'..'9'}
                        ELSE r:=chr(ord('A')+d-10)+r; {'A'..'F'}
                n:=n shr 4; {сдвиг: подготовить следующую цифру}
          END;
      HexPrint:=r
END;

BEGIN writeln; e:=1; {придать значение исследуемой переменной}
      s:=seg(e);o:=ofs(e);   {получить адрес переменной в ОЗУ}
      {напечатать содержимое DataLen байт ОЗУ}
      FOR i:=0 TO DataLen-1 DO writeln(HexPrint(mem[s:o+i]));
      readln;
END.

Текст подробно прокомментирован, но несколько слов по поводу устройства Data Explorer все же следует сказать.

Чтобы исследовать некоторый тип данных, необходимо внести сведения о нем в текст программы. Константа DataLen и тип exp определят требуемый размер памяти и исследуемый тип данных. Дополнительно потребуется задавать интересующее Вас значение изучаемой переменной e.

Примечание. Казалось бы, удобнее значение e вводить с клавиатуры, а не изменять каждый раз оператор присвоения в программе. Но на самом деле для многих типов данных в Паскале ввод с клавиатуры запрещен!

Программа определяет адрес переменной e в ОЗУ (сегмент s и смещение o) и выводит на экран ее содержимое. Для вывода на экран байта ОЗУ в шестнадцатиричном виде используется функция HexPrint.

Примечание.Если вы захотите воспользоваться этой программой в Delphi, у вас возникнут трудности. О способе их преодоления можно почитать здесь.

Имея такой довольно мощный инструмент исследования, как Data Explorer, мы можем смело начинать подробное рассмотрение типов данных.

Представление числовых данных в Паскале: типы INTEGER, REAL и другие

Всем известно, что ЭВМ первоначально предназначалась для обработки только числовой информации, о чем убедительно свидетельствует данное ей название. Неудивительно, что числовые типы данных появились в языках программирования в первую очередь и к настоящему времени практически стандартизировались.

Наиболее "старыми" являются числовые типы INTEGER (целые) и REAL (вещественные). Наличие двух базовых типов чисел объясняется целым рядом причин, причем то, что такого рода деление существует в математике, далеко не самое главное. Гораздо важнее, что обходиться на ЭВМ только вещественными числами, объявив целые частным случаем, практически невозможно. Дело в том, что из-за ограниченности количества разрядов вещественные числа в машине не могут быть представлены абсолютно точно. В ходе длинных вычислений это неудобство становится еще более заметно. Например, если 10 раз сложить значение 0,1 , результат окажется чуть-чуть отличным от 1. Не верите? Запустите на Турбо Паскале простейшую программу:

PROGRAM paradox;

VAR x,y:REAL; i:INTEGER;

BEGIN x:=1; y:=0;       FOR i:=1 TO 10 DO y:=y+0.1; IF x=y THEN writeln('x равно y') ELSE writeln('x не равно y'); writeln('разность x и y равна', x-y); END.

Как ни странно, она напишет, что x не равно y!

Такое "поведение" чисел достаточно неудобно, так как для многих объектов количество должно быть в точности целым (как тут не вспомнить известный детский мультфильм про полтора землекопа!) Кроме того, такие объекты есть и в самой ЭВМ - адреса ОЗУ, значения счетчиков циклов и некоторые другие. Если добавить, что для хранения целого числа требуется в несколько раз меньше памяти, то станет понятно, почему целочисленные переменные в программах обычно используются даже чаще, чем вещественные.

Все это вместе взятое приводит к тому, что INTEGER и REAL гармонично уживаются во всех языках программирования, подобно белым и черным клавишам на пианино.

Рассмотрение представления чисел удобнее начать с более простых - целых. В любой книге по устройству ЭВМ (см., например, [2,3]; много интересных сведений о внутреннем представлении чисел можно также найти в недавних публикациях журнала "Информатика и образование" [4,5]) Вы можете прочесть, что целые числа хранятся в памяти в двоичном виде и разобрать способы перевода десятичных чисел в двоичную систему и наоборот. Поэтому здесь мы ограничимся примером представления отрицательного числа, чему внимания обычно уделяется гораздо меньше.

Для получения двоичного кода отрицательного числа (в литературе его обычно называют дополнительным кодом) необходимо выполнить две несложные операции:

  1. инвертировать код, т.е. заменить в нем все единичные разряды на ноль и наоборот;
  2. к полученному результату прибавить единицу.

Проверим сформулированное правило на примере числа -256.

Число 25610 = 010016 = 0000 0001 0000 00002 проинвертируем:
1111 1110 1111 1111.
Прибавим 1 и с учетом переноса в старшие разряды получим:
1111 1111 0000 00002 = FF0016 = -25610.

В качестве закрепления рекомендую самостоятельно повторить данную процедуру для результирующего двоичного кода. Если Вы нигде не ошибетесь, то получите исходное число, так как -(-256) = 256.

ТАБЛИЦА 1. Представление чисел типа INTEGER

десятичное число младший байт старший байт внутреннее представление
32767FF7F7FFF
32766FE7F7FFE
...   
25600010100
255FF0000FF
...   
202000002
101000001
000000000
-1FFFFFFFF
-2FEFFFFFE
...   
-25501FFFF01
-25600FFFF00
...   
-3276602808002
-3276701808001
-3276800808000

Примеры внутреннего представления некоторых характерных целых чисел, записанные в шестнадцатиричной системе, приведены в таблице 1. Внимательно ознакомьтесь с ней и обратите внимание на следующие моменты.

  • Тип INTEGER 16-разрядный.
  • Допустимый диапазон типа от -32768 до 32767.
  • Отрицательные значения хранятся в дополнительном коде.
  • Старший знаковый бит целого числа всегда установлен в 1, если число отрицательное, и сброшен, если оно положительное или ноль.
  • Как следствие предыдущего, абсолютный код отрицательных чисел по мере роста их модуля уменьшается (сравните -110 = FFFF16 и -3276710 = 800116).
  • Младший байт числа всегда расположен в памяти раньше, чем старший!

Примечание. Во всех имеющихся в статье таблицах при записи полного представления числа образующие его байты стоят в порядке старшинства, как это принято в математике, а не в том порядке, в каком они хранятся в оперативной памяти! Например, у кода 7FFF байт FF хранится первым, а за ним следует 7F.

Помимо INTEGER, в Турбо Паскале существует несколько других целочисленных типов данных: SHORTINT, LONGINT, а также BYTE и WORD. Их основные характеристики приведены в таблице 2.

ТАБЛИЦА 2. Целочисленные типы данных
тип данныхколичество байтдиапазон значений
SHORTINT1-128 .. 127
BYTE10 .. 255
INTEGER2-32 768 .. 32 767
WORD20 .. 65 535
LONGINT4-2 147 483 648 .. 2 147 483 647

Особенно важно, что типы BYTE и WORD отличаются от остальных отсутствием отрицательных значений. Такие типы принято называть беззнаковыми. Сравним, например, INTEGER и WORD. Из таблицы 2 следует, что количество допустимых значений для этих 16-разрядных данных одинаково, но диапазон различен. Кроме того, разное представление рассматриваемых типов приводит к неодинаковому порядку следования шестнадцатиричных кодов: сравните таблицы 1 и 3.

ТАБЛИЦА 3. Представление беззнаковых чисел типа WORD

десятичное числомладший байтстарший байтвнутреннее представление
65535FFFFFFFF
...   
3276901808001
3276800808000
32767FF7F7FFF
...   
101000001
000000000

Займемся теперь вещественными числами. Как известно, их главное отличие состоит в существовании двух частей - целой и дробной, разделяемых при записи запятой. Заметим, что в англоязыких странах в качестве разделителя принята точка: именно поэтому в Паскале число "полтора" будет выглядеть как 1.5 .

Как перейти от целых чисел к вещественным? Можно, например, зафиксировать положение запятой между двумя определенными соседними разрядами - такой подход носит название чисел с фиксированной запятой. Реализация арифметики в этом случае получается очень простая, но она обладает существенным недостатком - малым диапазоном допустимых значений: числа с большой целой частью равно как и числа много меньше единицы "не помещаются" на фиксированную разрядную сетку.

В качестве более удобного решения существует альтернативный способ представления вещественных чисел - с плавающей запятой. Он основан на том факте, что любое число A в системе счисления с основанием Q можно записать в виде

A = (±M) * Q ±P,

где M называют мантиссой, а показатель степени P - порядком числа. Для десятичной системы это выглядит очень привычно, например: заряд электрона равен -1,6*10-19 к, а скорость света составляет 3*108 м/сек.

Представление числа с плавающей запятой не является единственным:

3*108= 30*107 = 0,3*109 = 0,03*1010 = ...

Поэтому договорились для выделения единственного варианта записи числа считать, что мантисса всегда меньше единицы, а ее первый разряд содержит отличную от нуля цифру - в нашем примере этим требованиям удовлетворит только число 0,3*109. Описанное представление чисел называется нормализованным и является единственным. Любое число может быть легко нормализовано.

Все сказанное можно применять и к двоичной системе:

A = (±M) * 2 ±P, причем 1/2 ≤ M < 1.

Например: -310 = -0,11*210: M=0,11 и P=10.

Арифметика чисел с плавающей запятой оказывается заметно сложнее, чем с фиксированной. Например, чтобы сложить два таких числа требуется предварительно привести их к представлению, когда оба порядка равны; такую процедуру принято называть выравниванием порядков. Кроме того, в результате сложения часто нарушается нормализация, а значит необходимо ее восстанавливать. Тем не менее вычислительные машины со всем этим великолепно умеют автоматически справляться и именно такой способ лежит в основе работы современных компьютеров.

Казалось бы, теперь достаточно выяснить, сколько разрядов и какие отведены под мантиссу и порядок, и все станет на свои места. Но не тут-то было! Запустив DATA EXPLORER с DataLen=6 [1], получим не совсем понятное содержимое ОЗУ, приведенное во второй колонке таблицы 4:

ТАБЛИЦА 4. Представление чисел типа REAL

числосодержимое ОЗУмодифицир. мантиссамодифицир. порядок
583 00 00 00 00 2020 00 ... 00==>0 A0 00 ... 0083-80=3
483 00 00 00 00 0000 00 ... 00==>0 80 00 ... 0083-80=3
382 00 00 00 00 4040 00 ... 00==>0 C0 00 ... 0082-80=2
282 00 00 00 00 0000 00 ... 00==>0 80 00 ... 0082-80=2
181 00 00 00 00 0000 00 ... 00==>0 80 00 ... 0081-80=1
0.7580 00 00 00 00 4040 00 ... 00==>0 C0 00 ... 0080-80=0
0.580 00 00 00 00 0000 00 ... 00==>0 80 00 ... 0080-80=0
0.257F 00 00 00 00 0000 00 ... 00==>0 80 00 ... 007F-80=-1
000 00 00 00 00 0000 00 ... 0000
-0.257F 00 00 00 00 8080 00 ... 00==>1 80 00 ... 007F-80=-1
-0.580 00 00 00 00 8080 00 ... 00==>1 80 00 ... 0080-80=0
-0.7580 00 00 00 00 C0C0 00 ... 00==>1 C0 00 ... 0080-80=0
-181 00 00 00 00 8080 00 ... 00==>1 80 00 ... 0081-80=1
-282 00 00 00 00 8080 00 ... 00==>1 80 00 ... 0082-80=2
-382 00 00 00 00 C0C0 00 ... 00==>1 C0 00 ... 0082-80=2
-483 00 00 00 00 8080 00 ... 00==>1 80 00 ... 0083-80=3
-583 00 00 00 00 A0A0 00 ... 00==>1 A0 00 ... 0083-80=3

(в столбце, содержащем модифицированную мантиссу, байты переупорядочены по старшинству - см. примечание после таблицы 1).

Чтобы расшифровать полученные результаты, необходимо к уже известным нам сведениям о мантиссе и порядке добавить еще три [1]:

  1. поскольку первый разряд мантиссы двоичного числа всегда равен 1, его можно не хранить в ОЗУ, но восстанавливать перед выполнением операций (так называемая "скрытая единица");
  2. для того, чтобы получить истинное значение порядка, необходимо из прочитанного значения отнять 8016;
  3. если в качестве порядка хранится 0, то мантисса тоже равна 0.

Чтобы разобраться во всех этих сложностях, возьмем для примера число 5 из первой строки таблицы. Его первый байт хранит закодированное значение порядка. Нетрудно видеть, что P=83-80=3. Оставшиеся байты мантиссы расшифровать несколько сложнее. С учетом того, что младшие байты лежат в ОЗУ раньше (число хранится "задом наперед"), получим

20 00 00 00 0016 = 0010 0000 0000 00002

Сравнив с числом -5, увидим, что старший разряд знаковый: в нашем случае он равен 0, т.к. 5>0. Собственно на мантиссу без знака остается

010 0000 0000 0000

Наконец, восстановив "скрытую единицу", окончательно получим значение мантиссы:

M=0,1010 0000 0000

Поскольку P=3, расшифровываемое число имеет окончательный вид

101,0 0000 00002 = 5,010

Обратите также внимание на то, что отрицательная мантисса не преобразуется в дополнительный код, поэтому она отличается от таковой для положительного числа только в знаковом разряде.

Завершая разговор о представлении вещественных чисел в Паскале, отметим, что в современных его версиях есть еще несколько таких типов данных: SINGLE, DOUBLE, EXTENDED и COMP. Их основные характеристики приведены в таблице 5 (по данным [1]).

ТАБЛИЦА 5. Вещественные типы данных

типдиапазончисло значащих цифрразмер (байт)
REAL2.9*10-39 - 1.7*103811-126
SINGLE1.5*10-45 - 3.4*10387-84
DOUBLE5.0*10-324 - 1.7*1030815-168
EXTENDED3.4*10-4932 - 1.1*10493219-2010
COMP-263+1 .. 263-1
(-9.2*1018 .. 9.2*1018)
19-208

Наверное, Вы уже изрядно утомлены техническими деталями и не захотите разбираться в тонкостях новых вещественных типов данных. Хорошо, мы не будем их обсуждать. Позволю себе только привести в заключение цитату из книги о самой современной версии компилятора с языка Паскаль - Delphi:

"Delphi не допускает старого типа с плавающей точкой Real в качестве типа опубликованного свойства. Вы должны использовать новые типы: Single, Double, Extended, Comp или Currency. Фактически вы должны использовать новые свойства, поскольку они более гибки и более эффективны по сравнению с Real. Real существует только для обратной совместимости с предыдущими версиями Pascal." [6]

Таким образом мы рассмотрели представление различных числовых данных в ЭВМ. Автор надеется, что теперь Вы поняли, какая большая разница существует между внутреннем представлением целого числа 2 и вещественного 2.0 . Поэтому удивительным скорее должно быть не то, что для преобразования из REAL в INTEGER используются специальные функции TRUNC( ) и ROUND( ), а то, что такие функции отсутствуют для обратного перевода: учитывая однозначность перевода из INTEGER в REAL Турбо Паскаль "в порядке исключения" выполняет этот перевод автоматически.

Символьные и текстовые данные: тип CHAR и его дальнейшее развитие в более поздних версиях Паскаля

Во времена создания Паскаля обработка текстовой информации на ЭВМ делала только самые первые шаги. Поэтому Н.Вирт заложил в первоначальную версию языка единственный тип данных - CHAR, предназначенный для хранения отдельно взятого символа. Текст, являющийся совокупностью символов, может быть получен путем создания массива типа CHAR (о массивах речь впереди).

Стремительное увеличение количества задач обработки текстов на ЭВМ привело к созданию новых, более удобных типов данных, например STRING. С точки зрения лаконичности языка эти типы являются избыточными, поскольку любая задача обработки текста по-прежнему может быть реализована в рамках CHAR. Однако с новыми типами данных решение получается гораздо короче и нагляднее. Именно поэтому сейчас уже трудно представить, что когда-то в Паскале не было типа STRING!

Рассмотрение принципов хранения текстовых данных в памяти ЭВМ целесообразно начать именно с типа CHAR, значением которого является одиночный символ. Следует иметь ввиду, что к числу символов принадлежат не только буквы (заглавные или строчные, латинские или русские), но и цифры, знаки препинания, спецсимволы типа "=", "(", "&" и т.п. и даже (обратите особое внимание!) пробелы между словами. Да, не удивляйтесь: пустое место в тексте тоже должно иметь свое обозначение.

Каждый символ хранится в виде двоичного кода, который является номером символа. Можно сказать, что компьютер имеет собственный алфавит, где весь набор символов строго упорядочен. Количество символов в алфавите тесно связано с двоичным представлением и равняется 28 = 256. Иными словами, каждый символ всегда кодируется 8 битами, т.е. занимает ровно один байт.

Как видите, хранится не начертание буквы, а ее номер. Именно по этому номеру воспроизводится вид символа на экране дисплея или на бумаге.

Наиболее стабильное положение в алфавитах всех ЭВМ занимают латинские буквы, цифры и некоторые специальные знаки. Это связано с существованием международного стандарта ASCII (American Standard Code for Information Interchange - Американский стандартный код для обмена информацией). Русские же буквы не стандартизированы и могут иметь различную кодировку.

Ниже в качестве примера приводится таблица стандартной части алфавита ЭВМ - символы с шестнадцатиричными кодами с 20 до 7F.

ТАБЛИЦА 6. Представление стандартных ASCII символов (тип CHAR)
Пример: код 52 соответствует "R".
  0123 4567 89AB CDEF
2  !"#$%&' ()*+,-./
3 01234567 89:;<=>?
4 @ABCDEFG HIJKLMNO
5 PQRSTUVW XYZ[\]^_
6 `abcdefg hijklmno
7 pqrstuvw xyz{|}~ 

Нельзя также пройти мимо еще одного интересного факта: каждый символ текста имеет свой числовой код, но не каждому коду соответствует отображаемый на экране символ. Речь идет о существовании так называемых управляющих кодов, величина которых меньше шестнадцатиричного числа 20 (т.е. 32 в десятичной системе счисления). При получении этих кодов внешние устройства не изображают какого-либо символа, а выполняют те или иные управляющие действия. Так, код 07 вызывает подачу стандартного звукового сигнала, а код 0C - очистку экрана. Особую роль играют коды 0A перевод строки, обозначаемый часто LF) и 0D (возврат каретки - CR). Первый вызывает перемещение в следующую строку без изменения позиции, а второй - на начало текущей строки. Таким образом, для перехода на начало новой строки требуются оба кода и в любом тексте эта "неразлучная пара" кодов хранится после каждой строки.

Обратим внимание читателя на то, что названия возврат каретки и перевод строки имеют историческое происхождение и связаны с устройством пишущей машинки.

Рассматриваемый тип данных CHAR "по внутреннему устройству" является числом, но в то же время для его преобразования в число используется специальная функция ORD( ). Другая функция - CHR( ) служит для обратного преобразования числа в символ.

В настоящее время стандарт ASCII начинает постепенно вытесняться новым международным стандартом - UNICODE. Символ в этом стандарте кодируется уже не одним, а двумя байтами: один из них по-прежнему определяет номер символа в алфавите, а другой хранит номер самого алфавита в стандартном списке алфавитов. Каждый язык, например, русский или греческий, имеет в этом списке свою страничку со вполне определенным номером. Некоторым сложным языкам типа японского выделяется несколько алфавитных страниц.

Применение UNICODE позволяет легко включать в текст символы из самых разнообразных языков. Многие современные программные продукты, например, последние версии операционной системы Windows или язык программирования JAVA уже включают в себя поддержку UNICODE. К сожалению, Паскаль пока не содержит типов данных, рассчитанных на UNICODE.

Описанный выше тип CHAR предназначен для хранения отдельного символа, что далеко не всегда удобно. Поэтому в более поздних версиях Паскаля появился новый вид данных для хранения текста - STRING (строка). Значением этого типа может служить текстовая строка с максимальной длиной 255 символов. Достоинствами STRING является то, что в одной переменной хранится целая строка, и самое главное, имеется большой набор удобных процедур и функций для обработки находящегося в ней текста. Можно, например, вставить текст в заданное место строки или, наоборот, удалить его, скопировать часть строки в другую переменную, осуществить поиск заданного образца в строке и т.д.

Под каждую строковую переменную компилятор Паскаля отводит определенное место в памяти, и это задает ее максимально возможную длину. Например, описание

VAR s:STRING[9];

зарезервирует в ОЗУ место под 9 символов. Часто начинающие программисты ничего не указывают после STRING - в этом случае создается строка с максимально возможной длиной 255.

Реально не вся отведенная память может быть заполнена, поэтому существует понятие текущей длины строки, которая равна фактическому количеству символов в тексте. Для ее определения имеется специальная функция LENGTH( ). Текущая длина строки всегда храниться в ее нулевом байте; именно поэтому строка более 255 символов принципиально невозможна. В качестве примера на рис.2.1 показана строковая переменная s, созданная по приведенному чуть выше описанию. В ней находится текст из 4 букв 'Text', а байты с пятого по девятый в настоящий момент не используются.

4 Text        
0123 4567 89

Рис.2.1. Тип данных STRING

Для тех, кому недостаточно в тексте иметь 255 символов, Borland Pascal предоставляет специальный модуль Strings, который поддерживает работу с так называемыми строками с нуль-окончанием. В них байт длины отсутствует, а конец строки определяется положением специального нуль-символа CHR(0). Принципиальные ограничения на длину такой строки отсутствуют, но 16-битная архитектура MS DOS устанавливает верхний предел в 65535 символов.

Отметим, что строки с нуль-окончанием широко используются в Windows, например, для хранения путей дисковых файлов.

Логические данные: тип BOOLEAN

Булевские переменные, названные так в честь известного математика Д.Буля, широко используются в Паскале для хранения результатов тех или иных логических действий. Для этого типа данных возможно всего два значения, которые принято обозначать TRUE и FALSE.

Несмотря на то, что для кодирования двух значений хватило бы одного бита, каждая переменная типа BOOLEAN хранится в отдельном байте. В качестве значения FALSE используется 0, а TRUE кодируется 1. Разрешается сравнивать логические значения на больше/меньше: нетрудно догадаться, что FALSE < TRUE.

Помимо "классического" логического типа BOOLEAN, в последних версиях Турбо Паскаля существуют типы BYTEBOOL, WORDBOOL и LONGBOOL.

INTEGER, CHAR и BOOLEAN как пример порядковых типов

Если значения какого-либо типа данных являются упорядоченными, т.е. к ним применимы понятия последующий/предыдущий, такой тип называется порядковым (ordinal). Все рассмотренные ранее типы, исключая вещественный и STRING, являются порядковыми. Кроме того, к порядковым типам относятся также перечисление и ограничение.

Перечисление является особым типом данных, который описывает сам пользователь путем перечисления всех его допустимых значений. Например:

TYPE svetofor = (red, yellow, green);

Естественно, что вместо текстовых значений Паскаль везде оперирует их номерами, которые начинаются с нуля (убедитесь, запустив DATA EXPLORER). Указанные в скобках значения используются только в момент трансляции программы и впоследствии не сохраняются. Именно поэтому переменные указанного типа нельзя ввести с клавиатуры или вывести на экран.

Ограничение позволяет в любом из порядковых типов выделить диапазон значений, например:

TYPE t1 = 1950..2000; t2 = 'A'..'F';

Использование ограничения не приводит к изменению внутреннего представления значений переменных: например, для типов t2 и CHAR значение 'A' кодируется одинаково.

Все порядковые типы обладают рядом приведенных ниже полезных свойств.

  • Каждому значению упорядоченного типа можно поставить в соответствие целое число. За исключением INTEGER, значения нумеруются начиная с 0 через 1. Порядковые номера для целых чисел совпадают с их значениями.

  • Порядковый номер любого такого типа может быть определен с помощью стандартной функции ORD( ).
  • Для любого элемента, кроме первого, стандартная функция PRED( ) возвращает предыдущий.
  • Для любого элемента, кроме последнего,стандартная функция SUCC( ) возвращает следующий.
  • Стандартная функция LOW( ) позволяет определить самое первое (младшее) значение, а HIGH( ) - самое последнее (старшее).

Благодаря упорядоченности, допускается сравнение значений внутри каждого порядкового типа. По ним может организовываться цикл FOR, переменные этого типа могут использоваться в качестве индексов массива.

Организация данных в массивы

Все рассмотренные в предыдущем разделе порядковые типы и дополнительно к ним вещественные числа образуют большой и разнообразный класс простых данных. Характерной чертой таких данных является то, что каждая переменная простого типа может единовременно хранить только одно значение. При записи нового значения старое теряется.

Кроме того, в Паскале существуют еще и сложные типы данных, которые тем или иным способом объединяют в себе под одним именем совокупность нескольких простых значений. Не запрещается также создавать сложные типы данных от других сложных типов.

Одним из первых сложных типов данных в языках программирования был массив (в Паскале он называется ARRAY). Массив состоит из нескольких элементов одинакового типа, объединенных общим именем. Доступ к нужному элементу массива реализуется с помощью индексов. В простейшем случае индекс - это целочисленный номер элемента, но Паскаль разрешает использовать в качестве индекса любой порядковый тип, например, CHAR, перечисление или даже BOOLEAN. Массив может иметь несколько индексов, причем допускается, чтобы они были разного типа.

Появление массивов в языках программирования позволило существенно упростить однотипную обработку большого количества данных. Достаточно описать, что делать с одним элементом, а затем просто указать, на какие элементы это действие распространяется. Программы с использованием массивов отличаются простотой, компактностью и наглядностью.

В отличие от простых данных, массивам не соответствует какой-либо предопределенный объем ОЗУ - все зависит от типа элементов и их количества. Поэтому большую роль при работе с массивом приобретает его описание. В описании не просто констатируется, что та или иная переменная является массивом: указываются также типы индексов, по которым легко определить количество элементов массива, и тип его элементов, что позволяет узнать объем памяти под каждый из них. Общий объем памяти вычисляется как произведение этих двух чисел. Например, двумерный массив A, описанный

ARRAY [1..10, ‘A’..’Z’] OF INTEGER

состоит из 260 элементов (латинских букв, как известно, 26) длиной по 2 байта каждый; таким образом, всего под массив требуется отвести 520 байт ОЗУ.

Элементы массива хранятся в памяти "вплотную", без всяких разделителей, в чем легко можно убедиться, запустив наш DATA EXPLORER. Для нахождения адреса требуемого элемента достаточно произвести некоторые арифметические вычисления. Рассмотрим этот вопрос подробнее, начав для простоты с одномерного массива.

Итак, пусть массив M имеет единственный индекс, который для определенности будем обозначать K. Как мы уже знаем, K обязательно имеет порядковый тип, а значит каждому его значению можно поставить в соответствие целое число. Поэтому, каким бы ни был индекс, его границам соответствуют вполне определенные числа Kmin и Kmax.. Будем также считать, что каждый элемент массива требует B байт памяти (для INTEGER B=2, для BOOLEAN - 1 и т.д.). Тогда адрес произвольного элемента с номером K в массиве будет определяться формулой

AK = A + (K - Kmin) * B ,

где A - адрес ОЗУ самого первого элемента с номером Kmin. Преобразуем это выражение к виду
AK = (A - Kmin * B) + K * B = A0 + K * B.

Итак, адрес произвольного элемента определяется окончательной формулой

AK = A0 + K * B                              (*)

A0 - это формальным образом полученная константа, но она имеет простой и понятный смысл: адрес нулевого элемента массива. Подчеркнем, что в массиве вполне может не быть нулевого элемента, но формула все равно остается правильной.

Для иллюстрации приведем пример двух массивов

VAR m1: ARRAY [3..6] OF CHAR; m2: ARRAY [-3..3] OF CHAR;

К обоим из них применима формула (*), хотя для первого A0 и будет находиться вне массива (см. рис. 2.2).

Массив m1:

 

 

 

 

 

 

 

0

1

2

3

4

5

6



Массив m2:

 

 

 

 

 

 

 

-3

2

1

0

1

2

3

Рис. 2.2. Пример расчета адресов элементов массивов.

Аналогичные формулы расчета адреса произвольного элемента могут быть получены и для многомерных массивов. Для наиболее часто встречающегося на практике двумерного массива адрес будет вычисляться по более сложной формуле

Aij = A + [ (j - jmin) + (i - imin) * nj ]* B = A - ( jmin  + imin * nj )* B + ( j + i * nj ) * B
= A0 + ( j + i * nj ) * B ,

где количество элементов в строке для краткости обозначено nj = jmax - jmin + 1. Для случая двумерного массива A0 уже не имеет наглядной интерпретации и является формальной константой.

При рассмотрении внутреннего представления многомерных массивов следует учитывать правило, что самый правый индекс всегда увеличивается первым [1].

Краткий обзор остальных типов данных

Остается рассмотреть еще несколько типов данных, которые существуют в Турбо Паскале. Ниже будут рассмотрены два сложных типа RECORD (запись) и SET (множество), а также особый тип данных, совершенно не похожий на другие - указатель.

Внутреннее представление наиболее сложных типов данных Турбо Паскаля - FILE и OBJECT мы здесь обсуждать не будем.

Начнем с записей. Запись, как и массив, образуется из отдельных компонентов, но, в отличие от массива, типы входящих в запись данных могут быть различны. Компоненты записи имеют специальное название - поля. Доступ к полям осуществляется по имени.

Записи очень хорошо подходят для создания всевозможных баз данных, так как информация об объектах обычно включает в себя самые разнообразные типы полей: текстовое название, те или иные числовые характеристики, булевские поля, связанные с наличием или отсутствием каких-то свойств и т.д. Даже обычная общепринятая запись даты, состоящая из числа и названия месяца, уже требует типа RECORD!

Зная, в каком формате хранятся образующие запись поля, вы без труда разберетесь, как хранятся переменные типа RECORD.

Гораздо интереснее кодируется в памяти ЭВМ другой тип - множество. Каждому его элементу соответствует отдельный бит, который установлен в 1, если элемент входит в данное множество, и сброшен в 0, если отсутствует в нем. Таким образом, для хранения множества требуется столько битов, сколько значений могут принимать элементы множества. Для вычисления количества байт под множество в [1] рекомендуется использовать следующую формулу:

(Max div 8) - (Min div 8) + 1.

Число элементов множества не может превышать 256, так что множество никогда не занимает более 32 байт.

В качестве примера в таблице 7 приведены различные состояния для переменной P типа множества, описанной следующим образом:

VAR P: SET OF 0 .. 9;

ТАБЛИЦА 7. Тип данных SET (множество)

значение множествамладший байтстарший байтвнутреннее представление
[ ] (пустое)000000 00
[0]010000 01
[1]020000 02
[2]040000 04
...   
[7]800000 80
[8]000101 00
[9]000202 00
[0,2,4,6,8]550101 55
[1,3,5,7,9]AA0202 AA
[0,1,2,3,4,5,6,7,8,9]FF0303 FF

Остается рассмотреть указатели. Указатель - это не само значение, а ссылка на него. Правильнее всего представлять себе указатель в виде адреса ОЗУ, где хранится требуемая информация.

Наиболее подготовленные читатели могут самостоятельно модифицировать нашу любимую программу DATA EXPLORER так, чтобы она рассматривала переменную e в ней как адрес памяти, где содержится интересующая Вас информация. Учтите только, что смещение для адреса располагается в младшем слове, а сегментная часть - старшем. Двойное нулевое значение соответствует неопределенному указателю nil [1].

Итак, мы рассмотрели основные принципы представления в памяти ЭВМ всех основных типов данных Паскаля. Автор надеется, что читатели узнали по этому вопросу много интересного, глубже разобрались в Паскале и еще больше его полюбили.




Л И Т Е Р А Т У Р А
  1. Borland Pascal With Objects. Руководство по языку программирования. - 393 с.
  2. Фомин С.В. Системы счисления (Популярные лекции по математике, вып.40). - М.: Наука, 1980. - 48 с.
  3. Изучение основ информатики и вычислительной техники: методическое пособие для учителей и преподавателей сред. учеб. Заведений. Ч.2. Ершов А.П, Монахов В.М., Витиньш М.В. и др. - М: Просвещение, 1986. - 207 с.
  4. Толстых Г.Д. Числа в математике, физике и информатике. - Информатика и образование, 1997, N 8, с.36-40.
  5. Толстых Г.Д. Представление чисел: от абака до компьютера. - Информатика и образование, 1998, N 1, с.43-47.
  6. Лишнер Р. Секреты Delphi 2. - К.:НИПФ “ДиаСофт Лтд.”, 1996. - 800 с.


© Е.А.Еремин, 2007
Публикация:
Еремин Е.А. Представление информации в ЭВМ средствами Turbo Pascal. "Информатика и образование", 1999, N 3, с.47-58.


Автор сайта - Евгений Александрович Еремин (Пермский государственный педагогический университет). e_eremin@yahoo.com