Вы находитесь на странице: 1из 16

Всякий структурированный тип данных в языке Pascal характеризуется

множеством входящих в его состав элементов. Константа или переменная


такого типа всегда содержит несколько компонент, каждая из которых также
может быть структурированного типа.
Язык Pascal включает следующие структурированные типы: строки, массивы,
записи, множества, файлы и два порядковых типа данных — перечисляемый и
интервальный
Перечисляемый тип данных — это упорядоченная последовательность скалярных
констант, которые составляют этот тип. В качестве значения каждой из констант мы
используем ее имя. Имена разных констант разделяются запятыми, а совокупность
констант, имеющих перечисляемый тип, помещается в круглые скобки.
В процессе создания программы у нас может появиться необходимость объединить в
одну группу по какому-либу признаку совокупность значений перечисляемого типа. К
примеру, перечисляемый тип ZnakZodiaka (Знак зодиака) включает скалярные
значения Oven, Strelec, Kozerog, Bliznecy, Vesy (Овен, Стрелец, Козерог, Близнецы,
Весы); перечисляемый тип Planeta (Планета) объединяет скалярные значения
Mercury, Venera, Earth, Mars (Меркурий, Венера, Земля, Марс). Описание
перечисляемого типа происходит в разделе описания типов:

type
ZnakZodiaka=(Oven,Strelec,Kozerog,Bliznecy,Vesy);
Описание переменных скалярного типа, объявленные в разделе type, производится
при помощи имен типов:

type
Planeta = (Mercury,Venera,Earth,Mars); var       Solnsystem: Planeta;  
Таким образом, переменная Solnsystem может принимать следующие значения:
Mercury, Venera, Earth или Mars. Также переменные, имеющие перечисляемый тип,
могут быть объявлены в разделе var:

var
Solnsystem: Mercury,Venera,Earth,Mars;
Как мы видим, имена типов здесь уже не присутствуют, и переменные представляют
собой совокупность значений перечисляемого типа. К переменным рассматриваемого
типа можно применить оператор присваивания «:=»

Solnsystem:= Mercury;
Последовательность значений перечисляемого типа нумеруется автоматически,
начиная с нуля:

type
Planeta = (Mercury,Venera,Earth,Mars);
В этом примере Mercury имеет значение 0, Venera — значение 1, Earth — значение 2,
Mars — значение 3.

Интервальный тип данных Паскаль:


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

Примеры диапазонов:
type
diap=0..255;
type
bukv=’A’..’Z’;
Минимальное значение констант называется нижней границей отрезка, который
определяет интервальный тип, а максимальное значение — верхней границей
отрезка. Обязательное условие: нижняя граница всегда должны быть строго меньше
верхней границы отрезка, иначе программа будет работать некорректно, либо вовсе
не работать.

С переменными данного типа можно выполнять все операции и применять к ним


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

C++ содержит довольно много встроенных типов данных. Но этих типов бывает не всегда
достаточно для того, что мы хотим сделать. Итак, C++ содержит возможности, позволяющие
программистам создавать свои собственные типы данных. Эти типы данных
называются пользовательскими типами данных.
Возможно, самый простой пользовательский тип данных – это перечислимый
тип. Перечислимый тип (также называемый перечислением или enumeration, enum) – это
тип данных, в котором каждое возможное значение определяется как символьная константа
(называемая перечислителем, enumerator). Перечисления определяются с помощью
ключевого слова enum. Давайте посмотрим на пример:
// Определение нового перечисления с именем Color

enum Color

// Это перечислители

// Они определяют все возможные значения, которые этот тип может содержать

// Каждый перечислитель отделяется запятой, а не точкой с запятой

color_black,

color_red,
color_blue,

color_green,

color_white,

color_cyan,

color_yellow,

color_magenta, // после последнего перечислителя может быть запятая, но она


не обязательна

}; // однако само перечисление должно заканчиваться точкой с запятой

// Определяем несколько переменных перечислимого типа Color

Color paint = color_white;

Color house(color_blue);

Color apple { color_red };

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


никакой памяти. Когда определяется переменная перечислимого типа (например,
переменная paint в приведенном выше примере), для этой переменной память выделяется.
Обратите внимание, что каждый перечислитель отделяется запятой, а всё перечисление
заканчивается точкой с запятой.

Именование перечислений и перечислителей


Указание имени для перечисления необязательно, но часто оно указывается.
Перечисления без имени иногда называют анонимными перечислениями. Имена
перечислений часто начинаются с заглавной буквы.
Перечислителям должны быть даны имена, и они обычно используют тот же стиль
именования, что и константные переменные. Иногда перечислителям даются имена
ПОЛНОСТЬЮ_ЗАГЛАВНЫМИ_БУКВАМИ, но делать это не рекомендуется, поскольку это
может привести к путанице с именами макросов препроцессора.

Область видимости перечислителя


Поскольку перечислители помещаются в то же пространство имен, что и перечисление,
имя перечислителя нельзя использовать в нескольких перечислениях в одном пространстве
имен:
enum Color

red,

blue, // blue помещается в глобальное пространство имен

green

};

enum Feeling

happy,

tired,

blue // ошибка, blue уже использовался в enum Color в глобальном пространстве


имен

};

Следовательно, для предотвращения конфликтов имен и для документирования кода в


именах перечислителей обычно используются префиксы, например, animal_ или color_.

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

{
color_black, // присвоено значение 0

color_red, // присвоено значение 1

color_blue, // присвоено значение 2

color_green, // присвоено значение 3

color_white, // присвоено значение 4

color_cyan, // присвоено значение 5

color_yellow, // присвоено значение 6

color_magenta // присвоено значение 7

};

Color paint{ color_white };

std::cout << paint;

Приведенная выше инструкция с cout выводит значение 4.


Значение перечислителя можно определить явно. Эти целочисленные значения могут
быть положительными или отрицательными и могут иметь те же значения, что и у других
перечислителей. Любым неопределенным перечислителям присваивается значение на
единицу больше, чем у предыдущего перечислителя.
// определяем новое перечисление с именем Animal

enum Animal

animal_cat = -3,

animal_dog, // присвоено -2

animal_pig, // присвоено -1

animal_horse = 5,
animal_giraffe = 5, // имеет то же значение, что и animal_horse

animal_chicken // присвоено 6

};

Обратите внимание, что в этом случае animal_horse и animal_giraffe имеют


одинаковые значения. Когда это происходит, перечислители становятся неразличимыми – по
сути, animal_horse и animal_giraffe взаимозаменяемы. Хотя C++ позволяет это, обычно
следует избегать присвоения одного и того же значения двум перечислителям в одном
перечислении.
Лучшая практика

Не назначайте перечислителям конкретные значения.


Лучшая практика

Не присваивайте одно и то же значение двум перечислителям в одном перечислении,


если в этом нет веской причины.

Вычисление перечислимого значения и ввод/вывод


Поскольку перечислимые значения вычисляются как целочисленные значения типа int,
они могут быть присвоены целочисленным переменным. Это означает, что они также могут
выводиться (как целочисленные значения), поскольку std::cout знает, как выводить целые
числа.
int mypet{ animal_pig };

std::cout << animal_horse; // перед передачей в std::cout вычисляется как int

Это дает следующий результат:


5

Компилятор не будет неявно преобразовывать целочисленное значение int в


перечислимое значение. Следующее приведет к ошибке компиляции:
Animal animal{ 5 }; // вызовет ошибку компилятора

Однако вы можете заставить его сделать это с помощью static_cast:


auto color{ static_cast<Color>(5) }; // некрасиво

Компилятор также не позволит вам выполнить ввод перечисления с помощью std::cin:


enum Color
{

color_black, // присвоено 0

color_red, // присвоено 1

color_blue, // присвоено 2

color_green, // присвоено 3

color_white, // присвоено 4

color_cyan, // присвоено 5

color_yellow, // присвоено 6

color_magenta // присвоено 7

};

Color color{};

std::cin >> color; // вызовет ошибку компилятора

Один из способов решения проблемы – прочитать значение int и


использовать static_cast, чтобы заставить компилятор поместить целочисленное значение
в перечислимый тип:
int inputColor{};

std::cin >> inputColor;

auto color{ static_cast<Color>(inputColor) };

Каждый перечислимый тип считается отдельным типом. Следовательно, попытка


присвоить перечислители из одного типа перечисления другому типу перечисления вызовет
ошибку компиляции:
Animal animal{ color_blue }; // вызовет ошибку компиляции
Если вы хотите использовать другой целочисленный тип для перечислителей, например,
для экономии полосы пропускания при передаче значения перечислителя по сети, вы можете
указать его в объявлении перечисления.
// Использовать в качестве базы для перечисления

// 8-битный целочисленный тип без знака.

enum Color : std::uint_least8_t

color_black,

color_red,

// ...

};

Поскольку перечислители обычно не используются для арифметических операций или


сравнений, использовать целочисленные типы без знака здесь безопасно. Также, когда мы
хотим выполнить предварительное объявление перечисления, необходимо указать его базу.
enum Color; // Ошибка

enum Color : int; // Okay

// ...

// Поскольку Color был предварительно объявлен с фиксированной базой,

// нам необходимо снова указать базу и в определении.

enum Color : int

color_black,
color_red,

// ...

};

Как и в случае с константными переменными, перечислимые типы отображаются в


отладчике, что делает их более полезными в этом отношении, чем значения определенные с
помощью #define.

Печать перечислителей
Как вы видели выше, попытка напечатать перечислимое значение с
помощью std::cout приводит к печати целочисленного значения перечислителя. Так как же
распечатать сам перечислитель в виде текста? Один из способов сделать это – написать
функцию и использовать оператор if или switch:
enum Color

color_black, // присвоено 0

color_red, // присвоено 1

color_blue, // присвоено 2

color_green, // присвоено 3

color_white, // присвоено 4

color_cyan, // присвоено 5

color_yellow, // присвоено 6

color_magenta // присвоено 7

};

void printColor(Color color)

{
switch (color)

case color_black:

std::cout << "Black";

break;

case color_red:

std::cout << "Red";

break;

case color_blue:

std::cout << "Blue";

break;

case color_green:

std::cout << "Green";

break;

case color_white:

std::cout << "White";

break;

case color_cyan:

std::cout << "Cyan";

break;

case color_yellow:
std::cout << "Yellow";

break;

case color_magenta:

std::cout << "Magenta";

break;

default:

std::cout << "Who knows!";

Выделение памяти для переменных типа enum и предварительное


объявление
Типы enum считаются частью целочисленного семейства типов, и компилятор должен
определить, сколько памяти выделить для переменной enum. Стандарт C++ говорит, что
размер перечисления должен быть достаточно большим, чтобы представлять значения всех
перечислителей. Чаще всего он делает переменные перечисления того же размера, что и
стандартный тип int.
Поскольку компилятору необходимо знать, сколько памяти выделить для перечисления,
предварительно объявлять их вы можете только при указании фиксированной базы.
Поскольку определение перечисления не выделяет никакой памяти, если перечисление
необходимо в нескольких файлах, можно определить его в заголовке и включить с
помощью #include этот заголовок везде, где это необходимо.

Чем полезны перечислители?


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

if (!openFile())
return -1;

if (!readFile())

return -2;

if (!parseFile())

return -3;

return 0; // успех

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


метод – использование перечислимого типа:
enum ParseResult

// для наших перечислителей нам не нужны конкретные значения

success,

error_opening_file,

error_reading_file,

error_parsing_file

};

ParseResult readFileContents()

if (!openFile())
return error_opening_file;

if (!readFile())

return error_reading_file;

if (!parsefile())

return error_parsing_file;

return success;

Это намного легче читать и понимать, чем использовать магические числа в качестве
возвращаемого значения. Кроме того, вызывающий может проверить возвращаемое значение
функции по соответствующему перечислителю, что легче понять, чем проверять
возвращаемый результат на совпадение с определенным целочисленным значением.
if (readFileContents() == success)

// сделать что-то

else

// вывести сообщение об ошибке

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


идентификаторов. Например, предположим, что вы пишете игру, в которой игрок может
носить один предмет, но этот предмет может быть нескольких разных типов. Вы могли бы
сделать это так:
#include <iostream>

#include <string>
enum ItemType

itemtype_sword,

itemtype_torch,

itemtype_potion

};

std::string getItemName(ItemType itemType)

switch (itemType)

case itemtype_sword:

return "Sword";

case itemtype_torch:

return "Torch";

case itemtype_potion:

return "Potion";

}
// На всякий случай, если в будущем добавим новый элемент и забудем обновить
эту функцию

return "???";

int main()

// ItemType - это перечислимый тип, который мы определили выше.

// itemType (i в нижнем регистре) - это имя нашей переменной (типа


ItemType).

// itemtype_torch - это перечислимое значение, которым мы инициализируем


переменную itemType.

ItemType itemType{ itemtype_torch };

std::cout << "You are carrying a " << getItemName(itemType) << '\n';

return 0;

Или, в качестве альтернативы, если бы вы писали функцию для сортировки кучи


значений:
enum SortType

sorttype_forward,

sorttype_backwards
};

void sortData(SortType type)

if (type == sorttype_forward)

// сортируем данные в прямом порядке

else if (type == sorttype_backwards)

// сортируем данные в обратном порядке

Многие языки используют перечисления для определения логических значений.


Логическое значение – это, по сути, просто перечисление с двумя
перечислителями: false и true! Однако в C++ значения true и false определяются как
ключевые слова, а не как перечислители.

Вам также может понравиться