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

g30zAZLYUG

.pdf
Скачиваний:
3
Добавлен:
15.04.2023
Размер:
1.89 Mб
Скачать

2.8. Измените текст процедуры печати непустого стека на следую-

щий:

void Print_LIFO()

{

cout << "*****Print_LIFO*****\n";

p1=ptop; // Начинаем с верхушки стека

while (p1 != NULL) // Делаем, пока не дошли до самого низа

{

// Печатаем поле данных элемента, расположенного по адресу p1 cout << p1->data << "\n";

// Сдвигаемся к следующему элементу p1 = p1->ptr;

}

};

Хотя текст процедуры снабжён подробными комментариями, поясню суть её работы на простом примере. Предположим, что у Вас имеется стек, в который помещены три элемента. Назовём их условно: верхний, средний и нижний. Структура такого стека показана на рис. 2.3.

31

Рис. 2.3.

Структура стека

В результате выполнения оператора p1 = ptop;

в указатель p1 засылается адрес верхнего элемента стека. Начинает работать цикл. Оператор cout печатает содержимое поля данных верхнего элемента, после че-

го в результате выполнения оператора p1 = p1->ptr;

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

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

p1 = p1->ptr;

впеременную p1 попадает то, что хранится у среднего элемента в поле указателя, т.е. адрес нижнего элемента. Тем самым, мы перешли к нижнему элементу.

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

p1 = p1->ptr;

впеременную p1 попадает то, что хранится у нижнего элемента в поле указателя, т.е. нулевой указатель NULL. Условие продолжения цикла нарушено, и цикл завершает свою работу.

2.9. Приступим к разработке процедуры Add_LIFO, добавляющей элемент в стек.

void Add_LIFO(int number)

{

cout << "***** Add_LIFO*****\n";

//Выделяем место в памяти под новую переменную типа NODE;

//адрес выделенного места помещаем в переменную p1

p1=new NODE;

//По адресу p1 в поле данных помещаем введённое число p1->data=number;

//По адресу p1 в поле указателя помещаем адрес прежней верхушки p1->ptr=ptop;

//Переопределяем верхушку, т.к. верхним стал вновь введённый элемент ptop=p1;

// Выводим стек на экран

Print_LIFO();

};

32

Как видите, в этой процедуре выполняются следующие действия:

c помощью оператора new выделяется место в памяти под новую переменную, имеющую тип NODE. Адрес выделенного места помещается в указатель p1;

в поле данных нового элемента помещается значение переменной number;

в поле указателя нового элемента заносится адрес того элемента, который «под ним», т.е. адрес прежней верхушки;

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

стек выводится на печать.

Запустите программу на выполнение. Используя пользовательское меню, добавьте в стек несколько новых элементов. Обратите внимание на тот факт, что элемент, попавший в стек последним, выводится на печать первым.

2.10. Наконец, процедура Del_LIFO, удаляющая верхушку стека. Если стек не пуст (есть что удалять), то переопределяем верхушку стека (адрес новой верхушки – это то, что хранится у старой верхушки в поле указателя), после чего очищаем место в памяти, занимаемое прежней верхушкой.

void Del_LIFO()

{// Адрес удаляемого верхнего элемента

//копируем во вспомогательную переменную p1 p1 = ptop;

//Переопределяем верхний элемент

if (ptop != NULL) ptop = ptop->ptr;

//Очищаем место, занимаемое удалённым элементом free(p1);

//Выводим стек на экран

Print_LIFO();

};

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

ЗАДАНИЕ № 1. Хранение набора записей в виде стека

Модифицируйте созданную программу так, чтобы в стеке хранились не целые числа, а набор данных из Вашего варианта (см. задание № 1 лабораторной работы № 1).

33

В структуре LIFO у Вас будет не два, а пять полей: четыре поля из Вашего набора данных плюс поле указателя.

Предусмотрите ввод элементов стека не с консоли, а из входного текстового файла (того самого, который Вы подготовили для выполнения задания № 1 лабораторной работы № 1).

Подсказка. В процедуре main выше метки label_1 организуйте выполнение следующих действий:

объявите буферную переменную типа NODE. Например, так:

NODE buff;

объявите файловую переменную f и откройте файл для чтения;

в цикле считывайте из файла очередную запись и помещайте её в переменную buff, после чего вызывайте процедуру Add_LIFO, передавая в неё не целочисленное значение, а запись buff. Например, так:

while (!f.eof())

// пока не будет достигнут конец файла

{

 

//Читаем строку из файла в переменную buff f >> buff.surname >> buff.name >> buff.ball;

//Передаём запись в процедуру добавления элемента

Add_LIFO(buff);

};

в самой процедуре Add_LIFO заполняйте поля новой записи, считывая их с переданной переменной:

void Add_LIFO(NODE b)

{

cout << "***** Add_LIFO*****\n";

//Выделяем место в памяти под новую переменную типа NODE p1=new NODE;

//Оценка

p1->ball=b.ball; // Фамилия

strcpy(p1->surname, b.surname); // Имя

strcpy(p1->name, b.name);

//По адресу p1 в поле указателя помещаем адрес прежней верхушки p1->ptr=ptop;

//Переопределяем верхушку, т.к. верхним стал вновь введённый элемент ptop=p1;

};

34

после окончания цикла в процедуре main распечатайте содержимое стека (с помощью модифицированной под Ваши данные процедуры

PRINT_LIFO).

Фрагмент добавления элементов посредством пользовательского меню можете закомментировать.

Работающую версию программы, а также программный код, предъявите преподавателю.

п. 3. STL контейнер stack (стек)

Способ организации стека, представленный в предыдущем пункте, универсален в том смысле, что аналогичным образом можно организовать стек и в другом языке программирования (например, Pascal), и в другой среде программирования (например, Builder, Delphi).

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

Однако Microsoft Visual Studio C++ предлагает разработчикам ПО ещё один способ организации стека – с помощью STL контейнера stack. Вся информация об этой структуре хранится в библиотеке

<stack>.

Чтобы создать новый стек, его надо объявить в коде программы следующим образом:

stack <type> name;

Здесь type – тип элементов, помещаемых в стек, а name – имя стека.

Операции, определённые над стеком, реализуются с помощью следующих функций:

push() – добавить элемент;

pop() – удалить верхний элемент;

top() – получить верхний элемент;

size() – размер стека;

empty() – возвращает булево значение true, если стек пуст. Рассмотрим пример работы с контейнером stack.

3.1.Создайте новый проект (см. пункты 1.1 – 1.7 этой лабораторной работы; разумеется, надо задать другое имя проекта, например, lab_02_2).

3.2.В раздел подключений библиотек добавьте ещё один include:

#include <stack>

35

3.3. В начало процедуры main добавьте объявление стека. Дадим ему имя proba. И пусть в нем хранятся целые числа:

stack <int> proba;

3.4. С помощью функции push положите в стек несколько целых чи-

сел:

proba.push(20); proba.push(30); proba.push(40); proba.push(50);

3.5. Распечатываем содержимое стека до тех пор, пока он не опусте-

ет:

while (!(proba.empty()))

// Пока стек не пуст

{

 

 

cout<<proba.top() << "\n";

// Печатаем верхушку

proba.pop();

// Удаляем верхушку

};

 

 

 

 

 

Запустите программу на выполнение. Обратите внимание на тот факт, что элемент, попавший в стек последним, выводится на печать первым.

п. 4. Очередь. Реализация очереди с помощью указателей

4.1.Закомментируйте или удалите последние добавленные строки, вернувшись к состоянию, описанному в пункте 1.6.

4.2.Как и в случае со стеком, каждый элемент очереди представляет собой запись, как минимум, с двумя полями, а именно:

поле (хотя бы одно) той смысловой информации, ради которой, собственно, мы и организуем очередь и

поле указателя.

Для простоты будем считать, что полей ровно два, причём первое

поле (поле смысловой информации) целочисленное.

В области объявления глобальных типов и переменных (после строки “using namespace std;”) вставьте объявление нового типа данных:

typedef struct FIFO

{

int data; // поле целочисленных данных struct FIFO *ptr; // указатель на запись типа NODE

}NODE;

4.3.Сразу под объявлением нового типа NODE задайте объявление трёх указателей на запись этого типа:

36

NODE *phead, *ptail, *p1;

В указателе phead будет храниться адрес головного элемента, в указателе ptail – адрес хвостового элемента, а указатель p1 понадобится нам в качестве вспомогательной переменной. Мы объявили их в области объявления глобальных переменных для того, чтобы все процедуры имели к ним доступ.

4.4. Чтобы создать пустую, не содержащую ни одного элемента, очередь, надо обнулить указатели на хвостовой и головной элементы. Для этого в начало процедуры main добавьте оператор:

phead=ptail=NULL; // Создание пустой очереди

4.5. Внесём информацию о добавляемом в стек числе. В начале процедуры main объявите целочисленную переменную k:

int k;

4.6.Аналогично пункту 2.6. вставьте три так называемые заглушки для будущих процедур:

Print_FIFO – вывода содержимого очереди на экран,

Add_FIFO – добавления элемента в очередь,

Del_FIFO – удаления элемента из очереди.

4.7.Аналогично пункту 2.7 организуйте пользовательское меню, заменив везде слово «стек» на слово «очередь», а сочетание «LIFO» на

«FIFO».

4.8.Измените текст процедуры печати непустой очереди на следую-

щий:

void Print_FIFO()

{

cout << "*****Print_FIFO*****\n";

p1 = phead; // Встаём на начало очереди

while (p1 != NULL) // Пока не дошли до конца очереди

{

//Печатаем поле данных элемента, расположенного по адресу p1 cout << p1->data << "\n";

//Сдвигаемся к следующему элементу

p1 = p1->ptr;

}

};

37

Поясню суть работы процедуры на простом примере. Предположим, что у Вас имеется очередь, в которую помещены три элемента. Назовём их условно: головной, средний и хвостовой. Структура такой очереди показана на рисунке 2.4.

Рис. 2.4. Структура очереди

В результате выполнения оператора p1 = phead;

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

Начинает работать цикл. Оператор cout печатает содержимое поля данных головного элемента, после чего в результате выполнения оператора

p1 = p1->ptr;

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

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

p1 = p1->ptr;

в переменную p1 попадает то, что хранится у среднего элемента в поле указателя, т.е. адрес хвостового элемента. Тем самым, мы перешли к хвостовому элементу.

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

p1 = p1->ptr;

в переменную p1 попадает то, что хранится у хвостового элемента в поле указателя, т.е. нулевой указатель NULL. Условие продолжения цикла нарушено, и цикл завершает свою работу.

38

4.8. Процедура Add_FIFO, добавляющая элемент в очередь.

void Add_FIFO(int number)

{

//cout << "***** Add_FIFO*****\n";

//Выделяем место в памяти под новую переменную типа NODE p1 = new NODE;

//По адресу p1 в поле данных помещаем введённое число k

p1->data = number;

//По адресу p1 в поле указателя помещаем ноль p1->ptr = NULL;

//Если вставка производится в пустую очередь,

//то новый элемент - головной,

//переопределяем указатель на голову очереди

//Иначе в поле указателя прежнего хвостового элемента

//помещаем адрес "новичка"

if (phead == NULL) {phead = p1;}

else

{ptail->ptr = p1;};

//Переопределяем хвостовой элемент,

//т.к. теперь последним стал вновь введённый элемент ptail = p1;

Print_FIFO();

// Печатаем очередь

};

Текст процедуры снабжён подробными комментариями; поэтому в пояснениях остановлюсь только на принципиальном отличии этой процедуры от процедуры добавления элемента в стек.

Здесь отдельно надо обрабатывать ситуацию, когда вставка производится в пустую, не содержащую ни одного элемента, очередь. Если очередь пустая, то (см. пункт 4.6)

phead=ptail=NULL;

В этом случае обращение к ячейке памяти ptail->ptr

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

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

phead = p1;

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

ptail->ptr = p1;

39

Группа if-else заканчивается, после чего в обоих случаях переопределяем хвостовой элемент:

ptail = p1;

Запустите программу на выполнение. Используя пользовательское меню, добавьте в очередь несколько новых элементов. Обратите внимание на тот факт, что элементы выводятся на печать в том же порядке, что и вводятся.

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

void Del_FIFO()

{

// Адрес удаляемого головного элемента копируем

//во вспомогательную переменную p1 p1 = phead;

//Если очередь не пуста, переопределяем головной элемент if (phead != NULL) phead = phead->ptr;

//Очищаем место, занимаемое удалённым элементом free(p1);

//Выводим очередь на экран

Print_FIFO();

};

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

ЗАДАНИЕ № 2. Хранение набора записей в виде очереди

Модифицируйте созданную программу так, чтобы в очереди хранились не целые числа, а набор данных из Вашего варианта (см. задание № 1 лабораторной работы № 1).

Предусмотрите ввод элементов очереди не с консоли, а из входного текстового файла – так же, как и в задании № 1 этой лабораторной работы.

п. 5. STL контейнер queue (очередь)

Microsoft Visual Studio C++ предлагает разработчикам ПО ещё один способ организацииочереди – с помощью STL контейнера queue. Вся информация об этой структуре хранится в библиотеке <queue>.

Чтобы создать новую очередь, её надо объявить в коде программы следующим образом:

queue <type> name;

40

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