Структуры управления
7.3. Основы структур управления
Спецификация алгоритма должна включать элементы двух типов:
- элементарные шаги выполнения (требование А2 определения алгоритма);
- порядок их выполнения (требование А3).
Управляющие структуры отвечают второй из этих потребностей. Более точно:
Определения: Поток управления, Управляющая структура
Структура управления или управляющая структура - это программная конструкция, воздействующая на поток управления.
Как уже отмечалось, существуют три фундаментальные формы структур управления.
- Последовательность, которая состоит из операторов, перечисленных в определенном порядке. Ее выполнение состоит в выполнении каждого из этих операторов в том же порядке.
- Цикл, который содержит последовательность операторов, выполняемую многократно.
- Выбор, состоящий из условия и двух последовательностей операторов. Выполнение состоит из выполнения либо одной, либо другой последовательности, в зависимости от того, принимает ли условие — булевское выражение — значение True или False. Структура может быть обобщена на случай выбора из нескольких возможностей.
Механизмы работы операторов программы используют три фундаментальные возможности компьютеров (последовательность, цикл, выбор).
- Выполнение всего множества предписанных действий в заданном порядке.
- Выполнение единственного предписанного действия, или как вариант — многократное выполнение этого действия.
- Выполнение одного из множества предписанных действий в зависимости от заданного условия.
Такие структуры управления предполагают, что программа в каждый момент времени выполняет только одно действие. Для компьютеров с несколькими процессорами или для компьютера, выполняющего несколько программ одновременно, можно говорить о параллельном выполнении, приводящем к новым структурам управления, которые здесь мы изучать не будем.
Наши базисные структуры можно комбинировать без всяких ограничений, так что можно задать структуру выбора, каждая ветвь которой включает цикл или структуру выбора, а они, в свою очередь, включают другие структуры. Процесс вычислений состоит из операторов, которые сгруппированы в структуры управления, описывающие план работы периода выполнения, — описание этого процесса и составляет алгоритм.
Эти понятия являются предметом следующих разделов. В дополнение рассмотрим еще две формы управляющих структур.
- Оператор перехода, также известный как оператор goto, потерявший благосклонность у программистов — мы увидим почему, — но все еще играющий роль в системе команд компьютера.
- Обработка исключений, обеспечивающая способы восстановления после возникновения событий, прерывающих нормальный поток управления, таких как например, void-вызовы.
После того, как алгоритм определен, возникает естественное желание превратить его в программу, дать ей имя и использовать ее, зная это имя. Такое группирование известно как создание подпрограммы — фундаментальной формы структурирования, достижимой с позиций управления. Подпрограммы в результате позволяют нам создавать новые структуры управления, создавая как новую абстракцию некоторую комбинацию существующих структур. Они являются предметом следующей лекции.
7.4. Последовательность (составной оператор)
Структура "последовательность", применимая к образцам решения задач, известна каждому: задав одну или несколько промежуточных целей, можно постепенно, шаг за шагом двигаться по направлению к цели. Если ставится только одна промежуточная цель, то мы решаем две раздельные задачи:
- достичь промежуточной цели, начиная с исходных предположений;
- достичь финальной цели, начиная с промежуточного рубежа.
Для более общего случая, с n промежуточными целями, будем выполнять n + 1 шагов, где на шаге i (для 2 < i < n) достигается i-я промежуточная цель, полагая, что предыдущие цели достигнуты.
Примеры
В рассматриваемой нами проблемной области путешествия по городу типичным примером последовательности является возможная стратегия перемещения из точки a в точку b.
- Найти на карте станцию метро ma, ближайшую к a.
- Найти на карте станцию метро mb, ближайшую к b.
- Пройти от a к ma.
- Проехать на метро от ma до mb.
- Пройти от mb к b.
Эта стратегия предназначена для человека, не для программы. Программа также может построить маршрут, используя введенные понятия. Маршрут (route), как вы помните, состоит из этапов (leg). Зададим, соответственно, следующие объявления:
full: ROUTE walking_1, walking_2, metro_1: LEG
Маршрут можно построить, используя следующую последовательность операторов:
— Версия 1 create walking_1.make_walk (a, ma) create walking_2.make_walk (mb, b) create metro_1.make_metro (ma, mb) create full.make_empty full.extend (walking_1) full.extend (metro_1) full.extend (walking_2)
Здесь мы в полной мере используем те преимущества, которые дает нам возможность иметь различные процедуры создания в классе LEG:
- make_walk, создает пеший этап от одного места к другому;
- make_metro, создает этап поездки на метро от одной станции до другой (с предусловием, требующим существования линии, которая проходит через обе станции, так как один этап метро должен быть полностью на одной линии).
Мы используем также процедуру создания и метод класса ROUTE:
- процедуру создания make_empty, строящую пустой маршрут;
- команду extend, добавляющую этап в конец маршрута.
Время программирования
Создание и анимация маршрута
Используя вышеприведенную схему, напишите и выполните программу, создающую маршрут от Елисейского дворца (Elysee_palace) до Эйфелевой башни (Eiffel_tower). Имена обоих мест определены как компоненты класса TOURISM. Анимируйте полученный маршрут.
Вставьте соответствующие программные элементы и последующие в этой лекции в новый класс, названный ROUTES. Имя системы для примеров и экспериментов с ней - control.
Начиная с этого и для всех последующих упражнений, больше не будут даваться пошаговые инструкции, как писать, компилировать и выполнять примеры, если только не понадобятся новые механизмы EiffelStudio, о которых ранее не шла речь. При возникновении вопросов обращайтесь к приложению.
Составной оператор: синтаксис
Управляющая структура "последовательность" не является новинкой этой лекции: мы уже встречались с ней многократно — фактически, начиная с самого первого примера, — только не используя это имя. Мы просто писали несколько операторов в порядке предполагаемого выполнения, как в примере:
Paris.display Louvre.spotlight Metro.highlight Route1.animate
Так как часто полезно рассматривать последовательность операторов как один оператор, например, для того чтобы сделать ее частью другой управляющей структуры, — последовательность часто называют составным оператором.
Правило синтаксиса очень простое.
Синтаксис
Для задания последовательности - составного оператора, содержащего ноль или более операторов, - пишите их последовательно друг за другом, в желаемом порядке выполнения, разделяя при необходимости символами "точка с запятой".
До сих пор мы не пользовались символами "точка с запятой". Правила стиля говорят, что фактически о них особенно беспокоиться не нужно.
Почувствуй стиль
- Опускайте точки с запятой, если (так следует поступать в большинстве случаев) каждый оператор появляется на отдельных строчках.
- В редких случаях появления нескольких операторов на одной строке (операторы короткие, и у вас есть причины экономии числа строк) всегда разделяйте операторы символом "точка с запятой".
Так что, если вам нужно напечатать выше приведенную "Версию1" и у вас туго с бумагой (или ваш босс — рьяный защитник окружающей среды), то можно написать последние три оператора так:
full.extend (walking_1); full.extend (metro_1); full.extend (walking_2)
Чтобы так поступать, должны быть веские доводы. На самом деле, обычно один оператор располагается на строке, так что можно забыть о точках с запятой.
Важно помнить, что разделение на строчки не несет никакой семантики; конец строки — это просто символ, с тем же эффектом, как пробел или табуляция. Так что ничто не мешает вам написать и так:
full.extend (walking_1) full.extend (metro_1) full.extend (walking_2)
Безобразно!
Ничто, за исключением хорошего вкуса, элементарного здравого смысла и официальных правил стиля, принятых в организации. Не стоит сбрасывать со счетов и тех, кто будет читать ваш программный текст позже, в особенности двух читателей: преподавателя (если программа пишется в рамках учебного курса) и вас самих после нескольких дней, недель или месяцев.
Даже записывая операторы на отдельных строчках, некоторые нервничают из-за отсутствия разделителей, возможно, по той причине, что многие языки программирования строго требуют их присутствия в одних места, и запрещают их появление — в других. Проведите простой тест: напишите две одинаковые программы по одному оператору на строке, но в первой завершайте каждую строку точкой с запятой, а во второй не используйте эти символы. Поставьте эти программы рядом и сравните — вы убедитесь, что текст второй программы выглядит чище и лучше читается.
Если вы используете точки с запятой и ошибочно поставите лишнюю, то вреда это не причинит, поскольку оператор_1;; оператор_2 формально воспринимается как три оператора, в котором второй является пустым оператором, имеющим семантику "ничего не делать", так что такая конструкция не создаст трудностей. Конечно, лучше чистить текст и удалять все ненужные элементы.
Составной оператор: семантика
Поведение в период выполнения следует из самого смысла имени конструкции.
Семантика
Выполнение последовательности операторов состоит из выполнения каждого оператора в заданном порядке.
Заметьте, что синтаксис говорит, что последовательность может быть пустой без операторов — тогда она эквивалентна пустому оператору и ничего не делает. Это не особенно впечатляет, но иногда бывает полезным при построении сложных структур управления.
Избыточная спецификация
Возможно, вы заметили, что в вышеприведенном примере ("Версия 1") выбранный порядок является одним из возможных. Например, можно добавлять в маршрут каждый этап непосредственно после его создания:
—Версия 2 - Создать маршрут: create full .make_empty - Создать и добавить первый этап: create walking_1 .make_walk ( a, ma) full .extend ( walking_1) - Создать и добавить второй этап: create metro_1 .make_metro ( ma, mb) full .extend (metro_1) - Создать и добавить третий этап: create walking_2 .make_walk ( mb, b) full .extend ( walking_2)
Возможны и другие порядки. Единственное ограничение: любой оператор, использующий объект, должен появляться после создания этого объекта; и мы добавляем этапы в правильном порядке.
Использование "последовательности" часто задает излишнюю спецификацию, поскольку приводимое решение не является наиболее общим. Это не причиняет вреда системе, но следует сознавать, что решение является одним из множества возможных.
Когда скорость выполнения является значимой, иногда можно ускорить работу, выполняя группу операторов параллельно. Четыре начальных оператора создания могут выполняться параллельно с тем же эффектом. Параллельность, однако, дело тонкое, программисты обычно в таких простых случаях распараллеливанием не занимаются, но хорошие компиляторы могут создавать параллельный код, если аппаратура поддерживает такую возможность, что теперь становится нормой для современных многоядерных процессоров.
Составной оператор: корректность
Мы знаем, что метод может иметь контракт, включающий предусловие и постусловие. Эти свойства управляют вызовом метода, такого как, например, full.extend (walking!). Предусловие говорит клиенту (вызывающему метод), что он должен гарантировать, чтобы его корректно обслужили. Постусловие говорит клиенту, на что он может полагаться по завершении корректного вызова.
Аналогично каждая управляющая структура, рассматриваемая в этой лекции, обладает связанным с ней правилом корректности, которое, используя контракты (предусловия и постусловия операторов, входящих в структуру), определяет результирующий контракт для структуры в целом. Для составного оператора правило корректности отражает свойство поочередного выполнения составляющих операторов.
Корректность
Для корректности составного оператора:
- необходимо выполнение предусловия первого оператора, если таковое задано, до начала выполнения составного оператора;
- из истинности постусловия каждого оператора должна следовать истинность предусловия следующего оператора, если таковое имеется;
- из истинности постусловия последнего оператора должна следовать истинность желаемого постусловия составного оператора в целом.
Специальный случай: пустой составной оператор всегда корректен, но не добавляет никаких новых постусловий.
В нашем примере можно проверить контракт метода extend класса ROUTE, получив его из EiffelStudio. С некоторыми опущенными постусловиями он выглядит так:
extend ( l: LEG) require leg_exists: l /= Void ensure lengths_added: count = old count + 1
Каждая процедура создания в форме create x или create x.make (...) гарантирует истинность условия x /= Void после ее завершения. Таким образом, предусловие корректности для составного оператора выполняется как в "Версии 1", так и в "Версии 2", но может не иметь места, если изменить порядок выполнения операторов:
— Версия 3 - Создать маршрут: create full.make_empty - Создать и добавить первый этап: full.extend ( walking_1) create walking_1.make_walk (a, ma)
В том месте, где используется walking_1, соответствующий LEG объект еще не существует, так что попытка добавить его к маршруту будет ошибочной.