Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
KursProekt.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
4.67 Mб
Скачать

Методические указания по выполнению курсовой работы

1. Общие сведения о динамических структурах данных

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

Динамические переменные в языках Паскаль и Си++ образуются с помощью ссылочного типа данных (ссылок или указателей). Указатель имеет значение адреса той переменной, на которую он указывает. В течение периода работы программного модуля один и тот же указатель может содержать адреса различных динамических переменных.

Ссылочный тип данных широко используется в программах для формирования сложных структур данных: списков, очередей, деревьев и их разновидностей. Аппарат ссылок позволяет динамически образовать любую структуру, элементы которой будут размещаться в памяти на любых свободных местах, в произвольном порядке и связываться друг с другом с помощью ссылок. Кроме того, можно включать в эту структуру новые элементы, исключать старые, не изменяя положения остальных. Для этого необходимо лишь организовать новые взаимные ссылки.

В качестве примера рассмотрим операции с динамическими цепочками (списками).

Динамические цепочки это последовательности взаимно связных динамических переменных, которые можно представить в виде графической модели рис. 1.

Рис. 1. Графическая модель динамической цепочки

Эта модель называется однонаправленным списком. Начало динамической цепочки должно быть зафиксировано, в противном случае доступ ко всей цепочке буден утерян. Для обозначения окончания цепочки в поле ссылки последнего элемента помещают специальную константу ссылочного типа, которая никуда не указывает (NIL в языке Паскаль и NULL – в языке Си++). Типовыми операциями с динамическими списками являются:

– создание и заполнение динамического списка;

– вывод данных динамического списка;

– поиск заданного элемента;

– удаление заданного элемента списка;

– добавление нового элемента в список;

– обмен местами заданных элементов списка.

2. Особенности реализации операций с динамическими списками на языке Pascal

Для создания динамических переменных используется процедура:

New(p1);

где р1 – имя ссылочной переменной.

После выполнения этой процедуры переменной р1 будет присвоено значение адреса динамической переменой, для которой выделено место в динамической области ОП в соответствии с базовым типом.

Для освобождения памяти от динамической переменной используется процедура:

Dispose(p1);

где р1 – имя ссылочной переменной.

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

Для решения этой задачи используются функции:

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

SizeOf(p1) – определяет длину динамической переменной, которой нужно выделить память, причём аргументом этой функции является имя переменной ссылочного типа или базовый тип.

Для реализации типовых операций будут использоваться следующие типы данных и переменных:

Type

ukazat=^elem; ссылочный тип; ссылка на запись типа elem }

elem=Record {тип элемента списка}

dan:String[10]; {поле данных элемента}

uk:ukazat {поле указателя элемента}

End;

Var

un, {указатель начала списка}

p,q:ukazat; {текущие указатели на элементы динамической цепочки}

d:String[10];

flag:Boolean; {признак продолжения работы со списком}

n:Integer;

Создание и заполнение динамического списка производится в соответствии со схемой программы, представленной на рис. 2. Фрагмент программы представлен ниже.

{Ввод, создание и заполнение динамического списка}

WriteLn(Вводите данные: Enter – конец слова,* – конец ввода);

New(un); {Создание первой динамической переменной в списке}

p:=un; {Указатели un и p указывают на неё}

ReadLn(p^.dan); {Ввод данных в поле данных динамической переменной}

p^.un:=Nil;

flag:=True;

While flag Do {Цикл для формирования и заполнения остальных элементов списка}

Begin

ReadLn(d);

If d<>'' Then {Проверка свободных участков памяти}

If MaxAvail<SizeOf(elem) Then

Begin

WriteLn (Не хватает памяти);

flag:=False

End

Else {Создание новой д. п. и включение её в список}

Begin

New(q); {Создание новой д. п.}

q^.dan:=d; {Заполнение поля данных}

q^.uk:=NIL;

p^.uk:=q; {Установка указателя текущего элемента на новый}

p:=q

End

Else flag:=False

End;

Рис. 2. Схема программы создания и заполнения динамического списка

Вывод данных динамического списка производится в соответствии с программой, фрагмент которой приведён ниже:

{Вывод данных динамического списка}

p:=un; {Присвоение начального адреса}

While p<> Nil Do

Begin

WriteLn(p^.dan); {Вывод значения поля данных}

p:=p^.uk {Присвоение следующего адреса указателю p}

End;

Поиск заданного элемента динамического списка производится в соответствии со схемой программы, представленной на рис. 3. Фрагмент программы приведён ниже:

{Поиск заданного элемента списка}

Write(Введите слово для поиска –);

ReadLn(d);

p:=un;

q:=un;

n:=1;

While (p<>NIL) Аnd (p^.dan<>d) Do

Begin

q:=p; {Сохранение адреса предыдущего элемента}

p:=p^.uk; {Присвоение следующего адреса}

Inc(n)

End;

If p=NIL Then WriteLn(Слово не найдено)

Else WriteLn(Номер элемента = ,n:2);

{p указывает на динамическую переменную с заданным словом, если слово найдено, а q – на предыдущий, если это не первый элемент}

Рис. 3. Схема программы поиска заданного элемента динамического списка

Удаление элемента из начала списка производится в соответствии с фрагментом программы, приведённым ниже:

{Удаление элемента из начала списка}

p:=un; {Сохранение указателя на первый элемент}

un:=un^.uk; {Изменение указателя начала цепочки:

второй становится первым}

Dispose(p); {Удаление “старого” первого}

Удаление элемента из конца списка производится в соответствии с фрагментом программы, приведённым ниже:

{Удаление элемента из конца списка}

p:=un;

While p^.uk<>NIL Do {Продвижение до конца цепочки; q

указывает на элемент,

предшествующий p^}

Begin

q:=p;

p:=p^.uk

End;

q^.uk:=NIL; {Замена ссылки предпоследнего элемента на NIL; предпоследний ставится последним}

Dispose(p); {Освобождение памяти от последнего элемента}

Исключение элемента, следующего за заданным, производится в соответствии с фрагментом программы, приведённым ниже:

{Удаление элемента, следующего за заданным, на

заданный элемент указывает указатель p}

q:=p^.uk; {Сохранение указателя на удаляемый элемент}

p^.uk:=q^.uk; {Замена ссылки заданного элемента на ссылку на

элемент, следующий за удаляемым}

Dispose(q);

Добавление элемента в начало списка производится с помощью операторов, приведённых ниже:

{Добавление элемента в начало списка}

New(q); {Создание новой динамической переменной}

q^.uk:=un; {Запись в поле ссылки нового элемента адреса элемента, который был первым}

un:=q; {Установка указателя на новый первый элемент}

Включение нового элемента в список после заданного производится с помощью операторов, приведённых ниже:

{Включение нового элемента в список после заданного,

на заданный элемент указывает p}

. . . . . . . . . . . .

New(q); {Создание новой динамической переменной}

q^.uk:=p^.uk; {Запись в поле ссылки нового элемента адреса элемента,

который будет за ним}

p^.uk:=q; {Замена ссылки заданного элемента на ссылку на новый,

включаемый в список элемент}

Освобождение памяти от динамических переменных производится в соответствии с фрагментом программы, приведённым ниже:

{Освобождение памяти от динамических переменных}

p:=un; {Присвоение начального адреса}

While p<>NIL Do

Begin

p:=p^.uk; {Присвоение следующего адреса}

Dispose(un); {Удаление элемента, предшествующего текущему, на который указывает p}

un:=p {Сохранение указателя на предыдущий элемент}

End;

3. Особенности реализации операций с динамическими списками на языке C++

Формирование динамических структур данных можно организовать с помощью указателей и средств для динамического выделения памяти. Указанными средствами являются библиотечные функции, описанные в заголовочных файлах alloc.h и stdlib.h стандартной библиотеки (файл alloc.h не является стандартным). В таблице 1 приведены сведения об этих библиотечных функциях. Функции malloc(), calloc() и realloc() динамически выделяют память в соответствии со значениями параметров и возвращают адрес выделенного участка памяти. Для универсальности тип возвращаемого значения каждой из этих функций – void*. Этот указатель (указатель такого типа) можно преобразовать к указателю любого типа с помощью операции явного приведения типа (тип*).

Функция free() решает обратную задачу – освобождает память, выделенную перед этим с помощью одной из трех функций malloc(), calloc() или realloc(). Сведения об этом участке памяти передаются в функцию free() с помощью указателя – параметра типа void*. Преобразование указателя любого типа к типу void* выполняется автоматически, поэтому вместо формального параметра void* bl можно подставить в качестве фактического параметра указатель любого типа без операции явного приведения типов.

Таблица 1

Функции для выделения и освобождения памяти

Функция

Прототип

Краткое описание

malloc

void* malloc (unsigned s);

Возвращает указатель на начало области (блока) динамической памяти длиной в s байт. При неудачном завершении возвращает значение NULL.

calloc

void* calloc (unsigned n, unsigned m);

Возвращает указатель на начало области (блока) обнуленной динамической памяти, выделенной для размещения n элементов по m байт каждый. При неудачном завершении возвращает значение NULL.

realloc

void* realloc (void* bl, unsigned ns);

Изменяет размер блока ранее выделенной динамической памяти до размера ns байт. bl – адрес начала изменяемого блока. Если bl равен NULL (память не выделялась), то функция выполняется как malloc.

free

void* free (void* bl);

Освобождает ранее выделенный участок (блок) динамической памяти, адрес первого байта которого равен значению bl.

Для динамического распределения памяти в языке C++ также существуют две особые унарные операции new и delete.

new – операция для динамического распределения памяти, имеющая формат

new  имя_типа

либо

new  имя_типа  инициализатор

Необязательный инициализатор – это выражение в круглых скобках. Операция позволяет выделить и сделать доступным свободный участок в основной памяти, размеры которого соответствуют типу данных, определяемому именем типа. В выделенный участок заносится значение, определяемое инициализатором. При отсутствии в операции new инициализатора значение, которое заносится в выделенный участок памяти, не определено. В случае успешного выполнения операция new возвращает адрес начала выделенного участка памяти. Если участок нужных размеров не может быть выделен (нет памяти), то операция возвращает нулевое значение адреса (NULL). Синтаксис применения операции:

указатель = new  имя_типа  инициализатор

Указатель, которому присваивается получаемое значение адреса, должен относиться к тому же типу данных, что и имя_типа в операции new, и предварительно должен быть определен:

тип  * имя_указателя;

Здесь имя указателя – это идентификатор. Продолжительность существования выделенного с помощью операции new участка памяти – от точки создния до конца программы или до его явного освобождения. Если в качестве имени типа в операции new используется массив, то для массива должны быть полностью определены все размерности. Но и при этом инициализация участка памяти, выделяемого для массива, запрещена.

delete – операция, также необходимая для динамического распределения памяти. При использовании оператора

delete  указатель;

происходит явное освобождение выделенного операцией new участка памяти. Указатель адресует освобождаемый участок памяти, ранее выделенный с помощью операции new. Повторное применение операции delete к тому же указателю дает неопределенный результат. Также непредсказуем результат применения этой операции к указателю, получившему значение без использования операции new. Однако применение delete к указателю с нулевым значением не запрещено, хотя и не имеет особого смысла. Для освобождения памяти, выделенной для массива, используется следующая модификация того же оператора:

delete [ ] указатель;

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

Далее в примере рассмотрен динамический список, элементы которого состоят из информационной части (целое число и его порядковый номер) и указателя на следующий элемент.

struct info //Определение структурного типа для числа

{

int nomer; //Порядковый номер

int znachenie; //Значение

};

struct element //Структурный тип для элемента списка

{

info chislo; //Структура с информацией о числе

element *next; //Указатель на следующий элемент списка

};

element *first, //Указатель начала списка

   *last, //Указатель на очередной элемент

   *list; //Указатель на элементы списка

int n;

Фрагмент программы для создания и заполнения динамического списка представлен ниже.

int zn;

printf("Vvodite dannie");

n=1;

first=new element; //Создание первой динамической переменной списка

(*first).chislo.nomer=n; //Ввод значения в поле данных номера первой переменной

printf("\nVvedite znachenie %d chisla: ",n); //

scanf("%d",&zn);

(*first).chislo.znachenie=zn; //Ввод значения в поле данных числа

first->next=NULL; //В поле указателя первой переменной – NULL, //так как остальные переменные еще не созданы

last=first; //Указатель last указывает на первую переменную

int flag=1;

while (flag) //Цикл для создания и заполнения остальных элементов списка

{

printf("Prodolzhit? 1 - da, 0 - net\t");

scanf("%d",&flag);

if (flag)

{

n++;

list=new element; //Создание новой динамической переменной

(*list).chislo.nomer=n; //Ввод значения в поле данных новой переменной

printf("\nVvedite znachenie %d chisla: ",n);

scanf("%d",&zn);

(*list).chislo.znachenie=zn; //Ввод значения в поле данных

list->next=NULL; // В поле указателя первой переменной – NULL, //так как остальные переменные еще не созданы

last->next=list; //Предыдущий элемент указывает на новый

last=list; // Указатель last указывает на новую переменную

}

}

Вывод данных динамического списка производится в приведенном ниже фрагменте программы.

printf("Nomer\tZnachenie\n");

list=first; //Присвоение текущему указателю //адреса начала списка

while (list) //Цикл для вывода данных списка

{

printf("%3d\t",(*list).chislo.nomer); //Вывод значения поля данных

printf("%5d\n",(*list).chislo.znachenie); // Вывод значения поля данных

list=list->next; //Присвоение текущему указателю //адреса следующей переменной списка

}

getch();

Поиск заданного элемента динамического списка производится в следующем фрагменте программы.

element *poisk() //Функция poisk возвращает значение типа //указатель на соответствующий элемент списка

{

int i,zn;

printf("1 - poisk po nomeru\n2 - poisk po kluchu\n");

char vibor=getch();

switch (vibor)

{

case '1':printf("Nomer elementa = ");

scanf("%d",&i);

int k=0;

list=last=first; //Указатели last и list указывают на первую переменную

while ((last)&&(k!=i)) //Цикл для поиска элемента с заданным номером

{

k++;

last=list; //Сохранение адреса текущего элемента

list=list->next; //Присвоение адреса следующего элемента

}

if (!last) printf("Element ne naiden\n");

break;

case '2':printf("Znachenie dlya poiska = ");

scanf("%d",&zn);

list=last=first; //Указатели last и list указывают на первую переменную

while ((last)&&(last->chislo.znachenie!=zn)) //Цикл для поиска //элемента с заданным ключом

{

last=list; //Сохранение адреса текущего элемента

list=list->next; //Присвоение адреса следующего элемента

}

if (!last) printf("Element ne naiden\n");

else printf("Nomer elementa = %d",(*last).chislo.nomer);

break;

default:printf("Oshibka vibora punkta");

}

getch();

return last; //Функция возвращает указатель на найденный элемент

}

Добавление элемента в список производит фрагмент программы, приведенной ниже.

int zn;

n++;

printf("1 - dobavlenie v nachalo\n2 - dobavlenie posle elementa\n\

3 - dobavlenie v konec\n");

char vibor=getch();

switch (vibor)

{

case '1':list=new element;

(*list).chislo.nomer=n;

printf("\nVvedite znachenie %d chisla: ",n);

scanf("%d",&zn);

(*list).chislo.znachenie=zn;

list->next=first;

first=list;

break;

case '2':last=poisk();

if(last)

{

list=new element;

(*list).chislo.nomer=n;

printf("\nVvedite znachenie %d chisla: ",n);

scanf("%d",&zn);

(*list).chislo.znachenie=zn;

list->next=last->next;

last->next=list;

break;

}

else printf("Dobavlenie proizoidet v konec spiska");

case '3':last=first;

while(last->next) last=last->next;

list=new element;

(*list).chislo.nomer=n;

printf("\nVvedite znachenie %d chisla: ",n);

scanf("%d",&zn);

(*list).chislo.znachenie=zn;

list->next=NULL;

last->next=list;

break;

default:printf("Oshibka vibora punkta");

}

getch();

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

printf("1 - udalenie iz nachala\n2 - udalenie elementa, sleduyutchego\

za zadannim\n3 - udalenie iz konca\n");

char vibor=getch();

switch (vibor)

{

case '1':list=first;

first=first->next;

delete list;

break;

case '2':last=poisk();

if(last)

{

list=last->next;

last->next=list->next;

delete list;

break;

}

else printf("Udalenie proizoidet iz konca spiska");

case '3':list=first;

while(list->next)

{

last=list;

list=list->next;

}

last->next=NULL;

delete list;

break;

default:printf("Oshibka vibora punkta");

}

getch();

Варианты заданий на курсовое проектирование

Номер вари-анта

Структура программы

Интерфейс пользователя

Операции обработки

Предметная область

1

1

1

1-2-3-8

Одежда

2

1

2

1-2-3-9

Автомобили

3

1

3

1-2-3-10

Мотоциклы

4

1

4

1-2-3-11

Города

5

1

1

1-2-4-7

Реки

6

1

2

1-2-4-9

Моря

7

1

3

1-2-4-10

Газеты

8

1

4

1-2-4-12

Книги

9

2

1

1-2-5-7

Журналы

10

2

2

1-2-5-8

Компакт-диски

11

2

3

1-2-5-10

Направления в музыке

12

2

4

1-2-5-11

Список группы

13

2

1

1-2-6-7

Список дисциплин

14

2

2

1-2-6-8

Планеты Солнечной системы

15

2

3

1-2-6-9

Государства

16

1

4

1-2-6-12

Кинофильмы

17

1

1

1-2-7-11

Профессии

18

1

2

1-2-7-12

Авиарейсы

19

1

3

1-2-8-11

Городской транспорт

20

1

4

1-2-8-12

Цветы

21

1

1

1-2-9-12

Овощи

22

1

2

1-2-10-11

Фрукты

23*

2

3

1-2-3-9-12

Животные

24*

2

4

1-2-3-10-11

Рыбы

25*

2

5

1-2-4-9-12

Птицы

26*

2

6

1-2-4-10-11

Мобильные телефоны

27*

2

3

1-2-5-7-12

Бытовая техника

28*

2

4

1-2-5-8-12

Компьютеры

29*

2

5

1-2-6-7-11

Канцтовары

30*

2

6

1-2-6-8-11

Мебель

Примечание. Знаком * отмечены задания повышенной сложности

Структура программы:

1 – программа

2 – основная программа + модули пользователя (для языка Pascal);

основная программа + заголовочные файлы (для языка С++)

Интерфейс пользователя:

1 – горизонтальное меню (выбор по номеру пункта)

2 – вертикальное меню (выбор по номеру пункта)

3 – горизонтальное меню (выбор по функциональной клавише)

4 – вертикальное меню (выбор по функциональной клавише)

5 – горизонтальное меню (выбор по выделенной строке)

6 – вертикальное меню (выбор по выделенной строке)

Операции обработки динамического списка:

1 – создание динамического списка с элементами типа «запись»

(см. вариант предметной области) и заполнение полей данных

элементов динамического списка данными из текстового файла

2 – вывод данных динамического списка в файл в виде таблицы

3 – дополнение в начало списка

4 – дополнение в конец списка

5 – дополнение списка после элемента с заданным номером

6 – дополнение списка после элемента с заданным ключом

7 – удаление элемента, первого в списке

8 – удаление элемента, последнего в списке

9 – удаление элемента с заданным номером

10 – удаление элемента с заданным ключом

11 – обмен местами элементов с заданными номерами

12 – обмен местами элементов с заданными ключами

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]