Структура – это тип данных, представляющий собой определяемый пользователем набор именованных членов (компонентов). Эти компоненты могут быть любых типов, как встроенных (например, int, char, long, float, double и т.д.), так и созданных пользователем, записанные в любой последовательности. Кроме того, компонента структуры может иметь тип битового поля. Тип структуры в языке С/C++ позволяет обрабатывать сложные структуры данных так же легко, как и простые переменные.
Структуры объявляются при помощи ключевого слова struct. Например:
struct mystruct { … }; // mystruct – это тег структуры, т.е. имя типа,
// созданного пользователем.
Тогда объект с типом данной структуры будет создан объявлением
struct mystruct s, *ps, arrs[10]; /* s имеет тип структуры mystruct;
ps это указатель на тип struct mystruct,
arrs – это массив из 10 стpуктуp типа mystruct */
Если тег структуры опущен, то имеется безтеговая структура. Такую структуру можно использовать для объявления идентификаторов в разделяемом запятыми списке идентификаторов структуры как имеющих данный тип структуры, но нельзя объявлять объекты этого типа где-нибудь еще. Например:
struct { …} s, *ps, arrs[10]; // безтеговая структура
При объявлении структуры с тегом или без него, можно использовать ключевое слово typedef. Например:
typedef struct { … } MYSTRUCT;
MYSTRUCT s, *ps, arrs[10]; // то же, что и struct mystruct s и т.д.
typedef struct { … } YRSTRUCT;
YRSTRUCT y, *yp, arry[20];
Список объявления компонентов в фигурных скобках объявляет типы и имена компонентов структуры. Компоненты структуры могут быть любого типа, за двумя исключениями:
Первое исключение – тип компонента не может быть тот же, что и объявляемая в текущий момент структура, например:
struct mystruct { mystruct s } s1, s2; // так нельзя
Тем не менее, тег структуры может быть указателем на объявляемую структуру. Например:
struct mystruct { mystruct *ps } s1, s2; // так можно
Кроме того, структура может содержать ранее объявленные типы структур (в языке С++ можно опускать ключевое слово struct).
Второе исключение – компонент структуры нигде не может иметь тип "функция, возвращающая …", кроме языка С++, но тип "указатель на функцию, возвращающую …" допустим. В языке С++ ключевое слово struct может иметь компоненты-функции.
Доступ к компонентам структур и объединений выполняется операторами выбора (.) и (->). Операция (.) называется прямым выбором компонента структуры; операция (->) называется косвенным выбором компонента (или указателем) структуры. Например:
struct mystruct
{
int i;
char str[21];
double d;
} s, *sptr=&s;
…
s.i = 3; // присвоение члену i структуры mystruct s;
sptr->d = 1.23; // присвоение компоненту d структуры mystruct s;
Если структура B содержит поле, тип которого есть структура A, то доступ к компонентам A выполняется через два одновременно задаваемых выбора компонента структуры. Например:
struct A
{
int j;
double x;
};
struct B
{
int i;
struct A a;
double d;
} s, *sptr;
…
s.i = 3; // присвоение компоненту i структуры B
s.a.j = 2; // присвоение компоненту j структуры A
sptr->d = 1.23; // присвоение компоненту d структуры B
(sptr->).x = 3.14 // присвоение компоненту x структуры A
Каждое объявление структуры вводит уникальный тип, поэтому в структуре:
struct A
{
int i,j;
double d;
} a, a1;
struct B
{
int i,j;
double d;
} b;
объекты a и a1 оба имеют тип struct A, но объекты a и b имеют различные типы структуры.
Структурам могут присваиваться значения только в том случае, если и исходная структура, и структура назначения имеют один и тот же тип. Например:
a = a1; // так можно; тип один и тот же, поэтому может быть
// выполнено покомпонентное присвоение структур;
a = b; // так нельзя; различные типы;
a.1 = b.1; a.j = b.j; a.d = b.d; // такое присваивание на
// уровне компонентов структуры можно выполнять;
Память распределяется в структуре покомпонентно, слева направо, от младшего к старшему адресу памяти. В следующем примере:
struct mystruct {
int i;
char str[21];
double d;
} s;
объект s занимает достаточное количество памяти для размещения целочисленного значения типа int, 21-байтовой строки и 8-байтового значения типа double.
Имена тегов структур разделяют общее пространство имен с тегами объединений и перечислений (однако в языке С++ имена входящих в структуру перечислений находятся в другом адресном пространстве). Это означает, что в пределах одного контекста такие теги должны иметь уникальные имена. Тем не менее, имена тегов не обязаны отличаться от идентификаторов, находящихся в трех других адресных пространствах: пространстве имен меток, пространстве имен компонентов и едином адресном пространстве.
Имена компонентов в пределах данной структуры или объединения обязаны быть уникальными, но среди разных структур или объединений они могут совпадать. Например:
goto s;
…
s: struct s { // так можно; теги и имена меток находятся в разных адресных пространствах;
int s // так можно; теги, имена меток и имена компонентов находятся в разных адресных пространствах;
float s; // так нельзя: повторение имени компонентов структур;
} s; /* так можно; пространства имен переменных различны. В языке С++ это допустимо только если s не имеет конструктора */
union s { // так нельзя: повторение имен в пространстве тегов;
int s; // так можно: новое пространство компонентов;
float f;
} f; // так можно: пространство имен переменных;
struct t {
int s; // так можно: следующее пространство имен компонентов;
…
} s; // так нельзя: повторение имен переменных
Указатель на структуру типа А допустим в объявлении другой структуры В до объявления структуры А. Например:
struct A; // неполное объявление;
struct B { struct A *pa };
struct A { struct B *pb };
Первое объявление А называется неполным, поскольку в этой точке отсутствует определение А. В данной ситуации неполное объявление допустимо, поскольку в объявлении В размер А необязателен.
Структура может содержать любые комбинации битовых полей с данными других типов.
Целочисленные компоненты типа signed или unsigned можно объявить битовыми полями шириной от 1 до 16 бит. Ширина битового поля и его опциональный идентификатор задаются следующим образом:
Спецификатор_типа <идентификатор-битового поля>:ширина;
где спецификатор_типа это char, unsigned char, int или unsigned int. Битовые поля располагаются, начиная с младшего и кончая старшим битом слова. Выражение "ширина" должно быть задано и должно давать целочисленную константу со значением в диапазоне от 1 до 16.
Если идентификатор битового поля опущен, то число битов, заданное выражением "ширина", распределяется в памяти, но поле при этом остается недоступным программе. Это позволяет создавать битовые шаблоны для, например, аппаратных регистров компьютера, в которых некоторые биты не используются. Например, структура
struct mystruct {
int i:2;
unsigned j:5;
int :4;
int k:1;
unsigned m:4;
} a, b, c;
создает следующее распределение памяти:
Для битового поля типа int (например, signed) старший бит интерпретируется как знаковый бит. Битовое поле шириной 2, содержащее двоичное 11, будет, следовательно, в случае типа unsigned интерпретироваться как 3, а в случае int как -1. В данном примере выражение a.i = 6 поместит в a.i двоичное 10 = -0, не выдавая каких-либо предупреждений. Поле k типа signed int шириной 1 может содержать только значения 1 и 0, так как битовый шаблон 1 будет интерпретирован как «минус» (-).
Примечание: Битовые поля могут быть объявлены только в структурах, объединениях и классах. Доступ к ним выполняется тем же способом выбоpа компонентов (.) и (->), что и для небитовых компонентов. Битовые поля вызывают некоторые проблемы, когда записывается переносимый код, поскольку организация битов в байтах и байтов в словах зависит от конкретной машины. Выражение &mystruct.x недопустимо, так как x – это идентификатор битового поля, и никакой гарантии, что mystruct.x имеет адрес на границе байта, нет.