Кафедра: Автоматика и Информационные Технологии
Структуры и объединения
СОДЕРЖАНИЕ
Структура - это именованная совокупность переменных возможно разных типов, расположенная в памяти последовательно друг за другом. Структуры называются пользовательскими типами данных и помогают в организации сложных данных, поскольку позволяют группу связанных между собой переменных трактовать не как множество отдельных элементов, а как единое целое.
Традиционный пример структуры - строка платежной ведомости. Она содержит такие сведения о служащем, как его полное имя, адрес, номер карточки социального страхования, зарплата и т. д. Некоторые из этих характеристик сами могут быть структурами: например, полное имя состоит из нескольких компонент (фамилии, имени и отчества); аналогично адрес, и даже зарплата. Другой пример (более типичный для Си) - из области графики: точка есть пара координат, прямоугольник есть пара точек и т. д.
Главные изменения, внесенные стандартом ANSI в отношении структур, - это введение для них операции присваивания. Структуры могут копироваться, над ними могут выполняться операции присваивания, их можно передавать функциям в качестве аргументов, а функции могут возвращать их в качестве результата. В большинстве компиляторов уже давно реализованы эти возможности, но теперь они точно оговорены стандартом. Для допускается инициализация.
Объявление структуры начинается с ключевого слова struct и содержит список объявлений, заключенный в фигурные скобки:
struct имя_структуры {
список объявлений;
};
имя_структуры иногда называют тегом структуры.
Перечисленные в структуре переменные называются элементами. Элементами структур могут быть:
- переменные и массивы базовых типов,
- переменные и массивы пользовательских типов, кроме типа самой структуры имя_структуры,
- указатели на любые типы, включая и тип самой структуры имя_структуры,
- функции.
Включение в структуры элементов-функций не является общепринятым. Как правило, в этом случае переходят к понятию класса.
Элементы структур являются публичными, то есть к элементам структурных переменных можно обращаться в любом месте области видимости этих переменных.
Приведем пример структуры time:
struct time {
int hour;
int minutes;
};
В нашем примере элементами структуры будут hour и minutes.
Объявление структуры не резервирует памяти. Оно является информацией компилятору о введении пользовательского типа данных. Память выделится при определении структурных переменных.
Если структурный тип в программе больше не будет использоваться, объявляют безымянную структуру одновременно с определением переменной. Например,
struct {
intx, y;
} q;
Однако если структура имеет тег, то этим тегом далее можно пользоваться при определении структурных объектов. Например, с помощью заданного выше описания структуры time строка
struct time t;
определяет структурную переменную t типа structtime. Принято использовать один и тот же термин структура применительно к пользовательскому типу данных и к структурной переменной. Однако, по фразам «объявление структуры» и «определение структуры» ситуация становится однозначной. Для структурной переменной, как и для массива при объявлении сразу выделяется память. Поэтому структурная переменная определяется, а тип объявляется.
Структурную переменную при ее определении можно инициализировать, формируя список инициализаторов ее элементов в виде константных выражений. При этом каждый элемент, сам являющийся структурой или массивом, инициализируется отдельной парой фигурных скобок. Доступ к отдельному элементу структуры осуществляется посредством бинарной операции «точка». Официальное название этой операции: обращение к элементу структуры по имени структуры. Синтаксис операции
Имя_структуры.элемент_структуры
Операция доступа к элементу структуры ‘.’ соединяет имя структуры и имя элемента.
Например,
struct time t = {21, 30};
printf("%d:%d", t.hour, t.minutes);
Структуры могут быть вложены друг в друга. Например, структура chronos содержит две структуры timebegin и end:
struct chronos {
struct time begin, end;
};
struct chronos timer = {{2,4}, {10, 10}};
Выражение timer.begin.minutesобращается к минутам minutesвремени beginиз timer.
В стандарте ANSIC ключевое слово struct при объявлении структурных переменных можно опускать, то есть допустима и общепринята запись .
chronos timer;
Размер структуры в байтах складывается из размера его элементов. Например, sizeof(timer) = 8 байт. Однако, если включена опция компилятора Options-Compiler-Code generation-Word allgnment, то все элементы будут располагаться по четным адресам. Поэтому в случае
struct FIO { char F[25], I[15], Otch[20]};
будем иметь sizeof(FIO) = 26 + 16 + 20 = 62.
Над структурами возможны следующие операции:
- присваивание,
- взятие адреса с помощью &,
- осуществление доступа к ее элементам.
Присваивание используется при передаче структуры в функцию по значению и возврат структуры по значению. Структуры нельзя сравнивать. Инициализировать структуру можно списком константных значений ее элементов; автоматическую структуру также можно инициализировать присваиванием.
Чтобы лучше познакомиться со структурами, напишем несколько функций, манипулирующих time и chronos. Возникает вопрос: а как передавать функциям названные объекты? Существует, по крайней мере, три подхода: передавать компоненты по отдельности, передавать всю структуру целиком и передавать указатель на структуру. Каждый подход имеет свои плюсы и минусы.
Первая функция, maketime, получает два целых значения и возвращает структуру time.
/* maketime: формирует время по компонентам hour и minutes */
time maketime(int hour, int minutes){
time temp;
temp.hour = hour;
temp.minutes = minutes;
return temp;
}
Заметим: никакого конфликта между именем аргумента и именем элемента структуры не возникает; более того, сходство подчеркивает родство обозначаемых им объектов.
Теперь с помощью maketime можно выполнять динамическую инициализацию структуры или формировать структурные аргументы для той или иной функции:
chronos timer;
timer.begin = maketime(0, 0);
timer.end = maketime(12, 5);
Следующий шаг состоит в определении ряда функций, реализующих различные операции над временем. В качестве примера рассмотрим следующую функцию:
/* addtime: сложение времени */
time addtime (time tm1, time tm2)
{
tm1.minutes += tm2.minutes;
tm1.hour += tm2.hour + tm1.minutes/60;
tm1.minutes %= 60;
return tm1;
}
Здесь оба аргумента и возвращаемое значение - структуры.
В качестве другого примера приведем функцию tinchronos, которая проверяет: находится ли данный момент времени внутри нашего интервала.
/* tinchronos: возвращает 1, если t в c, и 0 в противном случае */
int tinchronos (struct time t, struct chronos c)
{
return t.hour >= c.begin.hour
&& t.hour < c.end.hour
&& t.minutes >= c.begin.minutes
&& t.minutes < c.end.minutes;
}
Так как имя структурного типа обладает всеми правами имен типов, то разрешено определять указатели на структуры:
имя_структурного_типа * имя_указателя_на_структуру;
Если функции передается большая структура, то, чем копировать ее целиком, эффективнее передать указатель на нее. Указатели на структуры ничем не отличаются от указателей на обычные переменные. Объявление
time *pt;
сообщает, что pt - это указатель на структуру типа struct time. Если pt указывает на структуру time, то *pt - это сама структура, а (*pt).hour и (*pt).minutes - ее элементы. Используя указатель pt, мы могли бы написать
time origin, *pt;
pt = &origin;
printf("origin: (%d,%d)\n", (*pt).hour, (*pt).minutes);
Скобки в (*pt).hour необходимы, поскольку приоритет операции «.» выше, чем приоритет операции разыменования «*». Выражение *pt.hour будет проинтерпретировано как *(pt.hour), что неверно, поскольку pt.hour не является указателем.
Указатели на структуры используются весьма часто, поэтому для доступа к ее элементам была придумана операция «обращение к элементу структуры по указателю», сокращенно «стрелка» с формой записи «->». Если t — указатель на структуру, то
t->элемент-структуры
есть ее отдельный элемент. Поэтому printf можно переписать в виде
printf("origin: (%d,%d)\n", pt->hour, pt->minutes);
Операции . и -> левоассоциативны, то есть выполняются слева направо. Таким образом, при наличии объявления
chronos ch, *cht = &ch;
следующие четыре выражения будут эквивалентны:
ch.begin.hour
cht->begin.hour
(ch.begin).hour
(cht ->begin).hour
Операции доступа к элементам структуры . и -> вместе с операторами вызова функции () и индексации массива [] занимают самое высокое положение в иерархии приоритетов и выполняются раньше любых других операторов. Например, если задано объявление
struct {
int len;
char *str;
} *p;
то
++p->len
увеличит на 1 значение элемента структуры len, а не указатель p, поскольку в этом выражении как бы неявно присутствуют скобки: ++(p->len). Чтобы изменить порядок выполнения операций, нужны явные скобки. Так, в (++р)->len, прежде чем взять значение len, программа прирастит указатель p. В (р++)->len указатель p увеличится после того, как будет взято значение len (в последнем случае скобки не обязательны).
По тем же правилам *p->str обозначает содержимое объекта, на который указывает str; *p->str++ прирастит указатель str после получения значения объекта, на который он указывал (как и в выражении *s++), (*p->str)++ увеличит значение объекта, на который указывает str; *p++->str увеличит p после получения того, на что указывает str.