6.1  Структура

Структураэто тип данных, представляющий собой определяемый пользователем набор именованных членов (компонентов). Эти компоненты могут быть любых типов, как встроенных (например, 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 имеет адрес на границе байта, нет.