
Основы программирования. Борисенко
.pdf
252 4 4 Простейшие структуры данных Стек Очередь
необходимости перерисовки области окна, о выборе пункта меню и т.п. Каждая программа имеет очередь запросов. Когда программа по¬ лучает свой квант времени на выполнение, она выбирает очередной запрос из начала очереди и выполняет его. Таким образом, работа оконного приложения состоит, упрощенно говоря, в последователь¬ ном выполнении запросов из ее очереди. Очередь поддерживается операционной системой.
Подход к программированию, состоящий не в прямом вызове про¬ цедур, а в посылке сообщений, которые ставятся в очередь запро¬ сов, имеет много преимуществ и является одной из черт объектноориентированного программирования. Так, например, если оконной программе необходимо завершить работу по какой-либо причине, лучше не вызывать сразу команду завершения, которая опасна, пото¬ му что нарушает логику работы и может привести к потере данных. Вместо этого программа посылает самой себе сообщение о необхо¬ димости завершения работы, которое будет поставлено в очередь за¬ просов и выполнено после запросов, поступивших ранее.
Р е а л и з а ц ия очереди на базе массива
Как у ж е было сказано, программисту массив дан свыше, все остальные структуры данных нужно реализовывать на его основе. Конечно, такая реализация может быть многоэтапной, и не всегда массив выступает в качестве непосредственной базы реализации. В случае очереди наиболее популярны две реализации: непрерывная на базе массива, которую называют также реализацией на базе кольце¬ вого буфера, и ссылочная реализация, или реализация на базе спис¬ ка. Ссылочные реализации будут рассмотрены ниже.
При непрерывной реализации очереди в качестве базы выступает массив фиксированной длины N , таким образом, очередь ограниче на и не может содержать более N элементов. Индексы элементов массива изменяются в пределах от 0 до N — 1. Кроме массива, ре¬ ализация очереди хранит три простые переменные: индекс начала очереди, индекс конца очереди, число элементов очереди. Элементы очереди содержатся в отрезке массива от индекса начала до индекса конца.


4.4.2. Стек |
|
|
|
255 |
A . В процессе |
ее выполнения возникает необходимость |
выполнить |
||
задачу B . Состояние задачи A запоминается, и компьютер |
переходит |
|||
к выполнению |
задачи B . Но ведь и при выполнении |
задачи B |
ком |
|
пьютер может |
переключиться на другую задачу C , |
и нужно |
будет |
сохранить состояние задачи B , прежде чем перейти к C . Позже, по окончании C будет сперва восстановлено состояние задачи B , затем, по окончании B , — состояние задачи A . Таким образом, восстановле¬ ние происходит в порядке, обратном сохранению, что соответствует дисциплине работы стека.
Стек позволяет организовать рекурсию, т.е. обращение подпро¬
граммы к самой себе либо непосредственно, либо |
через |
цепочку |
|
других вызовов. Пусть, например, подпрограмма A |
выполняет ал¬ |
||
горитм, зависящий от входного параметра X и, возможно, |
от состо |
||
яния глобальных данных. Д л я самых простых значений |
X |
алгоритм |
|
реализуется непосредственно. В случае более сложных |
значений X |
алгоритм реализуется как сведение к применению того ж е алгоритма для более простых значений X . При этом подпрограмма A обра¬ щается сама к себе, передавая в качестве параметра более простое
значение X . При таком обращении предыдущее значение параметра |
|
X , |
а также все локальные переменные подпрограммы A сохраняются |
в |
стеке. Д а л е е создается новый набор локальных переменных и пе¬ |
ременная, содержащая новое (более простое) значение параметра X . Вызванная подпрограмма A работает с новым набором переменных, не разрушая предыдущего набора. По окончании вызова старый на¬
бор локальных переменных |
и старое состояние |
входного параметра |
X восстанавливаются из стека, и подпрограмма |
продолжает работу |
|
с того места, где она была |
прервана. |
|
На самом деле даже не приходится специальным образом сохра¬ нять значения локальных переменных подпрограммы в стеке. Дело в том, что локальные переменные подпрограммы (т.е. ее внутренние, рабочие переменные, которые создаются в начале ее выполнения и уничтожаются в конце) размещаются в стеке, реализованном аппаратно на базе обычной оперативной памяти. В самом начале работы подпрограмма захватывает место в стеке под свои локальные пере¬
менные, этот участок памяти |
в аппаратном стеке называют обычно |
блок локальных переменных |
или по-английски frame ("кадр"). В мо¬ |
мент окончания работы подпрограмма освобождает память, удаляя из
256 |
4.4. Простейшие структуры данных. Стек. Очередь |
||
стека блок своих локальных переменных. |
|
|
|
Кроме локальных переменных, в аппаратном |
стеке сохраняются |
||
адреса возврата при вызовах подпрограмм. Пусть |
в некоторой |
точке |
|
программы A вызывается подпрограмма B . Перед вызовом подпро¬ |
|||
граммы B адрес инструкции, следующей за инструкцией вызова B , |
|||
сохраняется |
в стеке. Это так называемый адрес |
возврата |
в про¬ |
грамму A . По окончании работы подпрограмма B извлекает из стека адрес возврата в программу A и возвращает управление по этому адресу. Таким образом, компьютер продолжает выполнение програм¬ мы A , начиная с инструкции, следующей за инструкцией вызова. В большинстве процессоров имеются специальные команды, поддержи¬ вающие вызов подпрограммы с предварительным помещением адреса возврата в стек и возврат из подпрограммы по адресу, извлекаемому из стека. Обычно команда вызова назывется call, команда возврата — return.
В стек помещаются также параметры подпрограммы или функ¬ ции перед ее вызовом. Порядок их помещения в стек зависит от соглашений, принятых в языках высокого уровня. Так, в языке Си
или |
C + + на вершине стека лежит |
первый аргумент функции, под |
ним |
второй и так далее. В Паскале |
все наоборот, на вершине стека |
лежит последний аргумент функции. (Поэтому, кстати, в Си возмож¬ ны функции с переменным числом аргументов, такие, как printf, а в Паскале нет.)
В Фортране-4, одном из самых старых и самых |
удачных язы¬ |
ков программирования, аргументы передаются через |
специальную |
область памяти, которая может располагаться не в стеке, поскольку
до конца |
70-х годов X X века еще существовали компьютеры вроде |
|
I B M 360 |
или ЕС Э В М без |
аппаратной реализации стека. Адреса |
возврата |
также сохранялись |
не в стеке, а в фиксированных дл я каж¬ |
дой подпрограммы ячейках памяти. Программисты называют такую память статической в том смысле, что статические переменные за¬ нимают всегда одно и то ж е место в памяти в любой момент работы программы. При использовании только статической памяти рекур¬ сия невозможна, поскольку при новом вызове предыдущие значения локальных переменных разрушаются. В эталонном Фортране-4 ис¬ пользовались только статические переменные, а рекурсия была за¬ прещена. До сих пор язык Фортран широко используется в научных


4.4.2. Стек |
259 |
// Прототипы функций, реализующих предписания стека:
void s t _ i n i t ( i n t maxSize); // Начать работу (вх: цел
//макс, размер стека)
void |
st_terminate(); |
// Закончить |
работу |
|
void |
st_push(double |
x); // Добавить |
эл-т (вх: вещ x) |
|
double |
st_pop(); |
// Взять элемент: вещ |
||
double |
st_top(); |
// Вершина стека: вещ |
||
int |
s t _ s i z e ( ) ; |
// Текущий размер стека: цел |
||
bool |
st_empty(); |
// Стек пуст? : лог |
||
int |
st_maxSize(); |
// Макс, размер стека: цел |
||
bool |
st_freeSpace(); |
// Есть свободное место? : лог |
||
void |
s t _ c l e a r ( ) ; |
// Удалить все элементы |
double st_elementAt(int i ) ; // Элемент стека на
// глубине (вх: i ) : вещ
#endif
// Конец файла "streal.h"
Отметим, что директивы условной трансляции
#ifndef ST_REAL_H #define ST_REAL_H
#endif
используются дл я предотвращения повторного включения h-файла: при первом включении файла определяется переменная препроцессо¬ ра ST_REAL_H, а директива "#ifndef ST_REAL_H" подключает текст, только если эта переменная не определена. Такой трюк используется практически во всех h-файлах. Нужен он потому, что одни h-файлы могут подключать другие, и без этого механизма избежать повтор¬ ного включения одного и того ж е файла трудно.
Файл "streal.cpp" описывает общие статические переменные, над которыми работают функции, соответствующие предписаниям стека,
иреализует эти функции.
//Файл "streal.cpp"
//Стек вещественных чисел, реализация
260 |
4.4. Простейшие структуры данных. Стек. Очередь |
// |
<stdlib.h> |
#include |
|
#include <assert.h> |
|
#include |
"streal.h" // Подключить описания функций стека |
//Общие переменные для функций, реализующих
//предписания стека:
static |
double |
*elements =0; |
// Указатель на массив эл-тов |
||
static |
i n t max_size =0; |
// |
стека в дин. памяти |
||
// Размер |
массива |
||||
static |
i n t sp |
= (-1); |
// Индекс |
вершины стека |
|
// Предписания |
стека: |
|
|
|
|
void s t _ i n i t ( i n t maxSize) { |
// |
Начать |
работу (вх: |
||
|
|
|
// |
макс. размер стека) |
assert(elements == 0); max_size = maxSize;
elements = (double *) malloc( max_size * sizeof(double)
);
sp = (-1);
} |
|
|
|
|
|
void st_terminate() |
{ |
// |
Закончить |
работу |
|
i f (elements != |
0) |
{ |
|
|
|
free(elements); |
|
|
|
||
} |
|
|
|
|
|
} |
|
|
|
|
|
void st_push(double x) { |
// Добавить эл-т (вх: вещ x) |
||||
assert( |
0 && |
// |
утв: |
начал работу и |
|
elements != |
// |
стек |
|||
sp < max_size-1 |
// |
есть |
своб. место |
||
); |
|
|
|
|
|
++sp; |
x; |
|
|
|
|
elements[sp] = |
|
|
|
|