Указатели на структуры

Язык С
Строк программы, исключая математическое обеспечение Является учебным введением в центральную часть языка “C” Hачинаем. Единственный способ освоить новый язык Оператор FOR Набор полезных программ Подсчет символов Подсчет слов Функции Аргументы - вызов по значению Область действия: внешние переменные Резюме Константы Описания Преобразование типов До 9 и буквы от а до F Операции и выражения присваивания Старшинство и порядок вычисления Операторы и блоки Переключатель Цикл DO - WHILE Оператор CONTINUE Основные сведения Функции, возвращающие нецелые значения Еще об аргументах функций Правила, определяющие область действия Статические переменные Блочная структура Рекурсия Указатели и адреса Указатели и массивы Адресная арифметика Указатели символов и функции Указатели - не целые До 12, а не от 0 до 11. Так как за экономию памяти у нас пока не награждают, такой способ проще, чем подгонка индек-сов Инициализация массивов указателей Указатели на функции Структуры Структуры и функции Указатели на структуры Мы продемонстрируем, как правильно выполнить эту задачу Поля Определение типа Обращение к стандартной библиотеке Форматный вывод - функция PRINTF Форматный ввод - функция SCANF Форматное преобразование в памяти Обработка ошибок - STDERR и EXIT Обращение к системе Низкоуровневый ввод/вывод - операторы READ и WRITE Произвольный доступ - SEEK и LSEEK Пример - распечатка справочников Пример - распределитель памяти Константы Синтаксическая нотация Преобразования Первичные выражения Унарные операции Аддитивные операции Операция присваивания Спецификаторы типа Описание структур и объединений Инициализация TYPEDEF Оператор SWITCH Внешнее определение функции Область действия внешних идентификаторов Неявные описания Явные преобразования указателей Анахронизмы Операторы
439386
знаков
0
таблиц
0
изображений

6.4. Указатели на структуры.

Чтобы проиллюстрировать некоторые соображения, связанные

с использованием указателей и массивов структур, давайте

снова составим программу подсчета ключевых строк, используя

на этот раз указатели, а не индексы массивов.

Внешнее описание массива KEYTAB не нужно изменять, но

функции MAIN и BINARY требуют модификации.

MAIN() /* COUNT C KEYWORD; POINTER VERSION */

\(

INT T;

CHAR WORD[MAXWORD];

STRUCT KEY *BINARY(), *P;

WHILE ((T = GETWORD(WORD, MAXWORD;) !=EOF)

IF (T==LETTER)

IF ((P=BINARY(WORD,KEYTAB,NKEYS)) !=NULL)

P->KEYCOUNT++;

FOR (P=KEYTAB; P>KEYTAB + NKEYS; P++)

IF (P->KEYCOUNT > 0)

PRINTF(“%4D %S/N”, P->KEYCOUNT, P->KEYWORD);

\)

STRUCT KEY BINARY(WORD, TAB, N) / FIND WORD */

CHAR WORD / IN TAB[0]...TAB[N-1] */

STRUCT KEY TAB [];

INT N;

\(

INT COND;

STRUCT KEY *LOW = &TAB[0];

STRUCT KEY *HIGH = &TAB[N-1];

STRUCT KEY *MID;

WHILE (LOW <= HIGH) \(

MID = LOW + (HIGH-LOW) / 2;

IF ((COND = STRCMP(WORD, MID->KEYWORD)) < 0)

HIGH = MID - 1;

ELSE IF (COND > 0)

LOW = MID + 1;

ELSE

RETURN(MID);

\)

RETURN(NULL);

\)

 

Здесь имеется несколько моментов, которые стоит отме-

тить. Во-первых, описание функции BINARI должно указывать,

что она возвращает указатель на структуру типа KEY, а не на

целое; это объявляется как в функции MAIN, так и в BINARY.

Если функция BINARI находит слово, то она возвращает указа-

тель на него; если же нет, она возвращает NULL.


·     138 -

Во-вторых, все обращения к элементам массива KEYTAB осу-

ществляются через указатели. Это влечет за собой одно сущес-

твенное изменение в функции BINARY: средний элемент больше

нельзя вычислять просто по формуле

 

MID = (LOW + HIGH) / 2

потому что сложение двух указателей не дает какого-нибудь

полезного результата (даже после деления на 2) и в действи-

тельности является незаконным. эту формулу надо заменить на

 

MID = LOW + (HIGH-LOW) / 2

в результате которой MID становится указателем на элемент,

расположенный посередине между LOW и HIGH.

Вам также следует разобраться в инициализации LOW и

HIGH. указатель можно инициализировать адресом ранее опреде-

ленного объекта; именно как мы здесь и поступили.

В функции MAIN мы написали

FOR (P=KEYTAB; P < KEYTAB + NKEYS; P++)

Если P является указателем структуры, то любая арифметика с

P учитывает фактический размер данной структуры, так что P++

увеличивает P на нужную величину, в результате чего P указы-

вает на следующий элемент массива структур. Но не считайте,

что размер структуры равен сумме размеров ее членов, - из-за

требований выравнивания для различных объектов в структуре

могут возникать “дыры”.

И, наконец, несколько второстепенный вопрос о форме за-

писи программы. Если возвращаемая функцией величина имеет

тип, как, например, в

 

STRUCT KEY *BINARY(WORD, TAB, N)

Tо может оказаться, что имя функции трудно выделить среди

текста. В связи с этим иногда используется другой стиль за-

писи:

 

STRUCT KEY *

BINARY(WORD, TAB, N)

Это главным образом дело вкуса; выберите ту форму, которая

вам нравится, и придерживайтесь ее.

 

6.5. Структуры, ссылающиеся на себя.

Предположим, что нам надо справиться с более общей зада-

чей, состоящей в подсчете числа появлений всех слов в неко-

тором файле ввода. Так как список слов заранее не известен,

мы не можем их упорядочить удобным образом и использовать

бинарный поиск. Мы даже не можем осуществлять последователь-

ный просмотр при поступлении каждого слова, с тем чтобы ус-

тановить, не встречалось ли оно ранее; такая программа будет

работать вечно. (Более точно, ожидаемое время работы растет

как квадрат числа вводимых слов). Как же нам организовать

программу, чтобы справиться со списком произвольных слов?


·     139 -

Одно из решений состоит в том, чтобы все время хранить

массив поступающих до сих пор слов в упорядоченном виде, по-

мещая каждое слово в нужное место по мере их поступления.

OДнако это не следует делать, перемещая слова в линейном

массиве, - это также потребует слишком много времени. Вместо

этого мы используем структуру данных, называемую доичным де-

ревом.

Каждому новому слову соответствует один “узел” дерева;

каждый узел содержит:

указатель текста слова

счетчик числа появлений

указатель узла левого потомка

указатель узла правого потомка

Никакой узел не может иметь более двух детей; возможно от-

сутсвие детей или наличие только одного потомка.

Узлы создаются таким образом, что левое поддерево каждо-

го узла содержит только те слова, которые меньше слова в

этом узле, а правое поддерево только те слова, которые боль-

ше. Чтобы определить, находится ли новое слово уже в дереве,

начинают с корня и сравнивают новое слово со словом, храня-

щимся в этом узле. Если слова совпадают, то вопрос решается

утвердительно. Если новое слово меньше слова в дереве, то

переходят к рассмотрению левого потомка; в противном случае

исследуется правый потомок. Если в нужном направлении пото-

мок отсутствует, то значит новое слово не находится в дереве

и место этого недостающего потомка как раз и является мес-

том, куда следует поместить новое слово. Поскольку поиск из

любого узла приводит к поиску одного из его потомков, то сам

процесс поиска по существу является рекурсивным. В соответс-

твии с этим наиболее естественно использовать рекурсивные

процедуры ввода и вывода.

Возвращаясь назад к описанию узла, ясно, что это будет

структура с четырьмя компонентами:

STRUCT TNODE \( /* THE BASIC NODE */

CHAR WORD; / POINTS TO THE TEXT */

INT COUNT; /* NUMBER OF OCCURRENCES */

STRUCT TNODE LEFT; / LEFT CHILD */

STRUCT TNODE RIGHT; / RIGHT CHILD */

 \);

 

Это “рекурсивное” описание узла может показаться рискован-

ным, но на самом деле оно вполне корректно. Структура не

имеет права содержать ссылку на саму себя, но

 

STRUCT TNODE *LEFT;

описывает LEFT как указатель на узел, а не как сам узел.

·    
140 -

Текст самой программы оказывается удивительно маленьким,

если, конечно, иметь в распоряжении набор написанных нами

ранее процедур, обеспечивающих нужные действия. Мы имеем в

виду функцию GETWORD для извлечения каждого слова из файла

ввода и функцию ALLOC для выделения места для хранения слов.

Ведущая программа просто считывает слова с помощью функ-

ции GETWORD и помещает их в дерево, используя функцию TREE.

 #DEFINE MAXWORD 20

 MAIN() /* WORD FREGUENCY COUNT */

 \(

STRUCT TNODE *ROOT, *TREE();

CHAR WORD[MAXWORD];

INT T;

ROOT = NULL;

WHILE ((T = GETWORD(WORD, MAXWORD)) \! = EOF)

IF (T == LETTER)

ROOT = TREE(ROOT, WORD);

TREEPRINT(ROOT);

 \)

 

Функция TREE сама по себе проста. Слово передается функ-

цией MAIN к верхнему уровню (корню) дерева. На каждом этапе

это слово сравнивается со словом, уже хранящимся в этом уз-

ле, и с помощью рекурсивного обращения к TREE просачивается

вниз либо к левому, либо к правому поддереву. В конце концов

это слово либо совпадает с каким-то словом, уже находящимся

в дереве (в этом случае счетчик увеличивается на единицу),

либо программа натолкнется на нулевой указатель, свидетель-

ствующий о необходимости создания и добавления к дереву но-

вого узла. В случае создания нового узла функция TREE возв-

ращает указатель этого узла, который помещается в родитель-

ский узел.

 

STRUCT TNODE *TREE(P, W)

/* INSTALL W AT OR BELOW P */

STRUCT TNODE *P;

CHAR *W;

\(

STRUCT TNODE *TALLOC();

CHAR *STRSAVE();

INT COND;

IF (P == NULL) \( /* A NEW WORD

HAS ARRIVED */

P == TALLOC(); /* MAKE A NEW NODE */

P->WORD = STRSAVE(W);

P->COUNT = 1;

P->LEFT = P->RIGHT = NULL;

\) ELSE IF ((COND = STRCMP(W, P->WORD)) == 0)

P->COUNT++; /* REPEATED WORD */

ELSE IF (COND < 0)/* LOWER GOES INTO LEFT SUBTREE */

P->LEFT = TREE(P->LEFT, W);

ELSE /* GREATER INTO RIGHT SUBTREE */

P->RIGHT = TREE(P->RIGHT, W);

RETURN(P);

\)

·     141 -

 

Память для нового узла выделяется функцией TALLOC, явля-

ющейся адаптацией для данного случая функции ALLOC, написан-

ной нами ранее. Она возвращает указатель свободного прост-

ранства, пригодного для хранения нового узла дерева. (Мы

вскоре обсудим это подробнее). Новое слово копируется функ-

цией STRSAVE в скрытое место, счетчик инициализируется еди-

ницей, и указатели обоих потомков полагаются равными нулю.

Эта часть программы выполняется только при добавлении нового

узла к ребру дерева. Мы здесь опустили проверку на ошибки

возвращаемых функций STRSAVE и TALLOC значений (что неразум-

но для практически работающей программы).

Функция TREEPRINT печатает дерево, начиная с левого под-

дерева; в каждом узле сначала печатается левое поддерево

(все слова, которые младше этого слова), затем само слово, а

затем правое поддерево (все слова, которые старше). Если вы

неуверенно оперируете с рекурсией, нарисуйте дерево сами и

напечатайте его с помощью функции TREEPRINT ; это одна из

наиболее ясных рекурсивных процедур, которую можно найти.

 

TREEPRINT (P) /* PRINT TREE P RECURSIVELY */

STRUCT TNODE *P;

\(

IF (P != NULL) \(

TREEPRINT (P->LEFT);

PRINTF(“%4D %S\N”, P->COUNT, P->WORD);

TREEPRINT (P->RIGHT);

\)

 \)

 

Практическое замечание: если дерево становится “несба-

лансированным” из-за того, что слова поступают не в случай-

ном порядке, то время работы программы может расти слишком

быстро. В худшем случае, когда поступающие слова уже упоря-

дочены, настоящая программа осуществляет дорогостоящую ими-

тацию линейного поиска. Существуют различные обобщения дво-

ичного дерева, особенно 2-3 деревья и AVL деревья, которые

не ведут себя так “в худших случаях”, но мы не будем здесь

на них останавливаться.

Прежде чем расстаться с этим примером, уместно сделать

небольшое отступление в связи с вопросом о распределении па-

мяти. Ясно, что в программе желательно иметь только один

распределитель памяти, даже если ему приходится размещать

различные виды объектов. Но если мы хотим использовать один

распределитель памяти для обработки запросов на выделение

памяти для указателей на переменные типа CHAR и для указате-

лей на STRUCT TNODE, то при этом возникают два вопроса. Пер-

вый: как выполнить то существующее на большинстве реальных

машин ограничение, что объекты определенных типов должны

удовлетворять требованиям выравнивания (например, часто це-

лые должны размещаться в четных адресах)? Второй: как орга-

низовать описания, чтобы справиться с тем, что функция ALLOC

должна возвращать различные виды указателей ?


·     142 -

Вообще говоря, требования выравнивания легко выполнить

за счет выделения некоторого лишнего пространства, просто

обеспечив то, чтобы распределитель памяти всегда возвращал

указатель, удовлетворяющий всем ограничениям выравнивания.

Например, на PDP-11 достаточно, чтобы функция ALLOC всегда

возвращала четный указатель, поскольку в четный адрес можно

поместить любой тип объекта. единственный расход при этом -

лишний символ при запросе на нечетную длину. Аналогичные

действия предпринимаются на других машинах. Таким образом,

реализация ALLOC может не оказаться переносимой, но ее ис-

пользование будет переносимым. Функция ALLOC из главы 5 не

предусматривает никакого определенного выравнивания; в главе


Информация о работе «Язык С»
Раздел: Информатика, программирование
Количество знаков с пробелами: 439386
Количество таблиц: 0
Количество изображений: 0

Похожие работы

Скачать
48443
0
0

... основаниям. При этом философская абстракция языка оказывается неразрывно связана с основными темами и движениями философии в целом. Более конкретно, на ранние стадии традиционно рассматриваемого в рамках АФ анализа обыденного языка глубокое влияние оказала философия Дж. Э. Мура, особенно его учение о здравом смысле, согласно которому такие понятия, как «человек», «мир», «я», «внешний мир», « ...

Скачать
43709
0
0

... и других странах СНГ, а также облегчение доступа к русской и мировой культуре и науке. Таким образом, судя по данным наших исследований, востребованность русского языка осталась в республике достаточно высокой. Многие представители современной молдавской молодежи продолжают, как их отцы и деды, тянуться к русской культуре, научным и техническим достижениям России. Русский язык остается языком ...

Скачать
39778
0
1

... рисуночное словесно-слоговое письмо). Памятники среднеэламского периода (14—12 вв. до н.э.) выполнены аккадской клинописью. Памятники новоэламского периода относятся к 8—6 вв. до н.э. Был официальным языком в персидском государстве Ахеменидов в 6—4 вв. предполагается, что он, подвергшись влиянию древнеперсидского, сохранился до раннего средневековья. 7. Бурушаски язык Язык бурушаски ( ...

Скачать
64931
0
0

... /диалект), скифский, согдийский, среднеперсидский, таджикский, таджриши (язык/диалект), талышский, татский, хорезмийский, хотаносакский, шугнано-рушанская группа языков, ягнобский, язгулямский и др. Они относятся к индоиранской ветви индоевропейских языков. Области распространения: Иран, Афганистан, Таджикистан, некоторые районы Ирака, Турции, Пакистана, Индии, Грузии, Российской Федерации. Общее ...

0 комментариев


Наверх