М. Бен-Ари - Языки программирования. Практический сравнительный анализ (2000) (1160781), страница 10
Текст из файла (страница 10)
Другими словами, конструкция enum* — всего лишь средство документирования, более удобное, чем длинные строки define, но она не создает новый тип.
К счастью, язык C++ использует более строгую интерпретацию типов перечисления и не допускает присваивания целочисленного значения переменной перечисляемого типа; указанные три команды здесь будут ошибкой. Однако значения перечисляемых типов могут быть неявно преобразованы в целые числа, поэтому контроль соответствия типов не является полным. К сожалению, в C++ не предусмотрены команды над перечисляемыми типами, поэтому здесь нет стандартного способа увеличения переменной этого типа. Вы можете написать свою собственную функцию, которая берет результат целочисленного выражения и затем явно преобразует его к типу перечисления:
C++ |
Обратите внимание на неявное преобразование 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 |
end loop;
He каждый разработчик языка «верует» в перечисляемые типы. В языке Eiffel их нет по следующим причинам:
• Желательно было сделать язык как можно меньшего объема.
• Можно получить тот же уровень надежности, используя контрольные утверждения (раздел 11.5).
• Перечисляемые типы часто используются с вариантными записями (раздел 10.4); при правильном применении наследования (раздел 14.3) потребность в перечисляемых типах уменьшается.
Везде, где только можно, следует предпочитать типы перечисления обычным целым со списками заданных констант; их вклад в надежность программы невозможно переоценить. Программисты, работающие на С, не имеют преимуществ контроля соответствия типов, как в Ada и C++, и им все же следует использовать enum, чтобы улучшить читаемость программы.
Реализация
Я расскажу вам по секрету, что значения перечисляемого типа представляются в компьютере в виде последовательности целых чисел, начинающейся с нуля. Контроль соответствия типов в языке Ada делается только во время компиляции, а такие операции как «<» представляют собой обычные целочисленные операции.
Можно потребовать, чтобы компилятор использовал нестандартное представление перечисляемых типов. В языке С это задается непосредственно в определении типа:
C |
тогда как в Ada используется спецификация представления: __
Ada |
type Heat is (Off, Low, Medium, High);
for Heat use (Off = >1, Low = >2, Medium = >4, High = >8);
4.3. Символьный тип
Хотя первоначально компьютеры были изобретены для выполнения операций над числами, скоро стало очевидно, что не менее важны прикладные программы для обработки нечисловой информации. Сегодня такие приложения, как текстовые процессоры, образовательные программы и базы данных, возможно, по количеству превосходят математические прикладные программы. Даже такие математические приложения, как финансовое программное обеспечение, нуждаются в обработке текста для ввода и вывода.
С точки зрения разработчика программного обеспечения обработка текста чрезвычайно сложна из-за разнообразия естественных языков и систем записи. С точки зрения языков программирования обработка текста относительно проста, так как подразумевается, что в языке набор символов представляет собой короткую, упорядоченную последовательность значений, то есть символы могут быть определены перечисляемым типом. Фактически, за исключением языков типа китайского и японского, в которых используются тысячи символов, достаточно 128 целых значений со знаком или 256 значений без знака, представимых восемью разрядами.
Различие в способе определения символов в языках Ada и С аналогично различию в способе определения перечисляемых типов. В Ada есть встроенный перечисляемый тип: __
Ada |
type Character is (..., 'А', 'В',...);
и все обычные операции над перечисляемыми типами (присваивание, отношения, следующий элемент, предыдущий элемент и т.д.) применимы к символам. В Ada 83 для типа Character допускались 128 значений, определенных в американском стандарте ASCII, в то время как в Ada 95 принято представление этого типа байтом без знака, так что доступно 256 значений, требуемых международными стандартами.
В языке С тип char — это всего лишь ограниченный целочисленный тип, и допустимы все следующие операторы, поскольку char и int по сути одно и то же:
char с;
int i;
с='А' + 10; /* Преобразует char в int и обратно */
C |
с = i; /* Преобразует int в char */
В языке C++ тип char отличается от целочисленного, но поскольку допустимы преобразования в целочисленный и обратно, то перечисленные операторы остаются допустимыми.
Для неалфавитных языков могут быть определены 16-разрядные символы. Они называются wcharj в С и C++, и Wide_Character в Ada 95.
Единственное, что отличает символы от обычных перечислений или целых, — специальный синтаксис ('А') для набора значений и, что более важно, специальный синтаксис для массивов символов, называемых строками (раздел 5.5).
4.4. Булев тип
Boolean — встроенный перечисляемый тип в языке Ada:
type Boolean is (False, True);
Тип Boolean имеет очень большое значение, потому что:
• операции отношения (=, >, и т.д.) — это функции, которые возвращают значение булева типа;
• условный оператор проверяет выражение булева типа;
• операции булевой алгебры (and, or, not, xor) определены для булева типа.
В языке С нет самостоятельного булева типа; вместо этого используются целые числа в следующей интерпретации:
• Операции отношения возвращают 1, если отношение выполняется, и 0 в противном случае.
• Условный оператор выполняет переход по ветке false (ложь), если вычисление целочисленного выражения дает ноль, и переход по ветке true (истина) в противном случае.
В языке С существует несколько методов введения булевых типов. Одна из возможностей состоит в определении типа, в котором будет разрешено объявление функций с результатом булева типа:
typedef enum {false, true} bool;
C |
if (data-valid (x, y)). . .
но это применяется, конечно, только для документирования и удобочитаемости, потому что такие операторы, как:
C |
b = b + 56; /* Сложить 56 с «true» ?? */
все еще считаются приемлемыми и могут приводить к скрытым ошибкам.
В языке C++ тип bool является встроенным целочисленным типом (не типом перечисления) с неявными взаимными преобразованиями между ненулевыми значениями и литералом true, а также между нулевыми значениями и false. Программа на С с bool, определенным так, как показано выше, может быть скомпилирована на C++ простым удалением typedef.
Даже в языке С лучше не использовать неявное преобразование целых в булевы, а предпочитать явные операторы равенства и неравенства:
C |
if (a + b-2)... /* ...такойвариант.*/
if (а + b ! = О)... /* Этот вариант понятнее, чем */
if (! (а + b))... /*... такой вариант. */
Наконец, отметим, что в языке С применяется так называемое укороченное (short-circuit) вычисление выражений булевой алгебры. Это мы обсудим в разделе 6.2.
4.5. Подтипы
В предыдущих разделах мы обсудили целочисленные типы, позволяющие выполнять вычисления в большом диапазоне значений, которые можно представить в слове памяти, и перечисляемые типы, которые работают с меньшими диапазонами, но не позволяют выполнять арифметические вычисления. Однако во многих случаях нам хотелось бы делать вычисления в небольших диапазонах целых чисел. Например, должен существовать какой-нибудь способ, позволяющий обнаруживать такие ошибки как:
Temperature: Integer;
Temperature := -280; -- Ниже абсолютного нуля!
Compass-Heading: Integer;
Compass-Heading := 365; - Диапазон компаса 0..359 градусов!
Предположим, что мы попытаемся определить новый класс типов:
type Temperatures is Integer range -273 .. 10000; - - He Ada!
type Headings is Integer range 0 .. 359; -- He Ada!
Это решает проблему проверки ошибок, вызванных значениями, выходящими за диапазон типа, но остается вопрос: являются эти два типа разными или нет? Если это один и тот же тип, то
Temperature * Compass_Heading
является допустимым арифметическим выражением на типе целое; если нет, то должно использоваться преобразование типов.
На практике полезны обе эти интерпретации. В вычислениях, касающихся физического мира, удобно использовать значения разных диапазонов. С другой стороны, индексы в таблицах или порядковые номера не требуют вычислений с разными типами: имеет смысл запросить следующий индекс в таблице, а не складывать индекс таблицы с порядковым номером. Для этих двух подходов к определению типов в языке Ada есть два разных средства.
Подтип (subtype) — это ограничение на существующий тип. Дискретные типы (целочисленные и перечисляемые) могут иметь ограничение диапазона.
subtype Temperatures is Integer range -273 .. 10000;
Temperature: Temperatures;
subtype Headings is Integer range 0 .. 359;
Compass_Heading: Headings;
Тип значения подтипа S тот же, что и тип исходного базового типа Т; здесь базовый как у Temperatures, так и у Headings — тип Integer. Тип определяется во время компиляции. Значение подтипа имеет то же самое представление, что и значение базового типа, и допустимо везде, где требуется значение базового типа:
Temperature * Compass_Heading
это допустимое выражение, но операторы:
Temperature := -280;
Compass-Heading := 365;
приводят к ошибке, потому что значения выходят за диапазоны подтипов. Нарушения диапазона подтипа выявляются во время выполнения.
Подтипы могут быть определены на любом типе, для которого его исходный диапазон может быть разумно ограничен:
subtype Upper-Case is Character range 'A'.. 'Z';
U: Upper-Case;
C: Character;
U := 'a'; -- Ошибка, выход за диапазон
С := U; -- Всегда правильно
U := С; -- Может привести к ошибке