C_Kurs_Lekt / C_I_семестр / 07_Struct
.pdfСтруктурыи объединения |
1 |
СТРУКТУРЫ И ОБЪЕДИНЕНИЯ
Из основных типов языка Си пользователь может конструировать производные типы, такие как указатели, массивы, функции, структуры и объединения.
Будем считать, что структура - это объединенное в единое целое множество поименованных элементов в общем случае разных типов. Сравнивая структуру с массивом, следует отметить, что массив - это совокупность однородных объектов, имеющая общее имя - идентификатор массива, а компоненты структуры могут быть разных типов и все должны иметь различные имена.
Данные базовых типов считаются скалярными данными. Массивы и структуры являются агрегирующими типами, а объединения и скалярные данные - относятся к неагрегирующим типам. Агрегирующие типы включают несколько однотипных элементов, для которых выделяется такое необходимое количество памяти, в котором разместятся одновременно значения всех элементов.
Для данных неагрегирующих типов в памяти выделяется область памяти, достаточная для размещения только одного значения.
Структуры и их определение
Пусть, например, библиотечная карточка каталога должна включать сведения, которые приведены для книг в списке литературы. Таким образом, для каждой книги будет указываться следующая информация:
фамилия и инициалы автора;
заглавие книги;
год издания;
Если к библиографической карточке каталога нужно обращаться как к единому целому, то удобно объединить такие разнородные данные удобно с помощью структуры. Каждая структура включает в себя один или несколько объектов (переменных, массивов, указателей, структур и т.д.), называемых элементами структуры.
Библиографическую карточку, с помощью структуры можно представить таким структурным типом:
struct card { char |
*author; |
/* Ф.И.О. автора книги */ |
|
char |
*title; |
/* |
Заголовок книги */ |
int |
year; }; |
/* |
Год издания */ |
В соответствии с синтаксисом языка определение структурного типа начинается со служебного слова struct, вслед за которым помещается выбранное пользователем имя типа. Описания элементов, входящих в структуру, помещаются в фигурные скобки, вслед за которыми ставится точка с запятой. Элементы структуры могут быть как базовых, так и производных типов.
struct имя_структурного_тuna { определения_элементов };
Определения элементов (компонентов) структурного типа внешне подобны определениям данных соответствующих типов. Однако имеется существенное отличие. При определении структурного типа его компонентам не выделяется память, и их нельзя инициализировать. Другими словами, структурный тип не является объектом.
Определив структурный тип, можно определять и описывать конкретные структуры, т.е. структурированные
объекты, например, так: |
|
|
struct |
имя_структурного_типа |
список_структур; |
|
|
struct card rec1, rec2, rесЗ; |
Также можно ввести собственное обозначение для структурного типа используя служебное слово typedef: typedef struct {определения_элементов} обозначение_структурного_тuna;
typedef struct { char |
*author; |
/* |
Ф.И.О. автора книги */ |
char |
*title; |
/* |
Заголовок книги */ |
int |
year; } card1; |
|
/* Год издания */ |
С помощью этого обозначения можно вводить структуры (объекты) так же, как с обычным именем структурного типа (например, struct card в предыдущем примере).
Пример: card1 rec4, rec5;
Структурный тип, которому программист назначает имя с помощью typedef, может в то же время иметь второе имя, вводимое стандартным образом после служебного слова struct:
typedef struct card { char *author; char *title;
int year; } card1;
Здесь card - обозначение структурного типа, вводимое с помощью typedef. Имя card1 введено для того же структурного типа стандартным способом. После такого определения структуры могут вводиться как с помощью названия card1, так и с помощью обозначения того же типа struct card
Структуры могут быть определены одновременно с определением структурного типа: struct имя_структурного_типа { определения_элементов } список_структур;
Если структура определяется однократно, т.е. нет необходимости в разных частях программы определять или описывать одинаковые по внутреннему составу структурированные объекты, то можно не вводить именованный структурный тип, а непосредственно определять структуры одновременно с определением их компонентного состава ( «урезаный» вариант предыдущего случая).
struct { определения_элементов } список_структур;
Выделение памяти для структур.
При определении структуры ей выделяется такое количество памяти, чтобы могли разместиться данные всех элементов.
В зависимости от опции «выравнивать по границам слов» и от аппаратных возможностей системы возможно два случая размещения элементов структуры: непосредственно друг за другом и с выравниванием (с «дырами»). Опция «выравнивать по границам слов» переключается в настройках компилятора или управляется директивой #pragma, которая позволяет управлять опциями компилятора прямо из программы.
При выравнивание память для следующего объекта, определенного в программе, будет выделена не сразу после элемента (если он заканчивается не на границе слова), а с промежутком, оставляемым для выравнивания по
Структурыи объединения |
2 |
принятой границе участка адресного пространства. Машинное слово на «старых» компиляторах ВС/ВС++ - 2 байта, на новых (например, версия 5.02) – 4 байта.
Необходимость выравнивания обуславливается поставленной задачей. Включение выравнивания позволяет получить более быстрый доступ к данным, а отсутствие выравнивания позволяет «плотнее» размещать данные – экономить память.
В зависимости от наличия "пропусков" между элементами изменяется общий объем памяти, выделяемый для структуры. Реальный размер памяти в байтах, выделяемый для структуры, можно определить с помощью операции
sizeof (имя_структуры) или sizeof (имя_структурного_типа)
Пример: sizeof (struct card); sizeof (rec1); sizeof rec2;
Инициализация и присваивание структур.
Для инициализации структур начальными значениями необходимо в определении конкретной структуры после ее имени и знака '=' в фигурных скобках поместить список начальных значений элементов. Например:
struct card rec1={«Петров Иван Иванович», «Изучайте С», 2002}
Вотличие от массивов, присваивание которых можно выполнить только поэлементно, стандарт Си разрешает присваивание структур. Например: rec2 = rec1;
ВС для структур не определены операции сравнения даже на равенство. И сравнивать структуры нужно только поэлементно.
Доступ к элементам структур.
Доступ к элементам (компонентам) структур можно получить с помощью уточненных имен или указателей. Уточненное имя - это выражение с двумя операндами и операцией "точка" между ними. Операция "точка" -
операция доступа к элементу структуры (или объединения). У нее самый высокий ранг наряду со скобками и операцией "стрелка" для доступа к элементам структуры через адресующий ее указатель.
Уточненное имя используется для выбора правого операнда операции "точка" из структуры (или объединения), задаваемой левым операндом. Левый операнд должен иметь структурный тип, а правый операнд должен быть именем компонента (элемента) этой структуры. Тип результата операции "точка", т.е. тип уточненного имени, - это тип именуемого ею компонента (элемента) структуры.
имя_структуры. имя_элемента
struct card rec1={«Петров Иван Иванович», «Изучайте С», 2002} rec1.author - указатель типа char* на строку «Петров Иван Иванович» rec1.title - указатель типа char* на строку «Изучайте С»
rec1.year - переменная типа int со значением 2002
Указатели на структуры определяются, как и указатели на данные других типов. Как обычно, для структурных типов название типа состоит из двух слов - служебного слова struct и имени уже определенного структурного типа, например:
card1 *book; struct card * books;
Указатели на структуры могут вводиться и для безымянных структурных типов. Если название структурного типа введено с помощью typedef, то при определении указателей название этого типа может использоваться без предшествующего служебного слова struct:
При определении указателя на структуру он может быть инициализирован: struct card *p_rec=&rec1
Указатель на структуру, настроенный на конкретную структуру (конкретный объект) того же типа,
обеспечивает доступ к ее элементам двумя способами: |
|
( * указатель_на_структуру ).имя_элемента |
или |
указатель_на_структуру -> имя_элемента
Первый способ традиционный. Он основан на обратимости операций разыменования '*' и получения адреса '&'. Обозначив знак равенства последовательностью '==', можно таким образом формально напомнить эти правила на конкретном примере: если
p_rec == &rec1, то *p_rec == rec1
Доступ к элементам структуры rec1 с помощью разыменования адресующего его указателя p_rec можно в соответствии с приведенными соотношениями реализовать с помощью таких конструкций:
(*p_rec).author == rec1.author
(*p_rec).year == rec1.year
Наличие скобок, ограничивающих операцию разыменования (*p_rec) необходимо т.к операция «точка» имеет более высокий приоритет , чем операция разыменования.
Второй способ для доступа к элементам структур – с помощью указателя на структуру и операции «стрелка» (->). Операция «стрелка» обеспечивает доступк элементу структуры через адресующий ее указатель:
указатель_на_структуру -> имя_элемента.
Операция «стрелка» (операция косвенного выбора компонента) двуместная. Она применяется для доступа к элементу, задаваемому правым операндом (именем компонента структуры), той структуры, которую адресует левый операнд (указатель на структуру). Пример:
p_rec->author == rec1.author p_rec->year == rec1.year
В С возможно определять в качестве компонентов структур массивы и определять массивы состоящие из структур.
Пример массива состоящего из структур и доступа к отдельным элементам:
struct card recs[50]; /* Определение массива структур */
recs[0].author – компонент author типа char[] структуры типа card, являющейся первым элементом массива recs[].
Объединения и битовые поля
Со структурами "в близком родстве" находятся объединения, которые вводятся с помощью служебного слова union.
Объединение можно рассматривать как структуру, все элементы которой имеют нулевое смещение от ее начала. При таком размещении разные элементы занимают в памяти один и тот же участок. Тем самым
Структурыи объединения |
3 |
объединения обеспечивают возможность доступа к одному и тому же участку памяти с помощью переменных разного типа. Необходимость в такой возможности возникает, например, при выделении из внутреннего представления целого числа его отдельных байтов.
Именованный объединяющий тип вводится с помощью определения такого вида: union имя_mипa { определения элементов };
С помощью конструкции union имя_типа можно вводить объединения-объекты.
Как и для структурных типов, с помощью typedef можно вводить обозначения объединяющих типов, не требующие применения union:
typedef union имя_типа { определения элементов } обозначение_тuna;
Пример: typedef union u8
{double d ; int i[4];
char ch[8]; } u8_name;
union u8 a1,a2; u8_name a3,a4;
Объединения не относятся ни к скалярным данным, ни к данным агрегирующих типов.
Битовые поля. Битовое поле может быть только элементом структуры или объединения и вне объектов этих типов не встречается. Битовые поля не имеют адресов и не могут объединяться в массивы. Назначение битовых полей - обеспечить удобный доступ к отдельным битам данных. Кроме того, с помощью битовых полей можно формировать объекты с длиной внутреннего представления, не кратной байту. Это позволяет плотно "упаковывать" информацию, что экономит память.
Описание структуры с битовыми полями должно иметь такой формат: struct { тип_1 имя_поля_1 : ширина_поля_1;
тип_2 имя_поля_2 : ширина_поля_2; } имя_структуры ;
где тип_i - тип поля, который может быть только int, возможно, со спецификатором unsigned или signed; ширина_поля - целое неотрицательное десятичное число, значение которого обычно (в зависимости от
реализации компилятора) не должно превышать длины слова конкретной ЭВМ.
Разрешается поле без имени (для чего указываются только двоеточие и ширина), с помощью которого в структуру вводятся неиспользуемые биты (промежуток между значимыми полями). Нулевая ширина поля вводится, когда необходимо, чтобы следующее в данной структуре поле разместилось с начала очередного слова конкретной ЭВМ.
Вместо служебного слова struct может употребляться union. В этом случае определяется объединение с битовыми полями.
Для обращения к полям используются те же конструкции, что и для обращения к обычным элементам структур:
имя_структуры. имя_поля_i
(*указатель_на_структуру).имя_поля_i
указатель_на_структуру -> имя_поля_i
От реализации зависит порядок размещения в памяти полей одной структуры. Поля могут размещаться как слева направо, так и справа налево. Для компиляторов, работающих под управлением MS-DOS на IBM PC, поля, которые размещены в начале описания структуры, имеют младшие адреса. Таким образом, для примера:
/* Структура позволяющая получить двоичное представление байта-параметра */ union { unsigned char ss;
struct { unsigned а0:1; unsigned al:1; unsigned a2:1;
. . .
unsigned a7:1; } byte;
} cod;
Доступ к битам - cod.byte.a0 . . . cod.byte.a7
Доступ к числу – cod.ss