И.Г. Головин - Конспект лекций по курсу Языки программирования (1161120), страница 15
Текст из файла (страница 15)
Удобство чтения. В стандарте языка Ада об этом было четко сказано. Сейчас от этогоправила отступили, но не слишком сильно.Обратим внимание, что в современных различие между блоком и составным оператором неслишком выделяется.В стандартном Паскале есть и блок, и составной оператор (заметим, что begin…end –составной оператор в чистом виде), где объявления размещать нельзя. Блок появляется впроцедуре. Процедура состоит из заголовок и тело, где тело – это блок. Аналогично – дляглавной программы, которая тоже состоит из заголовка и тела-блока.В этих языках обычно принята следующая синтаксическая структура:объявление составной операторобъявление begin последовательность операторов endВ Ада добавляется ключевое слово declare.Блок служит только как тело соответствующих процедур.Языки с явным терминаторомВернемся к вопросу: в синтаксическая разница между стандартным Паскалем и языкамиМодула, Оберон, Ада?В этих 3 языках поддерживается правило: любой структурный оператор должен иметьявный терминатор (завершитель, окончание).Многие языки (Алгол68 и позднее) используют это правило.
Там каждый структурныйоператор должен иметь завершитель.В предыдущих примерах в качестве терминатора выступало ключевое слово end.По правилам Ада терминатор можно дополнить необязательным описаниемсинтаксической конструкции, которая завершается: end if, end case и так далее.Компилятору легче локализовать ошибку.Пример сложно локализуемой ошибки:(((a))В этом примере невозможно определить, лишняя ли скобка, или, наоборот, не хватаетскобки.
Все, что может сказать транслятор: несоответствие скобок.Точно так же end if, end case упрощает диагностику.83Примечание: C, C++В языках С, С++: при пропуске }, компилятор выдает огромнуюдиагностику, по которой сложно понять ошибку.Еще хуже пропустить ; после класса.struct t1 { … }Это потому, что сразу после структуры можно объявитьпеременную или функцию.Strict time {…} time (struct time*) {…}Проблема в том, что явный терминатор отсутствует (скобка таковым не является).Хорошим стилем программирования было: блок всегда завершается именем именованнойсущности.Примерprocedure P…end pИнтересно, что у языков с явным терминатором есть 2 свойства:1. В этих языках составной оператор не нужен, т.к. конструкция begin … end являетсясоставной частью конструкции блок: <объявление> begin … end (Ада: тело, Оберон,Модула2: блок).В любом структурном операторе есть явный терминатор, поэтому в этих языкахвезде, где может стоять 1 оператор, может стоять и любое число операторов.Оператор структурного выбора:If B1 thenS1Else ifРекомендации по оформлению операторов структурного выбора: следует или все условияна одном уровне, или в одну строчку (чтобы подчеркнуть последовательность, а невложенность).Кстати, последовательные операторы легче воспринимаются человеком, чем вложенные.Кроме того, при использовании вложенности нужно писать много end if end if end if.Для этого ввели конструкции else if, elif, …if B1 thenS184elsif B2 then…elsif B3 then…ElseSnEnd ifЧастный случай: оператор case/switch.Оператор СASEРассмотрим подробнее case.ПримерCase expr ofСписок1:S1Список2: S2…ElseSnEndВ различных языках: одна константа, диапазон, список констант.
(Модула 2, Ада).1,3,4..8,10 (зачем нужен непонятно)Пример (Ада)Нужно ключевое слово whenWhen 1,3,4..8,19=>Others =>В Модула-2 используется другой синтаксис.Пример (Модула 2)1, 2:S1;S2;… |Case есть практически во всех современных языках. Опционально добавляется else-часть.Такой оператор можно промоделировать последовательностью if, но это дурной тон.Во-первых, case отлично моделирует записи с вариантами. Это ответ на проблемуобъединения типов (Янус-проблему).85Заметим, что синтаксис вариантной записи совпадает с синтаксисом оператора выбора.Во-вторых, case работает быстрее.Рассмотрим следующий пример:ПримерT = exprif(t == 1) S1;Else if (t==2) S2;Else Sn;Структурные выборы дискриминирующие.
Чем больше номер, тем больше: Таким образом,N-й по счету оператор выполняется только в том случае, если было N-1 проверок. Заметим,что иногда можно путем переупорядочивания увеличить быстродействие.Выражение внутри case дискретного типа (в C-подобных: интегрального). В языке C#: типастроки.Возникает вопрос, а как можно эффективно запрограммировать оператор case?ПримерCase I of0: S11: S2…255: S255В ассемблерном коде здесь создается таблица переходов.Пример (Fortran)GOTO(M1,…,Mn) iЭто означает GOTO MiЕсли метки идут «вразброс», то используется хэш-таблица или некая поисковая структура.В C# именно поэтому можно сделать строку в качестве метки case.Заметим, что если в операторе case всего лишь 2-3 метки, то линейный список эффективнее,чем бинарный поиск.
Если больше чем 3, то выгоднее сделать таблицу меток.Таким образом, case – это вычисляемый оператор перехода.Циклы.86Набора, который есть в стандартном паскале, хватает на всех.1. while B do S2. repeatS1,…,Snuntil B3. for (о цикле for мы поговорим отдельно)Вопрос: зачем использовать циклы, если есть goto?Здесь уместно вспомнить статью Дональда Кнута «О структурном программировании». Егоидея очень простая и очень здравая: не нужно слепо следовать правилам «нельзяиспользовать оператор goto, Нужно четко понимать, почему нельзя использовать goto.Почему же нельзя использовать goto? Потому что goto портит структуру программы.Однако, бывают ситуации, когда неиспользование goto портит структуру программы.Пример (выполняется в цикле)Подготовка вводаIf(success) {ввестиобработать}Рассмотрим блок-схему этого алгоритма:Подготовканетдаsuccess?ВводОбработкаЯвляется ли программа структурной? – Да: один вход, один выход.87Рассмотрим 2 варианта: без goto и с использованием goto.ПримерПодготовкаWhile (success) doBeginОбработатьПодготовкаEndМы нарушили структуру алгоритма, так как прибегли к дублированию кода.
Дубли – этовсегда плохо; потому что в случае изменений нужно менять в двух местах.Главный принцип хорошего программирования: структура алгоритма должнасоответствовать структуре программы.Хороший способ:ПримерWhile(true) doBeginПодготовитьIf not successGoto outendОбработатьEndOut:Операторы break, continue, return минимизирует наличие goto.Однако это не всегда спасает.Необходимость ограниченного выхода в некоторых случаях необходима.В Модуле 2 понятие цикла расширено следующим образом:LOOPSENDЕсли можно один оператор, то можно и много.Только внутри LOOP можно использовать метку EXIT.LOOP88IF B EXITENDEXITВ языке C: break может встретиться в C в любом цикле.В языке Ада:loopоператорыend loopЦиклы без выхода – нужны ли? В многопоточном программировании – да.
Их можнозакончить «снаружи». Но в общем случае наличие таких псевдо бесконечных цикловдопустимо.when условие => exit – внутри цикла (или например условного оператора).for v in range – другой вид заголовка цикла.Оператор break помогает почти всегда, и иногда (очень редко) в C++, в С – относительночасто (обработка аварийных ситуаций). В C нет встроенной обработки аварийных ситуаций.Чистить ресурсы нужно не только в случае аварийных ситуаций, но и вообще.Пример{{If(B) goto end;}End:Очистка захваченных ресурсов}Почему нужно выходить из цикла: Нам нужно выходить из середины 1) того требуетструктура алгоритма 2) аварийная ситуация: обрабатывать дальше не имеет смысла. Вязыке C в этой ситуации хорошо употребить goto, потому что если таких ситуацийнесколько, то будут повторяющиеся блоки кода.Один из методов рефакторинга: избавление от повторяющихся участков кода (рефакторинг– это изменение кода без изменения его функционала; необходимый этап в развитиикаждого проекта).Системы рефакторинга выискивают такие повторяющиеся части.Почему в C++ этого делать не надо?RAII (resource acquisition is initialization)89Захват ресурсов в конструкторе, освобождение – в деструкторе.Пример{{Wrapper w;If(B) return;}}Если бы вся работа с ресурсами была локальной, то можно было бы всегда применять этупарадигму.
К сожалению, в реальной жизни это далеко не всегда так.ИсторияНаписал однажды И. Г. мобильное приложение. Оно широкоиспользовало связь с сетью. Если есть связь, то всегда можнопредусмотреть, что загрузка тянется неограниченно долго и можетсломаться.Приходит пользователь и говорит: зациклилось.Курсор в форме часов – значит, зациклилась. А на самом делезациклилась загрузка ресурсов. У курсора есть точка привязки (mousepress).