Керниган и Ритчи - Язык программирования Си (793773), страница 32
Текст из файла (страница 32)
Имена элементов и тегов безкаких-либо коллизий могут совпадать с именами обычных переменных (т. е. не элементов), так как они всегдаразличимы по контексту. Более того, одни и те же имена элементов могут встречаться в разных структурах,хотя, если следовать хорошему стилю программирования, лучше одинаковые имена давать только близкимпо смыслу объектам.9От английского слова tag — ярлык, этикетка. — Примеч. пер.В некоторых изданиях (в том числе во 2-м издании на русском языке этой книги) structure members переводится какчлены структуры.
— Примеч. ред.10Объявление структуры определяет тип. За правой фигурной скобкой, закрывающей список элементов, могутследовать переменные точно так же, как они могут быть указаны после названия любого базового типа.Таким образом, выражениеstruct {…} х, у, z;с точки зрения синтаксиса аналогично выражениюint x, у, z;в том смысле, что и то и другое объявляет x, y и z переменными указанного типа; и то и другое приведет квыделению памяти соответствующего размера.Объявление структуры, не содержащей списка переменных, не резервирует памяти; оно просто описываетшаблон, или образец структуры.
Однако если структура имеет тег, то этим тегом далее можно пользоватьсяпри определении структурных объектов. Например, с помощью заданного выше описания структуры pointстрокаstruct point pt;определяет структурную переменную pt типа struct point. Структурную переменную при ееопределении можно инициализировать, формируя список инициализаторов ее элементов в виде константныхвыражений:struct point maxpt = { 320, 200 };Инициализировать автоматические структуры можно также присваиванием или обращением к функции,возвращающей структуру соответствующего типа.Доступ к отдельному элементу структуры осуществляется посредством конструкции вида:имя-структуры.элементОператор доступа к элементу структуры .
(точка) соединяет имя структуры и имя элемента. Чтобы напечатать,например, координаты точки pt, годится следующее обращение к printf:printf("%d,%d", pt.x, pt.y);Другой пример: чтобы вычислить расстояние от начала координат (0,0) до pt, можно написатьdouble dist, sqrt(double);dist = sqrt((double)pt.x * pt.x + (double)pt.y * pt.y);Структуры могут быть вложены друг в друга.
Одно из возможных представлений прямоугольника — это параточек на углах одной из его диагоналей:struct rect {struct point pt1;struct point pt2;};Структура rect содержит две структуры point. Если мы объявим screen какstruct rect screen;тоscreen.pt1.xобращается к координате х точки pt1 из screen.6.2. Структуры и функцииЕдинственно возможные операции над структурами — это их копирование, присваивание, взятие адреса спомощью & и осуществление доступа к ее элементам. Копирование и присваивание также включают в себяпередачу функциям аргументов и возврат ими значений.
Структуры нельзя сравнивать. Инициализироватьструктуру можно списком константных значений ее элементов; автоматическую структуру также можноинициализировать присваиванием.Чтобы лучше познакомиться со структурами, напишем несколько функций, манипулирующих точками ипрямоугольниками. Возникает вопрос: а как передавать функциям названные объекты? Существует покрайней мере три подхода: передавать компоненты по отдельности, передавать всю структуру целиком ипередавать указатель на структуру.
Каждый подход имеет свои плюсы и минусы.Первая функция, 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 и неиспользуем для этого временной переменной, чтобы подчеркнуть, что структурные параметры передаются позначению так же, как и любые другие.В качестве другого примера приведем функцию ptinrect, которая проверяет: находится ли точка внутрипрямоугольника, относительно которого мы принимаем соглашение, что в него входят его левая и нижняястороны, но не входят верхняя и правая./* ptinrect: возвращает 1, если р в г, и 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;}Если функции передается большая структура, то, чем копировать ее целиком, эффективнее передатьуказатель на нее. Указатели на структуры ничем не отличаются от указателей на обычные переменные.Объявлениеstruct point *pp;сообщает, что рр — это указатель на структуру типа struct point.
Если рр указывает на структуру point,то *рр — это сама структура, а (*рр).х и (*рр).y — ее элементы. Используя указатель рр, мы могли бынаписатьstruct point origin, *pp;рр = &origin;printf ("origin: (%d,%d)\n", (*pp).x, (*pp).y);Скобки в (*рр).х необходимы, поскольку приоритет оператора . выше, чем приоритет *. Выражение*рр.х будет проинтерпретировано как *(рр.х), что неверно, поскольку рр.х не является указателем.Указатели на структуры используются весьма часто, поэтому для доступа к ее элементам была придумана ещеодна, более короткая форма записи.
Если р — указатель на структуру, тор -> элемент-структурыесть ее отдельный элемент. (Оператор -> состоит из знака -, за которым сразу следует знак >.) Поэтомуprintf можно переписать в виде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.6.3. Массивы структурРассмотрим программу, определяющую число вхождений каждого ключевого слова в текст Си-программы.Нам нужно уметь хранить ключевые слова в виде массива строк и счетчики ключевых слов в виде массивацелых.
Один из возможных вариантов — это иметь два параллельных массива:char *keyword[NKEYS];int keycount[NKEYS];Однако именно тот факт, что они параллельны, подсказывает нам другую организацию хранения — черезмассив структур. Каждое ключевое слово можно описать парой характеристикchar *word;int count;Такие пары составляют массив. Объявлениеstruct key {char *word;int count;} keytab[NKEYS];объявляет структуру типа key и определяет массив keytab, каждый элемент которого является структуройэтого типа и которому где-то будет выделена память.
Это же можно записать и по-другому:struct key {char *word;int count;};struct key keytab[NKEYS];Так как keytab содержит постоянный набор имен, его легче всего сделать внешним массивом иинициализировать один раз в момент определения. Инициализация структур аналогична ранеедемонстрировавшимся инициализациям — за определением следует список инициализаторов, заключенныйв фигурные скобки:struct key {char *word;int count;} keytab[] = {"auto", 0,"break", 0,"case", 0,"char", 0,"const", 0,"continue", 0,"default", 0,..."unsigned", 0,"void", 0,"volatile", 0,"while", 0};Инициализаторы задаются парами, чтобы соответствовать конфигурации структуры. Строго говоря, паруинициализаторов для каждой отдельной структуры следовало бы заключить в фигурные скобки, как,например, в{ "auto", 0 },{ "break", 0 },{ "case", 0 },Однако когда инициализаторы — простые константы или строки символов и все они имеются в наличии, вовнутренних скобках нет необходимости.















