Версия для печати


Перечислимые типы данных, определяемые пользователем
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1145

Andrew Fionik
дата публикации 20-07-2005 09:06

урок из цикла: Азы Delphi.

Перечислимые типы данных, определяемые пользователем

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

Определение

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

Назначение
Перечислимые типы очень похожи на целочисленные типы данных. Кое-кто может задаться вопросом: "А зачем это все нужно, если и так можно пользоваться целочисленными типами данных?" Предположим, что нам нужно определить состояние лифта. Возможными состояниями лифта являются: "стоит", "едет", "отключен". Можно обозначить каждое из состояний значениями 1, 2, 3 и оперировать ими. Какие у нас тут появляются проблемы?

Во-первых - не наглядно. Посмотрит человек со стороны, и ему еще придется вникать, что 1 это "стоит", а 3 это "отключен", и почему не наоборот. Да и самому можно запутаться.

Во-вторых - есть потенциальное место для ошибок. Что если целочисленной переменной, которая обозначает состояние лифта, где-нибудь присвоят значение 4? Что это будет обозначать для кода, обрабатывающего это значение? Что с ним произойдет? Он отработает, но выдаст неверный результат, или программа просто "вывалится" с ошибкой? Придется в каждом месте, где используется эта переменная писать код проверки на правильность значения. А если у нас добавиться еще одно состояние "авария", то придется исправлять код проверяющий правильность значения.

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

Синтаксис
EnumeratedTypeName=(ListOfValues);
Список идентификаторов значений, это простое перечисление идентификаторов значений, разделенных запятой. Список идентификаторов не может быть пустым.

Value0, Value1, Value2, …, ValueN

Примеры описания перечислимых типов

Светофор

Предположим мы хотим иметь перечислимый тип данных, обозначающий цвет светофора (о том, что светофор бывает выключенным, на время забудем). Как мы все знаем, светофор может светить одним из следующих цветов: красный, желтый, зеленый. Обозначим каждый цвет своим идентификатором: Red, Yellow, Green. Назовем перечислимый тип данных, определяющий возможные цвета светофора, как TSemaphoreColor. Описание нашего типа данных будет выглядеть так:

type
	TSemaphoreColor=(Red, Yellow, Green);

NB! Не забываем, что секция описания типов данных начинается с ключевого слова type, см. лекцию 2 "Переменные, типы данных и константы".

Радуга

Вспоминая известную фразу "Каждый охотник желает знать, где сидит фазан", определяем перечислимый тип данных TRainbow.

type
	TRainbow=(Red, Orange, Yellow, Green, Azure, Blue, Violet);

Еще разные примеры

type
	TGender=(Male, Female);
	TCar=(Mercedess, BMW, Toyota, TVR, Mazda); // марки взяты с потолка :-)
	TPlayerCharacterClass=(Fighter, Cleric, Rogue, Wizard);

Порядковые номера

Каждое значение в перечислимом типе имеет свой порядковый номер. Первое значение имеет порядковый номер 0. Второе значение имеет порядковый номер 1 и т.д. Порядковые номера назначаются компилятором в том порядке, в котором идентификаторы перечислены в описании типа.

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

Value0=NumberOfValue0, Value1=NumberOfValue1, …, ValueN=NumberOfValueN

Пример

type
	TNumber=(Two=2, Three=3, Five=5, Seven=Two+Five);

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

При объявлении переменных перечислимого типа нет необходимости объявлять идентификатор типа данных. Описание перечислимого типа может быть использовано прямо при объявлении переменной:

var
	VariableName: (ListOfValues);

Пример

var
	Fighter:(Hornet, Tomcat, Falcon, Raptor);

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

var
	Fighter1:(Hornet, Tomcat, Falcon, Raptor);
	Fighter2:(Hornet, Tomcat, Falcon, Raptor);

Чтобы избежать ошибки компиляции следует изменить описание переменных следующим образом:

var
	Fighter1, Fighter2:(Hornet, Tomcat, Falcon, Raptor);

Операции со значениями перечислимых типов данных

В сущности, перечислимые типы данных не сильно отличаются от целочисленных типов данных.

Вычисление последующего и предыдущего значений

Функции стандартной библиотеки Succ и Pred обеспечивают получение последующего и предыдущего значений, как и для любого другого порядкового типа данных вроде Integer или Byte.

program enumdemo1;
{$APPTYPE CONSOLE}

type

TRainbow=(Red, Orange, Yellow, Green, Azure, Blue, Violet);

var
	Color:TRainbow;
begin
	Color:=Succ(Yellow); {присвоим переменной Color значение,
	принадлежащее типу данных TRainBow, являющееся следующим
за Yellow.}
	{В зависимости от значения Color выведем название цвета}
	case Color of
		Red: WriteLn('Red');
		Orange: WriteLn('Orange');
		Yellow: WriteLn('Yellow');
		Green: WriteLn('Green');
		Azure: WriteLn('Azure');
		Blue: WriteLn('Blue');
		Violet: WriteLn('Violet');
	end;

	Color:=Blue; // присвоим переменной Color значение синего цвета
	Color:=Pred(Color); {присвоим переменной Color, значение цвета
                            являющегося предыдущим для текущего значения Color}

	{В зависимости от значения Color выведем название цвета}
	case Color of
		Red: WriteLn('Red');
		Orange: WriteLn('Orange');
		Yellow: WriteLn('Yellow');
		Green: WriteLn('Green');
		Azure: WriteLn('Azure');
		Blue: WriteLn('Blue');
		Violet: WriteLn('Violet');
	end;
	WriteLn('Press Enter key…');
	ReadLn;
end.

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

var
	Color:TRainbow;
begin
	Color:=Pred(Red);
…

Использование в контрольных выражениях в управляющих структурах

Последовательно выведем с помощью цикла for названия цветов радуги, начиная с Orange и заканчивая Azure.

program enumdemo2;
{$APPTYPE CONSOLE}

type

TRainbow=(Red, Orange, Yellow, Green, Azure, Blue, Violet);

var
	Color:TRainbow;
begin
	// в цикле перебираются значения от Orange до Azure
	for Color:=Orange to Azure do
		{В зависимости от значения Color выведем название цвета}
		case Color of
			Red: WriteLn('Red');
			Orange: WriteLn('Orange');
			Yellow: WriteLn('Yellow');
			Green: WriteLn('Green');
			Azure: WriteLn('Azure');
			Blue: WriteLn('Blue');
			Violet: WriteLn('Violet');
		end;

	WriteLn('Press Enter key...');
	ReadLn;
end.

Сделаем то же самое, только с использованием цикла while. Обратите внимание на функции стандартной библиотеки Low и High. Каждая принимает в качестве единственного параметра идентификатор перечислимого типа данных и возвращает минимальное или максимальное значение, возможное для переменных данного типа.

program enumdemo3;
{$APPTYPE CONSOLE}

type

TRainbow=(Red, Orange, Yellow, Green, Azure, Blue, Violet);

var
	Color:TRainbow;
begin
	{В цикле перебираются значения от минимального
	значения типа TRainbow, до максимального}
	Color:=Low(TRainbow);
	while Color<=High(TRainbow) do
		begin
			{В зависимости от значения Color выведем название цвета}
			case Color of
				Red: WriteLn('Red');
				Orange: WriteLn('Orange');
				Yellow: WriteLn('Yellow');
				Green: WriteLn('Green');
				Azure: WriteLn('Azure');
				Blue: WriteLn('Blue');
				Violet: WriteLn('Violet');
			end;
			Color:=Succ(Color); {Присвоим Color значение цвета
			следующее за текущим значением Color}
		end;
	WriteLn('Press Enter key...');
	ReadLn;
end.

В зависимости от цвета выведем, к каким цветам он относится, "холодным" или "теплым". Будем считать, что все цвета до зеленого включительно являются "теплыми". Для разнообразия будем считать цвета от "конца" к "началу", т.е. от фиолетового цвета к красному.

program enumdemo4;
{$APPTYPE CONSOLE}

type

TRainbow=(Red, Orange, Yellow, Green, Azure, Blue, Violet);

var
	Color:TRainbow;
begin
	{В цикле перебираются значения от максимального
	значения типа TRainbow, до минимального}
	for Color:=High(TRainbow) downto Low(TRainbow) do
		begin
			{В зависимости от значения Color выведем название цвета}
			case Color of
				Red: Write('Red');
				Orange: Write('Orange');
				Yellow: Write('Yellow');
				Green: Write('Green');
				Azure: Write('Azure');
				Blue: Write('Blue');
				Violet: Write('Violet');
			end;
			{А затем добавим его описание, холодный или теплый.}
			if Color>Green then
				WriteLn(' is cold color.')
			else
				WriteLn(' is warm color.');
		end;
	WriteLn('Press Enter key...');
	ReadLn;
end.

Получение порядкового номера значений типа

Порядковый номер значения можно получить, используя функцию стандартной библиотеки Ord. Функция получает в качестве параметра выражение перечислимого типа, и возвращает его порядковый номер.

program enumdemo5;
{$APPTYPE CONSOLE}

type

TRainbow=(Red, Orange, Yellow, Green, Azure, Blue, Violet);

var
	Color:TRainbow;
begin
	{В цикле перебираются значения от максимального
	значения типа TRainbow, до минимального}
	for Color:=Low(TRainbow) to High(TRainbow) do
		begin
			Write('Ordinal value of ');
			{В зависимости от значения Color выведем название цвета}
			case Color of
				Red: Write('Red');
				Orange: Write('Orange');
				Yellow: Write('Yellow');
				Green: Write('Green');
				Azure: Write('Azure');
				Blue: Write('Blue');
				Violet: Write('Violet');
			end;
			{Выведем порядковый номер цвета}
			WriteLn(' is ', Ord(Color));
		end;
	WriteLn('Press Enter key...');
	ReadLn;
end.

Получение значения по его порядковому номеру

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

program enumdemo6;
{$APPTYPE CONSOLE}

type

TRainbow=(Red, Orange, Yellow, Green, Azure, Blue, Violet);

var
	Color:TRainbow;
	LowIndex, HighIndex, ColorIndex:Integer;

begin
	{Заранее вычислим минимально и максимально допустимые
	значения индекса цвета}
	LowIndex:=Ord(Low(TRainbow));
	HighIndex:=Ord(High(TRainBow));
	// выведем приглашение ко вводу
	WriteLn('Please enter index of rainbow color.');
	WriteLn('Valid values are from ', LowIndex, ' to ', HighIndex);
	// прочитаем введенное значение в переменную ColorIndex
	ReadLn(ColorIndex);
	// проверим не ввел-ли пользователь недопустимое значение
	if (ColorIndexor
		(ColorIndex>HighIndex) then
		begin // если это так,...
			// выведем сообщение об ошибке
			WriteLn('Invalid color index ',ColorIndex,' entered.');
			Exit; // досрочно завершим программу
		end;
	// поищем нужный нам цвет
	{В цикле перебираются значения от минимального
	значения типа TRainbow, до введенного}
	Color:=Low(TRainbow);
	while Ord(Color)<>ColorIndex do Color:=Succ(Color);
	{В зависимости от значения Color выведем название цвета}
	case Color of
		Red: WriteLn('Red');
		Orange: WriteLn('Orange');
		Yellow: WriteLn('Yellow');
		Green: WriteLn('Green');
		Azure: WriteLn('Azure');
		Blue: WriteLn('Blue');
		Violet: WriteLn('Violet');
	end;

	WriteLn('Press Enter key...');
	ReadLn;
end.