- •1. Структура (состав) языка
- •2. Алфавит
- •3. Лексическая структура языка
- •4. Структура программной единицы
- •5. Стиль записи программ на языке Паскаль
- •6. Типы данных в Паскале
- •6.3 Классификация типов данных в Турбо Паскале
- •6.4 Порядковые типы
- •6.4.2 Булевский (логический) тип
- •Repeat тело_цикла until (логическое_выражение);
- •6.4.3 Целые типы Выделяют целые типы ------------- со знаком – shortint, integer, longint)
- •1 Группа функций:
- •2 Группа функций:
- •3 Группа функций:
- •6.4.4 Перечисляемый тип.
- •6.4.6 Символьный тип
- •6.5 Вещественные типы.
- •7. Выражения в языке Паскаль.
- •10. Вычисление по формулам.
- •10.1 Оператор присваивания .
- •10.2 Характер использования переменных в математике и в программах.
- •10.3 Бесконечности
- •10.4 Нестандартные операции
- •6.6 Строки
- •1) Операции присваивания и сравнения.
- •3) Заполнение строки одним символом
- •4) Стандартные функции и процедуры для работы со строками:
- •5) Подпрограммы преобразования из строкового представления в числовое и наоборот:
- •8. Совместимость и преобразование типов.
- •Совместимость типов
- •8.2 Тип результата арифметических выражений.
- •8.3 Преобразование (приведение) типов и значений.
- •8.3.1 Явное преобразование (приведение) типов.
- •8.3.2 Неявное преобразование или приведение типов.
- •9. Простейший ввод-вывод на Паскале
- •Стандартные файлы Input и Output
- •9.2 Процедуры ввода информации (с клавиатуры.
- •9.3 Процедуры вывода в тр.
- •10. Вычисление по формулам (продолжение)
- •10.6. Уточнение многоместных (n - арных) операций
- •11. Средства языка Паскаль для циклов с известным числом повторений.
- •12. Табулирование функций
- •13. Разветвляющиеся алгоритмы
- •13.1 Таблица ситуаций и команда выбора.
- •13.2 Средства языка Паскаль для программирования разветствляющихся алгоритмов
- •13.4 Описание ситуаций
- •13.5 Запись команды выбора (case) (уточнение таблицы ситуаций) с помощью набора команд ветвления
- •13.6 Запись последовательных команд ветвления в случае, когда соседние зависимые ситуации имеют общие признаки.
- •13.6.1 Восходящий подход
- •13.6.2 Нисходящий подход
- •14. Циклы с неизвестным числом повторений
- •15. Структурированные типы данных. Массивы
- •15.1 Классификация (особенности) структурированных типов данных
- •15. 2. Определение массива
- •15.3 Объявление массива на Турбо Паскале
- •15.4 Хранение элементов массива. Доступ к элементам и частям массива
- •15.5 Уточнение команд обработки массива
- •16. Правила разработки цикла
- •15. 6 Действия над массивами
- •17. Множества.
- •17.1 Множества в Паскале и в математике. Сходства и различия между ними.
- •17.2 Объявление множества на Паскале
- •17.3 Присваивание значений множествам. Конструктор множества
- •17.4 Операции над множествами.
- •17.5 Сравнение множеств.
- •17.6 Применение множеств.
- •18. Вспомогательные алгоритмы (подпрограммы).
- •18.1 Три способа записи повторяющихся команд
- •18.2 Понятие блока
- •18.3 Объекты подпрограммы (то, над чем выполняются действия).
- •18.4 Свойства локальных и глобальных объектов
- •Свойства глобальных объектов:
- •18.5 Выделение памяти под локальные и глобальные переменные
- •18.6 Передача параметров в подпрограммы.
- •Фактические параметры
- •18.7 Подпрограммы, возвращающие значение (функции)
- •18.8 Особенности использования процедур и функций в Турбо Паскале
- •18.9. Побочный эффект (side effect)
- •18.10 Опережающее определение процедур и функций.
- •18.11 Рекурсия и итерация.
- •18.12 Процедуры и функции как параметры.
- •18.13 Директивы подпрограмм
- •Директива forward
- •Директивы far и near
- •Директива external
- •Директива assembler
- •Директива inline
- •Директива interrupt
- •Отладка и тестирование программ, содержащих подпрограммы
- •18.14.1 Нисходящее тестирование и подпрограммы-заглушки
- •18.14.2 Восходящее тестирование и программы-тестеры
- •18.14.3 Рекомендации по отладке программ, содержащих подпрограммы
- •18.14.4 Использование отладчика для трассировки процедур
- •18.14.5 Область действия идентификаторов и переменные в окне Watch
- •18.15. Получение доступа а параметрам командной строки, запуск внешних программ.
- •19. Записи.
- •19.1 Понятие записи. Объявление записи в программе.
- •19.2 Доступ к полям записи.
- •19.3 Оператор with
- •19.4 Действия над записями
- •19.5 Записи с вариантами
- •Замечание1:Порядок частей – именно такой, как показано: фиксированная часть всегда первая (или единственная)
- •19.6 Типизированные константы - записи
- •20. Модули (Unit)
- •20.1 Что такое модуль?
- •20.2 Зачем нужны модули и какие есть средства, аналогтчные (в какой-то мере) модулям
- •Интерфейсная секция
- •Секция реализации
- •Секция инициализации
- •Подключение других модулей к данному (модулю)
- •20.4 Ссылки на описания модуля
- •Пример создания модуля
- •Использование модулей. Режимы Compile, Build и Make при компиляции модулей
- •Косвенные и перекрестные ссылки на модули
- •Пример модуля (стек)
- •Модули и большие программы
18.11 Рекурсия и итерация.
Рекурсия - это определение понятия самого через себя.
Например, идентификатор ::= <буква>|<идентификатор><буква> |<идентификатор><цифра>
В случае процедур и функций рекурсия используется в виде вызова функции самой себя в собственном теле. Это делается обычно для последовательного понижения размерности (сложности) задачи, пока решение задачи не станет тривиальным (простым). Запись решения задачи в виде последовательности рекурсивных вызовов функции обычно выглядит более понятно (более просто) по сравнении с обычным (нерекурсивным ) (итерационным) решением.
Например, одно и то же вычисление факториала можно сделать несколькими способами:
Нерекурсивное (итерационное) решение:
fact(n)
=1*2*3*4*...*n =
Рекурсивное решение имеет вид:
1, при n
=0
Понижение размерности задачи
fact(n-1)*n при
n >0.
4!=(3!)*4
Примеры итерационных вычислений, например, при вычислении значений многоместных (n-арных) операций уже рассматривались выше (когда роль идет о вычислении n – арных операций)
Вычисление
с помощью итерации: Рекурсивное
определение той же функции:
Предопределенная
константа
Type type
natural=0.. maxint; natural=0.. maxint;
function fact(n: natural): Longint; function fact(n: natural): Longint;
var begin
i: integer; if n = 0 then
f: Longint; fact := 1;
begin else fact := n * fact(n-1); end;
f:= 1; end.
for i:=
1 to n
do вычисление
значения этого
f:= f * i; выражения будет производиться
fact := f; на рекурсивном возврате (после
end; рекурсивного вызова)
переменная где формируется
результат функции
Процесс рекурсивного вычисления значения в данном случае факториала организуется в соответствии с данным определением. Этот процесс сводится к последовательному вызову функции самой себя каждый раз с новым значением аргумента (цепочке рекурсивных вызовов). При этом каждый раз при каждом таком рекурсивном вызове в стеке откладывается копии всех локальных параметров процедуры или функции:
Fact(3) 3*Fact(2) 3*( 2* Fact(1) ) 3*( 2*( 1* Fact(0)))
Нерекурсивный вызов Fact(0)=1 завершает цепочку рекурсивных вызовов.
В общем случае при вызове любой процедуры или функции происходят следующие действия:
1). В стеке сохраняются значения локальных объектов подпрограммы (фактические параметры и локальные для вызываемой подпрограммы переменные);
2). Запоминается адрес возврата в вызывающую подпрограмму (или программу);
3). Управление передается вызванной подпрограмме.
В некоторый момент выполнения программы в стеке может присутствовать несколько (возможно даже одинаковых) групп локальных объектов, соответствующих цепочке вызванных, но не завершенных подпрограмм. В каждый момент времени доступ возможен только к той группе, которая включена в стек последней и находится в голове стека. Эта группа локальных объектов соответствует текущей вызванной подпрограмме. Таким образом, в случае, если в стеке оказывается одновременно несколько одноименных переменных, то доступна их будет лишь та, которая попала в стек последней. При завершении вызванной подпрограммы ее локальные объекты удаляются из вершины стека, и в голове стека оказываются (и становятся доступными) локальные объекты вызывающей подпрограммы.
Рассмотрим
процесс рекурсивного определения более
подробно на примере вызова y:=fact(3).
Fact(3)
Первичный вызов n
3
Fact ? 6
Fact(2) n
2 fact(2)*3
спуск рекурсивные
вызовы Fact
? 2
возврат
Fact(1) n 1 fact(1)*2
Fact ? 1
Fact(0) n 0 fact(0)*1
содержимое
стека
Fact 1
имя
значения имя при рекурсивных вызовах
вычисляемые значения при рекурсивных возвратах
Такой процесс последовательного вызова функции самой себя естественно с разными значениями аргумента называется рекурсивным спуском. В процессе рекурсивного спуска при каждом рекурсивном вызове в стеке выделяется память под копию каждого локального параметра. Процесс рекурсивного спуска будет продолжаться до тех пор, пока выражение в правой части оператора присваивания для вычисления возвращаемого значения не примет вполне определенный вид (в этом случае функция сама себя не вызывает). В данном случае конец наступит, когда при очередном рекурсивном вызове аргумент будет равен 0, после этого процесс начнет раскручиваться в обратную сторону - будет происходить рекурсивный возврат. Этот процесс сопровождается освобождением памяти(в стеке), занимаемой очередной копией локальных объектов процедур или функций.
Действия в теле рекурсивных процедур и функций могут выполняться либо на рекурсивном спуске (до рекурсивного вызова) , либо на рекурсивном возврате (после рекурсивного вызова). В данном случае действие (вычисление факториала) происходит в процессе рекурсивного возврата и имеет следующий вид:
то, что
получено после рекурсивного вызова
fact(n) = fact(n-1) * n.
Количество вызовов функцией самой себя при рекурсивном спуске называется глубиной рекурсии.
Необходимыми условиями для успешного завершения рекурсии процедур или функций являются следующие:
1). необходимо точно определить ситуацию, при которой должен завершиться рекурсивный спуск. В данном случае такая ситуация имеет вид: n =0 - один признак у этой ситуации Но может быть и более сложный случай.
2) необходимо предусмотреть в теле рекурсивной подпрограммы тех действий, которые должны приближать наступление указанной выше ситуации.
Каждую рекурсию можно заменить итерацией, применив цикл. Рекурсивное определение алгоритма обычно более просто и компактно. Можно считать, что при рекурсивном определении говорится, что нужно сделать, но не говорится как. При итерационном наоборот: строго формулируется, что и как нужно сделать.
У рекурсивной процедуры или функции имеются следующие недостатки: на выполнение рекурсии процедуры или функции требуется большое количество машинного времени и памяти.
Времени требуется больше потому, что многократно вызываются процедуры и функции, т.е. тратиться время на то, чтобы выйти и войти в нее. Память тратится больше из-за того что при каждом рекурсивном вызове резервируется память в стеке для каждого локального объекта процедуры или функции. Для контроля переполнения стека используется директива {$S+}. Для сокращения памяти в стеке (при этом увеличатся затраты памяти в сегменте данных) при рекурсивном вызове (при предполагаемом большом числе рекурсивных вызовов), целесообразно использовать статические локальные переменные - на Паскале это типизированные константы (память под них выделяется не в стеке, а сегменте данных).
Не каждый алгоритм может быть успешно реализован с помощью рекурсии. Успешно алгоритм может быть реализован, если удалось определить ситуацию, при наступлении которой завершится процесс рекурсивного спуска.
