Тверской государственный университет
Опубликован: 03.10.2011 | Доступ: свободный | Студентов: 3244 / 55 | Оценка: 4.33 / 3.83 | Длительность: 19:48:00
ISBN: 978-5-9963-0573-5
Лекция 8:

Структуры управления

Множественный выбор

Условный оператор, как мы видели, решает проблему разделения области задачи на непересекающиеся подмножества, в каждом из которых решение ищется независимо. Базисная форма if ... then ... else использует два подмножества. Включение elseif позволяет разбивать область на произвольное число частей.

Выбор из списка

Рис. 7.19. Выбор из списка

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

Предположим, что в приложении введена целочисленная переменная choice, принимающая значение от 1 до 4, в зависимости от выбора пользователя:

if choice = 1 then
  ... Выбрать английский в качестве языка интерфейса ...
elseif choice = 2 then
  ...
elseif choice = 4 then
  ... Выбрать русский в качестве языка интерфейса ...
else
  ... Этот вариант не должен встречаться (смотри ниже) ...
end

В этом случае множественный выбор доступен в более компактной форме:

inspect
  choice
when 1 then
  ...  Выбрать английский в качестве языка интерфейса ...
when 2 then
  ...
when 4 then
  ...  Выбрать русский в качестве языка интерфейса ...
else
  ...
end

Сделанные упрощения являются довольно скромными, но они отражают общую идею: если все условия выбора сводятся к форме choice = val_i для одного и того же выражения choice и различных констант val_i,, то нет необходимости в повторении "choice =". Вместо этого можно просто перечислить значения констант в последовательно идущих предложениях when.

Это нотация, используемая в Eiffel. В языках Pascal и Ada есть подобная конструкция с ключевыми словами case ... of. В языке С и его последователях (C++, Java, C#) есть оператор switch, не в точности соответствующий нашей нотации, но позволяющий получить ее эквивалент.

Множественный выбор доступен только для выражений определенного типа. В Eiffel его можно использовать только для целых и символов. В обоих случаях:

  • применяется простая нотация для выбора значений, такая как 1 для целых и _A_ для символов;
  • значения упорядочены. Как следствие, можно создавать интервалы: целых, такие как 1 ... 10, символов, такие как 'A' ...'Z'.

В предложениях when можно использовать интервальную нотацию:

 inspect
  last_character -- Символ введенный пользователем
when 'a' .. 'z' then
  ... Операции для случая ввода буквы латиницы в нижнем регистре ... 
when 'A' .. 'Z' then
  ... Операции для случая ввода буквы латиницы в верхнем регистре ... 
when '0' .. '9' then
  ...  Операции для случая ввода цифры ...
else
  ...  Операции для случая, когда ввод не является буквой или цифрой ...
end

В предложениях when можно также перечислять несколько значений или несколько интервалов или их смесь, используя запятые в качестве разделителей:

inspect 
  код заказа — при заказе авиабилетов 
when 'A', 'F', 'P', 'R' then
  ...  Операции при покупке билетов первого класса ...
when 'C' .. 'D', 'I .. 'J', then
  ...  Операции при покупке билетов бизнес класса ...
when 'B', 'H', 'K .. 'N', 'Q', 'S .. 'W', 'Y' then
  ... Операции при покупке билетов эконом класса ...
else
  ... Обработка случая для нестандартного кода заказа ...
end

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

В условном операторе if cl then ... elseif c2 then ... elseif c3 then ... end вполне возможно одновременное выполнение нескольких условий cl, c2, c3, .... Явно заданный последовательный характер проверки определяет, что будет выполняться первая ветвь, для которой выполняется условие. Множественный выбор с when требует, чтобы только одно условие было истинным.

Различные варианты условного оператора все же по умолчанию носят исключающий характер. Как определяет его семантика, i-я ветвь соответствует случаю, когда ее условие выполняется и ни одно из условий предыдущих ветвей не выполнилось. Динамически это ведет к непересекающимся случаям. Во множественном выборе отсутствие пересечений задано при проектировании.

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

Предположим, необходимо реализовать множественный выбор для случая, когда выбор определяется значениями целых чисел, как в нашем первом примере с выбором языка интерфейса; для общности положим, что выбор определяется значениями n чисел. В условном операторе пришлось бы последовательно проверять, равна ли переменная choice 1, если нет, то равна ли она 2, и так далее, выполняя n сравнений в худшем случае.

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

Использование таблицы переходов для организации множественного выбора

Рис. 7.20. Использование таблицы переходов для организации множественного выбора

Таблица переходов соответствует массиву — структуре данных высокого уровня, с которой мы вскоре познакомимся. Каждый из ее элементов содержит адрес кода, выполняемого для соответствующей ветви inspect. На рисунке это показано в виде стрелки, указывающей на точку хранения кода. Двойник "стрелки" есть в языках высокого уровня: понятие ссылки или указателя. Трансляция множественного выбора в машинный код проста: выражение choice используется для нахождения нужного элемента в таблице переходов, этот элемент содержит адрес, выполняется код, расположенный по этому адресу.

Важное свойство такой схемы — она не требует последовательных проверок, только доступ к массиву по индексу и переход по полученному адресу. Обе операции выполняются за константное время вне зависимости от числа ветвей.

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

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

Почувствуй историю
Когда методы реализации влияют на проектирование языка

Исторически операторы множественного выбора появились в языках программирования как следствие таблицы переходов, применяемой в реализации. Первый аналог появился еще в языке Фортран в виде оператора, получившего название "Вычисляемый Goto" в форме GOTO (L1, Ln), CHOICE. Если целочисленное выражение CHOICE имело значение i, то происходил переход по метке Li. Оператор switch языка C и его последователей близок по смыслу. Такие конструкции непосредственно отражают технику таблицы переходов.

Тони Хоар (C.A.R. Hoare) предложил свободную от goto конструкцию case, включенную Виртом в спроектированные им языки Algol W и Pascal. Она и является источником современных форм множественного выбора.

Хоар (2007)

Рис. 7.21. Хоар (2007)

При проектировании множественного выбора необходимо решить, что делать в случае, когда ни один из указанных вариантов не имеет места. Оператор inspect может включать предложение, обрабатывающее этот случай. Как и для случая условного оператора, это предложение не является обязательным и может быть опущено. В этом случае оба оператора выполняются по-разному.

  • В отсутствие предложения else, когда ни одно из условий не выполняется, условный оператор по своему действию эквивалентен пустому оператору.
  • Для inspect такая ситуация приведет к ошибке периода выполнения и возникновению исключительной ситуации.

Эта политика вначале может вызывать удивление. Причина в том, что множественный выбор явно перечисляет множество возможных вариантов и действия, предпринимаемые для каждого из них. Если возможны и другие значения, то для их обработки и следует указать предложение. Если такое предложение не включается, то это предполагает, что вы ожидаете появление только значений указанного множества. В случае с выбором языка значением может быть только от 1 до 4. Если эти ожидания нарушены, то ничего не делать — это неправильная идея, так как, вероятнее всего, она приведет к некорректным вычислениям и другим проблемам, ведущим к краху. Гораздо лучше захватить источник проблем в начале его появления и включить исключение.

Это обоснование неприменимо к условному оператору, который последовательно проверяет условие за условием, выполняя некоторые операции, когда условие становится истинным.

7.10. Введение в обработку исключений

"Все счастливые семьи похожи друг на друга, каждая несчастливая семья несчастлива по-своему", — все знают эти первые строчки романа "Анна Каренина". Счастливые выполнения программ все используют одни и те же структуры управления; несчастливые — несчастны по многим различным причинам, называемым исключениями.

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

Вы можете рассматривать этот материал как дополнительный и пропустить его (вместе с последующим разделом) при первом чтении.

Роль исключений

Что представляет "специальный" или "исключительный" случай? Мы увидим, что это понятие можно определить вполне строго, но пока будем полагаться на интуицию, считая, что это событие, которое не должно было случиться. Вот примеры некоторых событий, которые могут, каждое по-своему, разрушить блаженство нормального выполнения программы.

  • Деление на ноль, или другие арифметические неприятности.
  • Попытка создать объект после превышения пределов памяти.
  • Вызов void, который мы определили как попытку вызвать метод несуществующим объектом (x.f, где x имеет значение void).
  • Нарушение контрактов, если вы включили механизм мониторинга предусловий, постусловий и инвариантов в период выполнения.
  • Выполнение оператора, сигнализирующего о возникновении ошибочной ситуации.

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

  • Можно использовать обработку исключения как способ последней надежды обработать неожиданное событие, из-за которого нормальный поток управления потерпел неудачу. Нереально защищать каждое деление тестом: а не является ли знаменатель равным нулю? Нереально при каждом создании объекта проверять, а есть ли достаточно памяти. Еще труднее планировать некоторые другие ситуации — отказы аппаратуры, прерывания, инициируемые пользователем. Исключение позволяет программе восстановиться или, по крайней мере, с достоинством закончить свою работу, когда любой из ее операторов был прерван из-за возникновения неожиданного события. "Неожиданное" в данном контексте означает событие, не предусматриваемое при работе этого оператора.
  • Случай исключений, определенных программистом, отличен: здесь обработка исключений становится управляющей структурой, которую следует добавить к нашему каталогу.

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

Для проектирования подходящей стратегии использования исключений — в частности, определения того, что является "нормальным", а что "ненормальным", "исключительным", — будем основываться на понятии Проектирования по Контракту, определенном в предыдущих обсуждениях. Мы знаем, что каждая подпрограмма, независимо от ее конкретной реализации, позволяет задать:

  • предусловие: требования к вызывающей программе, которые должны быть выполнены непосредственно перед вызовом;
  • постусловие: истинное в конце выполнения и отвечающее интересам вызывающей программы;
  • инварианты охватывающего класса: общие свойства, необходимые всем методам класса. Этот подход можно расширить и на операции, не являющиеся подпрограммами, например, на сложение с плавающей точкой, на создание объектов и так далее.

В идеальном мире все семьи были бы счастливы, и все операции соответствовали бы своим контрактам. В реальном мире операции по тем или иным причинам не всегда выполняют свои обязательства. Если есть распределитель памяти, но вся доступная память уже распределена, то он не сможет создать объект. Если есть подпрограмма, некорректно запрограммированная, то она не сможет выполнить постусловие. Этих наблюдений достаточно для выработки точных определений.

Определения: Отказ, Исключение, Получатель
Отказ - неспособность операции выполнить свой контракт.

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

Получатель - подпрограмма, в которой возникло исключение.

"Отказ" является первичным понятием и применяется к операциям любого вида: подпрограммам, но также и к базисным операциям, таким как создание объекта. "Исключение" является производным понятием и применимо только к подпрограммам: в подпрограмме возникает исключение, если она выполняет операцию (базисную или вызов подпрограммы), приводящую к отказу.

Почему исключение — не то же самое, что и отказ? Часто появление исключения приводит к отказу у получателя, но не всегда! Подпрограмма может предусмотреть обработку исключениякод спасения, который попытается исправить положение, повторно запустит программу с лучшим исходом.

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

Мы уже сталкивались с примером исключения, причиной которого был "void-вызов".

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

Ирина Калашникова
Ирина Калашникова

Добрый день, подскажите на тест после каждой лекции сколько дается попыток? 

Наталья Король
Наталья Король

Что это значит?) Зранее спасибо)