Этот раздел является переводом туториала C++ Language
Псевдонимы типов (typedef/using)
Псевдоним типа — это другое имя, по которому можно идентифицировать тип. В C++ любой допустимый тип может иметь псевдоним, чтобы на него можно было ссылаться, используя другое имя.
В C++ есть два синтаксиса для создания таких псевдонимов типов. Первий из них унаследован от языка Си и использует ключевое слово typedef:
typedef существующий_тип псевдоним ;
где существующий_тип является любым типом, фундаментальным или сложным, а псевдоним - это идентификатор с новым именем, назначаемым типу.
Например:
typedef char C;
typedef unsigned int WORD;
typedef char * pChar;
typedef char field [50];
Здесь определяются четыре псевдонима типов: C, WORD, pChar и field как char, unsigned int, char* и char[50], соответственно. Объявленные однажды, эти псевдонимы могут быть использованы в любом объявлении так же, как и любой другой допустимый тип:
C mychar, anotherchar, *ptc1;
WORD myword;
pChar ptc2;
field name;
Недавно был введен второй синтаксис для определения псевдонимов типов в языке C++:
using псевдоним = существующий_тип ;
Например, псевдонимы того же типа, что и выше, могут быть определены как:
using C = char;
using WORD = unsigned int;
using pChar = char *;
using field = char [50];
Псевдонимы, определенные с использованием typedef и с использованием using являются семантически эквивалентными. Единственным различием являются некоторые ограничения typedef в области шаблонов, отсутствующие для using. Следовательно, using является более общим, хотя typedef имеет более длинную историю и, вероятно, чаще встречается в существующем коде.
Обратите внимание, что ни typedef, ни using не создают новый тип данных. Они только создают синонимы существующих типов. Это означает, что тип переменной myword, объявленной выше с типом WORD, может рассматриваться как unsigned int; на самом деле это не имеет значения, так как оба в действительности ссылаются на один и тот же тип.
Псевдонимы типов могут использоваться для сокращения длины длинных или вводящих в заблуждение имен типов, но они наиболее полезны в качестве инструментов для абстрагирования программ от используемых ими базовых типов. Например, используя псевдоним int для ссылки на определенный тип параметра вместо непосредственного использования int, можно легко заменить его на long (или другой тип) в следующих версиях, изменив тип только в объявлении псевдонима.
Объединения
Объединения предоставляют доступ к одной области памяти, представляя ее в виде различных типов данных. Объявление и использование объединения такие же, что и у структуры, но функциональность полностью различается:
union type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;
Это создаст новый тип объединения, идентифицируемый как type_name, все поля которого занимают одно и то же физическое пространство в памяти. Размер этого типа равен размеру наибольшего из его членов. Например:
union mytypes_t {
char c;
int i;
float f;
} mytypes;
это объявляет объект (mytypes) с тремя членами:
mytypes.c
mytypes.i
mytypes.f
Все члены этого объединения имеют различные типы данных. Однако, по той причине, что все они ссылаются на одну и ту же область памяти, модификация одного из этих членов влияет на значение каждого из них. Невозможно хранить различные значения в этих членах, не зависящее от других.
Одним из применений объединений является возможность доступа к значению как полностью, так и в виде массива или структуры более мелких элементов. Например:
union mix_t {
int l;
struct {
short hi;
short lo;
} s;
char c[4];
} mix;
Если мы предполагаем, что система, на которой исполняется программа, имеет тип int с размером 4 байта, а тип short 2 байта, объединение, определенное выше, предоставляет доступ к одной и той же группе из четырех байт: mix.l, mix.s и mix.c, которые мы можем использовать в зависимости от того как мы хотим получить доступ к этим байтам: как к целому значению типа int, как к двум значениям типа short или как к массиву элементов типа char, соответственно. В примере смешаны типы, массивы и структуры в объединении, чтобы продемонстрировать различные способы доступа к данным. Для little-endian систем это объединение может быть представлено как:
Точное выравнивание и порядок членов объединения в памяти зависят от системы, что может создавать проблемы переносимости.
Анонимные объединения
Когда объединения являются членами класса (или структуры), они могут быть объявлены без имени. В этом случае, они становятся анонимными объединениями, а их члены доступны из объектов непосредственно по именам членов. Например, рассмотрим различия между этими двумя объявлениями структур:
Структура с обычным объединением | Структура с анонимным объединением |
struct book1_t { char title[50]; char author[50]; union { float dollars; int yen; } price; } book1; |
struct book2_t { char title[50]; char author[50]; union { float dollars; int yen; }; } book2; |
Единственное различие между двумя этими типами заключается в том, что в первом случае объединение имеет имя (price), тогда как во втором случае имени нет. Это влияет на способ доступа к членам dollars и yen объекта этого типа. Для объекта первого типа (с обычным объединением), это будет:
book1.price.dollars
book1.price.yen
тогда как для объекта второго типа (с анонимным объединением), это будет:
book2.dollars
book2.yen
Опять же, помните, что ввиду того, что это объединение, члены dollars и yen в действительности разделяют один и тот же участок памяти, так что они не могут быть использованы для хранения различных значений в одно и то же время. Для price может быть задан либо dollars, либо yen, но не оба одновременно.
Перечислимые типы (enum)
Перечислимые типы — это типы, которые определяются набором пользовательских идентификаторов, известных как перечислители, в качестве возможных значений. Объекты перечислимых типов могут принимать любой из этих перечислителей в качестве значения.
Их синтаксис:
enum type_name {
value1,
value2,
value3,
.
.
} object_names;
Это создает тип type_name, который может принимать в качеcтве значения любое из value1, value2, value3, ... Объекты (переменные) этого типа могут быть непосредственно созданы как object_names.
Например, новый тип переменной с именем colors_t может быть определен для хранения цветов с помощью следующего объявления:
enum colors_t {black, blue, green, cyan, red, purple, yellow, white};
Обратите внимание, что в этом объявлении нет другого типа, ни фундаментального, ни составного. Проще говоря, это создает совершенно новый тип данных, не основывая его на каком-либо другом существующем типе. Возможными значениями переменных этого нового типа color_t могут быть перечислители, указанные в фигурных скобках. Например, после объявления перечислимого типа colors_t допустимы следующие выражения:
colors_t mycolor;
mycolor = blue;
if (mycolor == green) mycolor = red;
Значения перечислимых типов, объявленные при помощи enum, могут быть неявно приведены к целочисленному типу и наоборот. Фактически, элементам таких enum всегда соответствует внутренний целочисленный эквивалент, в который и из которого они могут быть преобразованы неявно. Если не указано иное, целочисленный эквивалент первого элемента перечисления равен 0, эквивалент второго равен 1, эквивалент третьего равен 2 и так далее. Следовательно, в типе данных colors_t, определенном выше, black является эквивалентом 0, blue эквивалент 1, green эквивалент 2 и так далее.
Конкретное целочисленное значение может быть указано для любого из возможных значений в перечислимом типе. Если для какого-либо элемента в перечислении не указано конкретное числовое значение, оно предполагается равным значению предыдущего элемента плюс 1. Например:
enum months_t { january=1, february, march, april,
may, june, july, august,
september, october, november, december} y2k;
В этом случае переменная y2k перечислимого типа months_t может содержать любое из 12 возможных значений, которые идут с january по december и которые эквивалентны значениям от 1 до 12 (не от 0 до 11, так как january равен 1).
Неявное приведение работает в обоих направлениях: значению типа months_t может быть присвоено значение 1 (которое будет эквивалентно january) или целочисленной переменной может быть присвоено значение january (эквивалент 1).
Перечислимые типы enum class
В C++ можно создавать реальные типы перечислений, которые не могут быть неявно преобразованы в int и не имеют значений перечислителя типа int, таким образом сохраняя безопасность типов. Они объявляются с помощью enum class (или enum struct) вместо enum:
enum class Colors {black, blue, green, cyan, red, purple, yellow, white};
К каждому из значений перечисления типа enum class необходимо обращаться через его тип (это также возможно для enum, однако, не является обязательным). Например:
Colors mycolor;
mycolor = Colors::blue;
if (mycolor == Colors::green) mycolor = Colors::red;
Перечислимые типы, объявленные enum class, имеют больший контроль над их базовым типом; это может быть любой целочисленный тип данных, такой как char, short или unsigned int, который служит для указания размера типа. Это указывается с помощью двоеточия и базового типа, следующими за перечислимым типом. Например:
enum class EyeColor : char {blue, green, brown};
Здесь EyeColor является новым типом, имеющим такой же размер, что и тип char (1 байт).