Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Программирование на языке Си

..pdf
Скачиваний:
15
Добавлен:
12.11.2023
Размер:
17.16 Mб
Скачать

422

Программирование на языке Си

else

{

ptr->prior = ptr-1; ptr->next = ptr+1;

}

ptr++

}

/■* Последний элемент */ ptr->prior = ptr-1; ptr->next NULL;

return 0;

}

В качестве упражнения рекомендуем разработать более на­ дежную схему и формат хранения базы данных и соответст­ вующие функции сохранения (восстановления). В качестве возможного варианта формата хранения базы данных в файле на диске можно использовать следующий:

#1

пате=фамилия йерште=названиеотдела depnumb=KO() отдела рпсе=зарплата

job=код^должности jonam ^-должность

dsAe=dama приема на_работу

#2

пат е=фамилия

где #1, #2,... - номера записей;

name=, depname=, ... - имена полей в структуре, содержащей сведения о сотрудниках учреждения;

фамилия, название отдела, ... - содержимое полей струк­ туры в символьном виде.

Файл в таком формате и символьном представлении значе­ ний полей структур легко прочитать и модифицировать с помо­

Глава 8. Примеры разработки программ

423

щью любого текстового редактора. Имена полей можно исполь­ зовать для контроля правильности обработки записи при вводе (см. гл. 7, форматный ввод). Список занятых элементов базы данных формируется в момент ввода данных из файла.

Для того чтобы подготовить программный комплекс к по­ следующей обработке (трансляция, сборка), в простейшем слу­ чае необходимо с помощью текстового редактора собрать все функции в один файл (набор данных). Этот файл должен иметь следующую структуру:

объектов, используемых во вспомогательных функциях (массив структур типа struct person, управляющие пере­ менные в структуре Ctrl);

описание внешних переменных;

функция main();

функция init();

функция find();

функция fr();

функция delete();

функция input();

функция print();

функция save() или функция save2( );

функция load() или функция load2( ).

Подробно подготовка программного комплекса для работы с базой данных о сотрудниках учреждения описана в главе 9.

8.3. С орти ровка на основе бинарного дерева

Статические и динамические данные. В предыдущих гла­ вах были рассмотрены как элементарные типы данных (целые, вещественные, символьные), так и более сложные производные типы (массивы, указатели, объединения и структуры). Для объ­ ектов перечисленных основных тицов при определении задают­ ся диапазон изменения соответствующих им значений и размер выделяемой для них памяти. В силу этого такие объекты могут считаться статическими. Однако для решения ряда задач, воз­

424

Программирование на языке Си

никающих при построении информационных систем, разработ­ ке баз данных, построении протоколов передачи данных в теле­ коммуникационных сетях и т. п., требуются информационные объекты с более сложной структурой. В процессе обработки этих информационных объектов могут изменяться не только значения объектов, но даже их конфигурация. Такие объекты (мы уже рассматривали их в §6.4) называют динамическими информационными структурами (см. Вирт Н. Алгоритмы и структуры данных. - М.: Мир, 1989 - С. 213). Следует заметить, что компоненты динамических информационных структур на нижних уровнях детализации представляют собой статические объекты, принадлежащие к одному из основных типов данных. Таким образом, основные типы данных используются для фор­ мирования более сложных информационных конструкций.

В §8.2 был приведен пример применения для построения ба­ зы данных, сложных данных в виде двунаправленного линейно­ го списка. Особенностью реализации такой базы данных в §8.2 является выделение для ее работы статического массива струк­ тур. Внутри массива существовали два списка - свободных и занятых элементов. Такой подход позволяет полностью контро­ лировать память, выделенную под массив структур на этапе трансляции программы. Однако, если мы заранее не можем оп­ ределить объем обрабатываемых данных, то применение стати­ ческого массива не всегда оправдано. На практике под динамические информационные структуры выделяют память также динамически по мере возникновения потребности в со­ хранении очередной порции данных. Динамическая память вы­ деляется операционной системой по запросу программы из так называемого динамического сегмента, входящего в адресное пространство исполняемой программы.

Исполняемую программу на языке Си после компиляции и загрузки в память условно можно разделить на 4 части: область команд, стек, область статических данных и область динами­ ческих данных. Область команд содержит машинные команды. Стек используется для временного хранения данных и адресов

Глава 8. Примеры разработки программ

425

возврата при вызове функций. Область статических данных обеспечивает хранение значений переменных программы, а в области динамических данных размещаются при исполнении программы дополнительные данные, которые могут понадо­ биться в процессе ее работы. На рис. 8.11 приведена одна из схем распределения адресного пространства выполняемой про­ граммы.

Область стека

Старшие адреса

(расширяется вниз)

 

Область динамических данных (расширяется вверх)

Область статических данных

Область команд

Младшие адреса

Рис. 8.11. Схема распределения оперативной памяти процесса

Управление динамической памятью. Как мы уже упоми­ нали, в библиотеке языка Си есть функции выделения и осво­ бождения динамической памяти. Благодаря наличию таких функций отпадает необходимость заранее резервировать для программы всю доступную память, что оказывается полезным в ситуациях, когда требуемый объем памяти заранее не известен или зависит от конкретного применения программы.

Работа с динамической памятью (выделение памяти, измене­ ние размера ранее занятого участка, освобождение памяти) про­ изводится с помощью функций malloc(), calloc(), realloc(), free(), описанных в §4.2. Использование функций malloc(), calloc(), free() выше продемонстрировано на многочисленных примерах. Остановимся на особенностях функции realloc().

Функция realloc() может быть использована для изменения размера ранее занятого участка памяти. При этом адрес начала

426

Программирование на языке Си

участка может измениться. Описание (прототип) функции realloc():

void *realloc ( void *oldptr, unsigned int new_size);

где o ld p tr-указатель на участок памяти, ранее выделенный с помощью функции malloc() или саНос();

new size - размер требуемой памяти.

Стандарт языка Си [5] рекомендует для устранения влияния "перемещений" выделяемого с помощью realloc( ) участка па­ мяти следующим образом использовать функцию realloc():

pt = realloc (oldptr, new_size); if (pt != NULL)

oldptr=pt;

Например, если oldptr указывал на часть связного списка и для этой части функцией realloc() нужно выделить новый уча­ сток памяти, то старое значение oldptr может оказаться невер­ ным (не будет адресовать новое продолжение списка). Применение описанной схемы (т.е. явное присваивание oldptr нового адреса) позволит избежать возможных ошибок и сохра­ нить связность списка.

Необходимо отметить, что все функции, за исключением функции free(), возвращают значение указателя, равное NULL, если для выделения запрошенного участка памяти не хватает.

Сортировка с помощью бинарного дерева. Рассмотрим применение функций работы с динамической памятью и дина­ мическими информационными структурами на примере сорти­ ровки с помощью бинарного дерева. Выбор этого способа сортировки определен следующими соображениями.

1.Структуры данных типа "дерево" являются динамически­ ми и важны при построении информационных и телекоммуни­ кационных систем.

2.При сортировке произвольного набора данных заранее не известен его объем, и применение статической памяти, которая выделяется на этапе трансляции, может оказаться неоправдан­ ным в силу возникающих несоответствий, между количеством

Глава 8. Примеры разработки программ

427

данных, подлежащих сортировке, и объемом статически выде­ ленной памяти.

3. Выбранный способ сортировки позволяет освоить столь важные при работе с деревьями операции, как построение дере­ ва и его обход, например, для печати содержимого его вершин.

Описание методов сортировки и работы с динамическими структурами данных можно найти в приведенной выше работе Н.Вирта. В качестве метода для программной реализации опре­ делим бинарную сортировку включением. Этот метод сорти­ ровки предполагает, что вводимые данные запоминаются в виде бинарного дерева (рис. 8.12). Дерево начинается с корня (root), который указывает на первую вершину. Из каждой вершины дерева выходят две ветви, связывающие ее с вершинами нижне­ го уровня.

root

Рис. 8.12. Пример заполненного бинарного дерева

428

Программирование на языке Си

При построении дерева на рис. 8.12 был использован произ­ вольный набор английских слов, записанный в файл. Слова рас­ полагаются в файле в следующем порядке:

l i n e , comma, a r r a y , nam e, ty p e , s t r i n g , u n io n , s i z e , c a s e .

Выбранный метод сортировки определяет следующую про­ цедуру формирования дерева. На первом от корня месте распо­ лагается первое слово, введенное из исходного файла. От него проводятся две ветви (левая и правая), ведущие к двум верши­ нам нижнего уровня. Если следующее слово в алфавитном по­ рядке выше первого слова, то оно располагается в вершине нижнего уровня, к которой ведет левая ветвь (рис. 8.13).

root

Рис. 8.13. Размещение в дереве нового слова

Следующий этап построения дерева вновь начинаем с корне­ вой вершины. Сравниваем слова "array" и "line". Строка "array" выше в алфавитном порядке строки "line", поэтому переходим к вершине нижнего уровня по левой ветви. Сравниваем строки "array" и "comma". Строка "array" также выше в алфавитном по­ рядке строки "comma", поэтому от вершины, содержащей стро­ ку "comma", вновь спускаемся к вершине нижнего уровня по левой ветви. Итог третьего этапа показан на рис. 8.14.

Глава 8. Примеры разработки программ

429

root

Рис. 8.14. Дерево после трех включений

Результат ввода следующей строки "name" приведен на рис. 8.15.

root

Рис. 8.15. Дерево после четырех включений

430

Программирование на языке Си

Первое же сравнение строк "line" и "name" показывает, что "name" располагается в алфавитном порядке ниже "line", поэто­ му из вершины "line" необходимо выйти по правой ветви. Эта ветвь не ведет ни к какой вершине, поэтому к ней и присоеди­ няется вершина нижнего уровня со строкой "name" (см. рис. 8.15).

Итогом описанного процесса будет бинарное дерево, пока­ занное на рис. 8.12.

После того как мы построили бинарное дерево на бумаге, пе­ рейдем к его описанию на языке Си. Каждая вершина должна содержать символьный массив для представления слова в виде строки и указатели на две вершины нижнего уровня - для левой и правой ветвей. Определение структурного типа для структур, представляющих вершины дерева, может быть, например, та­ ким:

s t r u c t n o d e

{

c h a r str[M A X LIN E]; s t r u c t n o d e * l e f t ; s t r u c t n o d e *r i g h t ;

> ;

С учетом приведенного определения структурного типа вер­ шину дерева можно изобразить графически (рис. 8.16).

strfMAXLINEI left | right

Рис. 8.16. Структурное представление вершины бинарного дерева

Как правило, заранее не известны длина каждого конкретно­ го слова - массива str и количество слов в исходном файле. В силу этого целесообразно динамически выделять память как для каждой вершины дерева, так и для собственно массива, храня­ щего слово в виде строки.

Глава 8. Примеры разработки программ

431

Модифицируем описание структурного типа, соответствую­ щего вершине дерева, следующим образом:

s t r u c t

node

{

* s t r ;

c h a r

s t r u c t n o d e * l e f t ; s t r u c t n o d e * r i g h t ;

);

Теперь в структуре типа struct node вместо массива симво­ лов фиксированной длины для слова из сортируемого файла ис­ пользуется указатель на строку. Память для каждой строки (массива типа char[ ]) необходимо запрашивать у операционной системы в соответствии с длиной конкретного слова.

Приведенному описанию соответствует графическое пред­ ставление, изображенное на рис. 8.17.

 

str ----------

left

| right

Рис. 8.17. Модифицированное структурное представление вершины бинарного дерева

С учетом приведенного определения структурного типа для вершин бинарного дерева нарисуем еще раз (рис. 8.18) дерево, приведенное выше на рис. 8.12.

На рис. 8.18 указателям, не содержащим адреса какой-либо вершины, присвоено значение NULL; указатель root типа struct node * содержит адрес начальной вершины дерева.

Уточним алгоритм ввода очередного слова и построения со­ ответствующей вершины.

Введя слово, необходимо, двигаясь от начальной вершины:

1)сравнить введенное слово со словом, хранящимся в дан­ ной вершине дерева (на него указывает str);

Соседние файлы в папке книги