logo search
Языки программирования

4.2. Типы перечисления

Языки программирования типа Fortran и С описывают данные в терминах компьютера. Данные реального мира должны быть явно отображены на типы данных, которые существуют на компьютере, в большинстве случаев на один из целочисленных типов. Например, если вы пишете программу для управле­ния нагревателем, вы могли бы использовать переменную dial для хранения текущей позиции регулятора. Предположим, что реальная шкала имеет четы­ре позиции: off (выключено), low (слабо), medium (средне), high (сильно). Как бы вы объявили переменную и обозначили позиции? Поскольку компьютер не имеет команд, которые работают со словами памяти, имеющими только четы­ре значения, для объявления переменной вы выберете тип integer, а для обо­значения позиций четыре конкретных целых числа (скажем 1, 2, 3, 4):

C


int dial; /* Текущая позиция шкалы */

if (dial < 4) dial++; /* Увеличить уровень нагрева*/

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

#define Off 1

C

#define Low 2

#define Medium 3

#define High 4

int dial;

if(dial<High)dial++;

Однако улучшение документации ничего не дает для предотвращения следу­ющих проблем:

C

dial=-1; /* Нет такого значения*/

dial = High + 1; /* Нераспознаваемое переполнение*/

dial = dial * 3; /* Бессмысленная операция*/

Другими словами, представление шкалы с четырьмя позициями в виде цело­го числа позволяет программисту присваивать значения, которые выходят за допустимый диапазон, и выполнять команды, бессмысленные для реального объекта. Даже если программист не создаст преднамеренно ни одну из этих проблем, опыт показывает, что они часто появляются в результате отсутствия I взаимопонимания между членами группы разработчиков программы, опеча- ток и других ошибок, типичных при создании сложных систем.

Решение состоит в том, чтобы разрешить разработчику программы созда- вать новые типы, точно соответствующие тем объектам реального мира, кото­рые нужно моделировать. Рассматриваемая здесь короткая упорядоченная последовательность значений настолько часто встречается, что современные языки программирования поддерживают создание типов, называемых типа­ми — перечислениями (enumiration types)*. В языке Ada вышеупомянутый при­мер выглядел бы так:

Ada type Heat is (Off, Low, Medium, High);

Dial: Heat;

Ada

Dial := Low;

if Dial < High then Dial := Heat'Succ(DialJ;

Dial:=-1; —Ошибка

Dial := Heat'Succ(High); -- Ошибка

Dial := Dial * 3; - Ошибка

Перед тем как подробно объяснить пример, обратим внимание на то, что в языке С есть конструкция, на первый взгляд точно такая же:

C


typedef enum {Off, Low, Medium, High} Heat;

Однако переменные, объявленные с типом Heat, — все еще целые, и ни од­на из вышеупомянутых команд не считается ошибкой (хотя компилятор мо­жет выдавать предупреждение):

Heat dial;

C

dial = -1; /*He является ошибкой!*/

dial = High + 1; /* Не является ошибкой! */

dial = dial * 3; /* Не является ошибкой! */

Другими словами, конструкция enum* — всего лишь средство документи­рования, более удобное, чем длинные строки define, но она не создает но­вый тип.

К счастью, язык C++ использует более строгую интерпретацию типов пе­речисления и не допускает присваивания целочисленного значения перемен­ной перечисляемого типа; указанные три команды здесь будут ошибкой. Од­нако значения перечисляемых типов могут быть неявно преобразованы в це­лые числа, поэтому контроль соответствия типов не является полным. К со­жалению, в C++ не предусмотрены команды над перечисляемыми типами, поэтому здесь нет стандартного способа увеличения переменной этого типа. Вы можете написать свою собственную функцию, которая берет результат це­лочисленного выражения и затем явно преобразует его к типу перечисления:

C++

dial = (Heat) (dial + 1);

Обратите внимание на неявное преобразование dial в целочисленный тип, вслед за которым происходит явное преобразование результата обратно в Heat. Операции «++» и «--» над целочисленными типами в C++ можно пере­грузить (см. раздел 10.2), поэтому они могут быть использованы для определе­ния операций над типами перечисления, которые синтаксически совпадают с операциями над целочисленными типами.

В языке Ada определение типа приводит к созданию нового типа Heat. Зна­чения этого типа не являются целыми числами. Любая попытка выйти за диапа­зон допустимых значений или применить целочисленные операции будет от­мечена как ошибка. Если вы случайно нажмете не на ту клавишу и введете Higj вместо High, ошибка будет обнаружена, потому что тип содержит именно те четыре значения, которые были объявлены. Если бы вы использовали один из типов integer, 5 было бы допустимым целым, как и 4.

Перечисляемые типы аналогичны целочисленным: вы можете объявлять переменные и параметры этих типов. Однако набор операций, которые могутвыполняться над значениями этого типа, ограничен. В него входят присваи­вание (:=), равенство (=) и неравенство (/=). Поскольку набор значений в объявлении интерпретируется как упорядоченная последовательность, для него определены операции отношений (<,>,>=,<=).

В языке Ada для заданного Т перечисляемого типа и значения V типа Т оп­ределены следующие функции, называемые атрибутами:

• T'First возвращает первое значение Т.

• Т'Last возвращает последнее значение Т.

• T'Succ(V) возвращает следующий элемент V.

• T'Pred(V) возвращает предыдущий элемент V.

• T'Pos(V) возвращает позицию V в списке значений Т.

• T'Val(l) возвращает значение I-й позиции в Т.

Атрибуты делают программу устойчивой к изменениям: при добавлении значений к типу перечисления или переупорядочивании значений циклы и индексы остаются неизменными:

for I in Heat'First.. Heat'Last - 1 loop

Ada

A(l):=A(Heat'Succ(l));

end loop;

He каждый разработчик языка «верует» в перечисляемые типы. В языке Eiffel их нет по следующим причинам:

• Желательно было сделать язык как можно меньшего объема.

• Можно получить тот же уровень надежности, используя контрольные утверждения (раздел 11.5).

• Перечисляемые типы часто используются с вариантными записями (раз­дел 10.4); при правильном применении наследования (раздел 14.3) по­требность в перечисляемых типах уменьшается.

Везде, где только можно, следует предпочитать типы перечисления обыч­ным целым со списками заданных констант; их вклад в надежность програм­мы невозможно переоценить. Программисты, работающие на С, не имеют преимуществ контроля соответствия типов, как в Ada и C++, и им все же следует использовать enum, чтобы улучшить читаемость программы.

Реализация

Я расскажу вам по секрету, что значения перечисляемого типа представляют­ся в компьютере в виде последовательности целых чисел, начинающейся с ну­ля. Контроль соответствия типов в языке Ada делается только во время ком­пиляции, а такие операции как «<» представляют собой обычные целочис­ленные операции.

Можно потребовать, чтобы компилятор использовал нестандартное пред­ставление перечисляемых типов. В языке С это задается непосредственно в определении типа:

C

typedef enum {Off = 1, Low = 2, Medium = 4, High = 8} Heat;

тогда как в Ada используется спецификация представления: __

Ada

type Heat is (Off, Low, Medium, High);

for Heat use (Off = >1, Low = >2, Medium = >4, High = >8);