Перечисляемый тип
Перечисляемый тип (сокращённо перечисле́ние, англ. enumeration, enumerated type) — в программировании тип данных, чьё множество значений представляет собой ограниченный список идентификаторов.
Описание и использование
[править | править код]Перечисляемый тип определяется как набор идентификаторов, с точки зрения языка играющих ту же роль, что и обычные именованные константы, но связанные с этим типом. Классическое описание типа-перечисления в языке Паскаль выглядит следующим образом:
type Cardsuit = (clubs, diamonds, hearts, spades);
Здесь производится объявление типа данных Cardsuit (карточная масть), значениями которого может быть любая из четырёх перечисленных констант. Переменная типа Cardsuit
может принимать одно из значений clubs, diamonds, hearts, spades
, допускается сравнение значений типа перечисление на равенство или неравенство, а также использование их в операторах выбора (в Паскале — case
) в качестве значений, идентифицирующих варианты.
Использование перечислений позволяет сделать исходные коды программ более читаемыми, так как позволяют заменить «магические числа», кодирующие определённые значения, на читаемые имена.
На базе перечислений в некоторых языках могут создаваться типы-множества. В таких случаях множество понимается (и описывается) как неупорядоченный набор уникальных значений типа-перечисления.
Перечисляемый тип может использоваться в объявлениях переменных и формальных параметров функций (процедур, методов). Значения перечислимого типа могут присваиваться соответствующим переменным и передаваться через параметры соответствующих типов в функции. Кроме того, всегда поддерживается сравнение значений перечислимого типа на равенство и неравенство. Некоторые языки поддерживают также другие операции сравнения для значений перечислимых типов. Результат сравнения двух перечислимых значений в таких случаях определяется, как правило, порядком следования этих значений в объявлении типов — значение, которое в объявлении типа встречается раньше, считается «меньше» значения, встречающегося позже. Иногда перечислимый тип или некоторый диапазон значений перечислимого типа также может быть использован в качестве типа индекса для массива. В этом случае для каждого значения выбранного диапазона в массиве имеется один элемент, а реальный порядок следования элементов соответствует порядку следования значений в объявлении типа.
Реализация
[править | править код]Обычно в процессе компиляции значения перечислений представляются при помощи целых чисел. В зависимости от конкретного языка программирования такое представление может быть либо полностью скрыто от программиста, либо доступно ему с помощью тех или иных «обходных манёвров» (например, принудительного преобразования значения типа перечисление к значению типа «целое число»), либо даже управляемо программистом (в таких случаях программист имеет возможность явно указать, какими числами будут кодироваться все или некоторые значения типа-перечисления). У всех вариантов есть свои положительные и отрицательные стороны. С одной стороны, возможность использования числовых значений констант, составляющих тип-перечисление, особенно при злоупотреблении ею, лишает смысла использование этих типов и создаёт опасность появления ошибок (когда используются числовые значения, для которых в типе нет соответствующих констант). С другой стороны, явное управление значениями даёт некоторые дополнительные возможности. Например, позволяет использовать типы-перечисления при организации интерфейса с модулями, написанными на других языках, если они используют или возвращают кодированные целыми числами значения из некоторого предопределённого набора.
Ещё одна возможность, которую дают перечислимые типы на уровне реализации языка — экономия памяти. При небольшом объёме типа-перечисления для хранения значения этого типа достаточно нескольких битов (вышеприведённый тип Cardsuit
требует всего два бита на значение, в то время как стандартное целое число на большинстве используемых архитектур занимает 32 бита — в 16 раз больше), и компилятор может использовать этот факт для уплотнения хранения данных в памяти. Это может быть особенно важно, если несколько значений типов-перечислений хранятся в одной записи — уплотнение записей при обработке больших их количеств может освободить много памяти. Компиляторы обычно не реализуют эту возможность, по крайней мере, в последнее время, когда компьютерная память существенно подешевела.
Критика
[править | править код]Тип перечисление является традиционным для развитых языков программирования, используется достаточно широко и часто воспринимается как нечто само собой разумеющееся. Тем не менее, этот тип также не обходится без критики со стороны теоретиков и практиков программирования. Так, при разработке языка программирования Оберон перечисляемые типы попали в список возможностей, которые были удалены из языка. Никлаус Вирт, разработчик языка, назвал следующие причины:
- «во всё возрастающем числе программ непродуманное использование перечислений … приводит к демографическому взрыву среди типов, что, в свою очередь, ведёт не к ясности программ, а к многословию»[1];
- когда тип перечисление экспортируется модулем (то есть становится частью интерфейса) нарушается общее правило — команда экспорта типа экспортирует одновременно все его элементы, тогда как для всех остальных типов экспорт типа скрывает его внутреннюю структуру;
- с точки зрения обеспечения удобочитаемости программ ничто не мешает вместо перечислимого типа использовать просто группу совместно определённых именованных констант, особенно при наличии таких языковых механизмов, как модули или классы.
С другой стороны, например, в Java, первоначально не содержащей перечислимого типа, этот тип был впоследствии введён из соображений не только удобства, но и надёжности: проблема использования вместо перечислений групп именованных констант в том, что отсутствует контроль со стороны компилятора как за уникальностью значений констант, так и за возможностью случайного присваивания переменным значений, не соответствующих ни одной из этих констант.
Описание перечислений в различных языках
[править | править код]В языке Ada перечисления задаются с помощью ключевого слова is
и последующего списка значений через запятую:
type Cardsuit is (clubs, diamonds, hearts, spades);
C и языки с C-подобным синтаксисом
[править | править код]Первоначальный K&R-диалект C не имел перечисляемых типов, однако они были добавлены в стандарте ANSI C.
enum cardsuit
{
CLUBS,
DIAMONDS,
HEARTS,
SPADES
};
Динамические языки слабой типизации с C-подобным синтаксисом (например perl или JavaScript), как правило, не имеют перечислений.
Перечисления в языке C++ прямо наследуют поведение перечислений языка C, за исключением того, что перечисляемый тип в C++ — настоящий тип, и ключевое слово enum
используется только при объявлении такого типа. Если при обработке параметра, являющегося перечислением, какое-либо значение из перечисления не обрабатывается (например один из элементов перечисления забыли обработать в конструкции switch
), то компилятор может выдать предупреждение о забытом значении.[2]
C++ 11 предоставляет второй, типобезопасный тип перечисления, который не преобразуется неявно в целочисленный тип. Это определяется фразой «enum class». Например:
enum class Color {Red, Green, Blue};
Базовый тип является реализация определённых интегрального типа, который достаточно велик, чтобы вместить все перечисленные значения (он не должен быть наименьшим возможным типом!). В C++ вы можете указать базовый тип напрямую. Это допускает «предварительные объявления» перечислений:
enum class Color : long {Red, Green, Blue}; // должен соответствовать по размеру и разметке памяти типа «long»
enum class Shapes : char; // предварительная декларация. Если позже будут определены значения, которые не помещаются в 'char', это ошибка.
C#
[править | править код]enum Cardsuit { Clubs, Diamonds, Spades, Hearts }
В первоначальной Java перечислений не было, вместо них предлагалось использовать классы со статическими константами. Начиная с версии 5 (1.5) перечисления были введены в язык, они представляют собой полноценный класс, в который можно добавлять произвольное количество полей и методов. Перечисления были введены для улучшенного контроля за типобезопасностью.[3]
enum Cardsuit { Clubs, Diamonds, Spades, Hearts }
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
В некоторых языках программирования (например, в языке Haskell) при помощи Алгебраических типов можно эмулировать перечисления. Например, так кодируется булевский тип, содержащий два идентификатора для представления значений истинности:
data Bool = False | True
type
enumType = enum
one, two, three
var a: enumType = three
var b = two
echo a > b
//Обычный enum
enum Colors {
GREEN = 1,
BLUE,
RED
}
//Flags enum
[Flags]
enum Borders {
LEFT,
RIGHT,
TOP,
BOTTOM
}
void draw_borders (Borders selected_borders) {
// equivalent to: if ((Borders.LEFT & selected_borders) > 0)
if (Borders.LEFT in selected_borders) {
}
}
pragma solidity ^0.4.4;
contract SimpleEnum {
enum SomeData {DEFAULT,ONE,TWO}
SomeData someData;
function SimpleEnum(){
someData = SomeData.DEFAULT;
}
function setValues(uint _value){
require(uint(SomeData.TWO) >= _value);
someData = SomeData(_value);
}
function getValue() constant returns (uint){
return uint(someData);
}
}
Примечания
[править | править код]- ↑ Н. Вирт. От Модулы к Оберону . Дата обращения: 17 апреля 2008. Архивировано 19 сентября 2011 года.
- ↑ Перечисления в C++ (c++ enum types) . Дата обращения: 20 апреля 2009. Архивировано 16 апреля 2009 года.
- ↑ Enums . Дата обращения: 13 февраля 2008. Архивировано 27 февраля 2008 года.