Структуры и объединения
- Понятия структуры и функции. Массивы структур.
- Объединения.
Лабораторная работа 10. Работа со структурами и массивами структур
Практическая работа 10. Работа с объединениями
1. Понятия структуры и функции. Массивы структур
Структура ("запись" в терминах языка Паскаль) - это составной объект, в который входят компоненты любых типов, за исключением функций.
1.1 Определение структуры
Существует 3 способа определения структур в программе.
1 способ.
struct {
<список описаний>
} <описатели>;
где <список описаний> - это описание компонентов (полей, элементов) структуры (должен быть указан хотя бы один компонент);
<описатели> - это обычно имена переменных, массивов, указателей и функций.
Пример 1. Переменные a и b определяются как структуры, каждая из которых состоит из двух компонентов x и y. Переменная c определяется как массив из 9 таких структур.
struct {
double x,y;
} a,b,c[9];
Пример 2. Каждая из двух переменных-структур date1 и date2 состоит из 3 компонент
struct {
int year
short mont,day;
} date1,date2;
2 способ
Можно явно задать имя типа структуры с помощью ключевого слова tуpedef, а затем это имя использовать для определения переменных. Общий вид описания типа структуры:
typedef struct {
<список описаний>
} <имя типа>
Пример 3. Описан тип структуры с именем empl.
typedef struct {
char name[30];
int d;
} empl;
empl e1,e2; /* Переменные e1,e2 - это структуры типа empl */
3 способ.
Обоснован на применении меток, или шаблонов, структуры (аналогично меткам перечисляемого типа). Метка структуры описывается следующим образом:
struct <метка> {
<список описаний>
};
где <метка> - это идентификатор.
Структуры определяются с помощью меток структур следующим образом :
struct <метка> <список идентификаторов>;
Использование меток структуры необходимо для описания рекурсивных структур.
Пример 4.
struct student { /* student - метка структуры */
char name[23];
int id,age;
char pol;
};
struct student s1,s2; /* s1,s2 - это структуры, тип которых задан меткой student */
struct student studgrup[30]; /* studgrup - это массив структур шаблона student */
Задание шаблона структуры и объявление переменных можно производить одновременно:
struct student { /* student - метка структуры */
char name[23];
int id,age;
char pol;
} s1, s2, studgrup[30];
1.2 Доступ к компонентам структуры
Доступ к компонентам структуры имеет вид:
<имя структуры-переменной>.<имя компоненты>
Например (здесь и далее см. пример 4 выше): s1.id, s2.pol.
Следует иметь в виду, что запись studgrup[20].age дает доступ к полю age 21-го элемента массива studgrup, а запись s1.name[5] дает доступ к 6-му элементу поля name переменной s1.
Если объявлены две переменные типа структуры с одним и тем же именем типа или шаблона, то их можно присвоить одна другой; например, s1=s2.
Переменные типа структуры нельзя сравнивать на "=" или " ≠" .
К структуре, как к любой переменной, можно применить операцию & для вычисления ее адреса.
Пример 5. Дан список автомобилей, каждая строка которого содержит: марку автомобиля, год выпуска, цену. Распечатать список тех автомобилей, год выпуска которых не ранее некоторого заданного года, а цена не превышает некоторой заданной цены.
Стандартная функция fflush (описание в stdio.h) обычно используется для очистки входного потока stdin. В начале работы программы входной поток очищается автоматически.
Рекомендуется перед любой стандартной подпрограммой буферизованного ввода очищать входной поток, что обеспечит защиту от предшествующего некорректного ввода.
#include <stdio.h>
main()
{
struct{
char marka[10]; /* Марка авто */
int year; /* Год выпуска*/
float money; /* Цена */
} avto[20]; /* Массив структур - список данных по автомобилям */
int n, /* Количество элементов в массиве avto */
i, /* Индекс массива avto */
y; /* Контрольный год */
float m; /* Контрольная цена */
printf("\n Введите количество автомобилей n (n<20): ");
scanf("%d",&n);
printf("Введите список из %d автомобилей:\n",n);
for (i=0;i<n;i++)
{ fflush(stdin); gets(avto[i].marka);
fflush(stdin); scanf(“%d”,&avto[i].year);
fflush(stdin); scanf(“%d”,&avto[i].money); }
printf("Введите контрольный год и цену: ");
scanf("%d%f",&y,&m);
for (i=0;i<n;i++)
if (avto[i].year>=y&&avto[i].money<=m)
printf("\n%12s %4d %7.3f", avto[i].marka, avto[i].year, avto[i].money); }
1.3 Структуры и функции
Единственно возможные операции над структурами — это их копирование, присваивание, взятие адреса с помощью & и осуществление доступа к ее элементам. Копирование и присваивание также включают в себя передачу функциям аргументов и возврат ими значений. Структуры нельзя сравнивать. Инициализировать структуру можно списком константных значений ее элементов; автоматическую структуру также можно инициализировать присваиванием.
Существует по крайней мере три подхода: передавать компоненты по отдельности, передавать всю структуру целиком и передавать указатель на структуру. Каждый подход имеет свои плюсы и минусы.
Пример 6. Функция, makepoint, получает два целых значения и возвращает структуру point.
/* makepoint: формирует точку по компонентам х и y */
struct point makepoint(int x, int y)
{
struct point temp;
temp.x = x;
temp.у = у;
return temp;
}
Заметим: никакого конфликта между именем аргумента и именем элемента структуры не возникает; более того, сходство подчеркивает родство обозначаемых им объектов.
Теперь с помощью makepoint можно выполнять динамическую инициализацию любой структуры или формировать структурные аргументы для той или иной функции:
struct rect screen;
struct point middle;
struct point makepoint(int, int);
screen.pt1 = makepoint(0, 0);
screen.pt2 = makepoint(XMAX, YMAX);
middle = makepoint((screen.pt1.x + screen. pt2.x)/2,
(screen.pt1.y + screen.pt2.y)/2);
Следующий шаг состоит в определении ряда функций, реализующих различные операции над точками:
/* addpoint: сложение двух точек */
struct point addpoint(struct point p1, struct point p2)
{
p1.x += p2.x;
p1.y += p2.y;
return p1;
}
Здесь оба аргумента и возвращаемое значение — структуры. Мы увеличиваем компоненты прямо в р1 и не используем для этого временной переменной, чтобы подчеркнуть, что структурные параметры передаются по значению так же, как и любые другие.
Пример 7. Функция ptinrect, которая проверяет: находится ли точка внутри прямоугольника, относительно которого мы принимаем соглашение, что в него входят его левая и нижняя стороны, но не входят верхняя и правая.
/* ptinrect: возвращает 1, если р в r, и 0 в противном случае */
int ptinrect(struct point p, struct rect r)
{
return p.x >= r.ptl.x && p.x < r.pt2.x
&& p.y >= r.ptl.y && p.y < r.pt2.y;
}
Здесь предполагается, что прямоугольник представлен в стандартном виде, т. е. координаты точки pt1 меньше соответствующих координат точки pt2. Следующая функция гарантирует получение прямоугольника в каноническом виде.
#define min(a, b) ((a) < (b) ? (а) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
/* canonrect: канонизация координат прямоугольника */
struct rect canonrect(struct rect r)
{
struct rect temp;
temp.pt1.x = min(r.pt1.x, r.pt2.x);
temp.ptl.y = min(r.pt1.y, r.pt2.y);
temp.pt2.x = max(r.pt1.x, r.pt2.x);
temp.pt2.y = max(r.pt1.y, r.pt2.y);
return temp;
}
Если функции передается большая структура, то, чем копировать ее целиком, эффективнее передать указатель на нее.
1.4 Указатели на структуры
Объявление
struct point *pp;
сообщает, что рр — это указатель на структуру типа struct point. Если рр указывает на структуру point, то *рр — это сама структура, а (*рр).х и (*рр).y — ее элементы.
Используя указатель рр, можно написать
struct point origin, *pp;
рр = &origin;
printf ("origin: (%d,%d)\n", (*pp).x, (*pp).y);
Скобки в (*рр).х необходимы, поскольку приоритет оператора . выше, чем приоритет *. Выражение *рр.х будет проинтерпретировано как *(рр.х), что неверно, поскольку рр.х не является указателем.
Указатели на структуры используются весьма часто, поэтому для доступа к ее элементам была придумана еще одна, более короткая форма записи.
Если р — указатель на структуру, то
р ->элемент-структуры
есть ее отдельный элемент. (Оператор -> состоит из знака -, за которым сразу следует знак >.)
Поэтому можно переписать в виде printf("origin: (%d,%d)\n", pp->x, pp->y);
Операторы . и -> выполняются слева направо. Таким образом, при наличии объявления
struct rect r, *rp = &r;
следующие четыре выражения будут эквивалентны:
r.pt1.х
rp->pt1.x
(r.pt1).x
(rp->pt1).x
Операторы доступа к элементам структуры . и -> вместе с операторами вызова функции () и индексации массива [] занимают самое высокое положение в иерархии приоритетов и выполняются раньше любых других операторов. Например, если задано объявление
struct {
int len;
char *str;
} *p;
то
++p->len
увеличит на 1 значение элемента структуры len, а не указатель р, поскольку в этом выражении как бы неявно присутствуют скобки: ++(р->len). Чтобы изменить порядок выполнения операций, нужны явные скобки. Так, в (++р)->len, прежде чем взять значение len, программа прирастит указатель р. В (р++)->len указатель р увеличится после того, как будет взято значение len (в последнем случае скобки не обязательны).
По тем же правилам *p->str обозначает содержимое объекта, на который указывает str; *p->str++ прирастит указатель str после получения значения объекта, на который он указывал (как и в выражении *s++);
(*p->str)++ увеличит значение объекта, на который указывает str; *p++->str увеличит р после получения того, на что указывает str.
1.5 Массивы структур
Структуры часто образуют массивы. Чтобы объявить массив структур, вначале необходимо определить структуру (то есть определить агрегатный тип данных), а затем объявить переменную массива этого же типа.
Пусть дана информация : name (имя), street (улица), city (город), state (штат) и zip (почтовый код, индекс). Вся эта информация находится в массиве структур типа addr:
struct addr {
char name[30];
char street[40];
char city[20];
char state[3];
unsigned long int zip;
} addr_list[100];
Это выражение создаст 100 наборов переменных, каждый из которых организован так, как определено в структуре addr.
Чтобы получить доступ к определенной структуре, указывайте имя массива с индексом. Например, чтобы вывести ZIP-код из третьей структуры, напишите следующее:
printf("%d", addr_list[2].zip);
Как и в других массивах переменных, в массивах структур индексирование начинается с 0.
Чтобы указать определенную структуру, находящуюся в массиве структур, необходимо указать имя этого массива с определенным индексом. А если нужно указать индекс определенного элемента в структуре, то необходимо указать индекс этого элемента. Таким образом, в результате выполнения следующего выражения первому символу члена name, находящегося в третьей структуре из addr_list, присваивается значение 'X'.
addr_list[2].name[0] = 'X';
2. Объединения
Объединение — это переменная, которая может содержать (в разные моменты времени) объекты различных типов и размеров. Все требования относительно размеров и выравнивания выполняет компилятор.
Объединения позволяют хранить разнородные данные в одной и той же области памяти без включения в программу машинно-зависимой информации. Эти средства аналогичны вариантным записям в Паскале.
Объединение подобно структуре, однако в каждый момент времени может использоваться (или являться активным) только один из компонентов. Как и структуру, объединение можно определить 3 способами (см. п.7.2.1.).
Согласно 1-му способу можно записать:
union {
<описание компонента 1>
<описание компонента 2>
... ... ... ... ... ...
<описание компонента n>
} <описатели>;
Для каждого из этих компонентов 1,2,...n выделяется одна и та же область памяти, то есть они перекрываются. Причем места в памяти выделяется ровно столько, сколько надо тому элементу объединения, который имеет наибольший размер в байтах.
Объединения применяются для:
1)минимизации объема памяти, если в каждый момент времени только один объект из многих является активным;
2)интерпретации основного представления объекта одного типа, как если бы этому объекту был присвоен другой тип.
Доступ к компонентам объединения осуществляется тем же способом, что и к компонентам структур.
имя-объединения.элемент
или
указатель-на-объединение->элемент
Пример 8.
union {
float radius; /* Окружность */
float a[2]; /* Прямоугольник */
int b[3]; /* Треугольник */
position p; /* Точка. position - тип, описанный пользователем */
} geom_fig
Имеет смысл обрабатывать лишь активный компонент, который последним получил свое значение. Так, после присваивания значения компоненту radius не имеет смысла обращаться к массиву b[3].
Объединения могут входить в структуры и массивы, и наоборот. Запись доступа к элементу объединения, находящегося в структуре (как и структуры, находящейся в объединении), такая же, как и для вложенных структур.
Пример 9. Пусть дан массив структур
struct {
char *name;
int flags;
int utype;
union {
int ival;
float fval;
char *sval;
} u;
} symtab[NSYM];
к ival обращаются следующим образом: symtab[i].u.ival
а к первому символу строки sval можно обратиться любым из следующих двух способов:
*symtab[i].u.sval
symtab[i].u.sval[0]
Фактически объединение — это структура, все элементы которой имеют нулевое смещение относительно ее базового адреса и размер которой позволяет поместиться в ней самому большому ее элементу, а выравнивание этой структуры удовлетворяет всем типам объединения.
Операции, применимые к структурам, годятся и для объединений, т. е. законны присваивание объединения и копирование его как единого целого, взятие адреса от объединения и доступ к отдельным его элементам.
Инициализировать объединение можно только значением, имеющим тип его первого элемента; таким образом, упомянутую выше переменную u можно инициализировать лишь значением типа
|