diff --git a/03-инструкция/README.md b/03-инструкция/README.md index 73c6c46..13038ae 100644 --- a/03-инструкция/README.md +++ b/03-инструкция/README.md @@ -6,22 +6,22 @@ - [3.4. Инструкция static if](#3-4-инструкция-static-if) - [3.5. Инструкция switch](#3-5-инструкция-switch) - [3.6. Инструкция final switch](#3-6-инструкция-final-switch) -- [3.7. Циклы]() - - [3.7.1. Инструкция while (цикл с предусловием)]() - - [3.7.2. Инструкция do-while (цикл с постусловием)]() - - [3.7.3. Инструкция for (цикл со счетчиком)]() - - [3.7.4. Инструкция foreach (цикл просмотра)]() - - [3.7.5. Цикл просмотра для работы с массивами]() - - [3.7.6. Инструкции continue и break]() -- [3.8. Инструкция goto (безусловный переход)]() -- [3.9. Инструкция with]() -- [3.10. Инструкция return]() -- [3.11. Обработка исключительных ситуаций]() -- [3.12. Инструкция mixin]() -- [3.13. Инструкция scope]() -- [3.14. Инструкция synchronized]() -- [3.15. Конструкция asm]() -- [3.16. Итоги и справочник]() +- [3.7. Циклы](#3-7-циклы) + - [3.7.1. Инструкция while (цикл с предусловием)](#3-7-1-инструкция-while-цикл-с-предусловием) + - [3.7.2. Инструкция do-while (цикл с постусловием)](#3-7-2-инструкция-do-while-цикл-с-постусловием) + - [3.7.3. Инструкция for (цикл со счетчиком)](#3-7-3-инструкция-for-цикл-со-счетчиком) + - [3.7.4. Инструкция foreach (цикл просмотра)](#3-7-4-инструкция-foreach-цикл-просмотра) + - [3.7.5. Цикл просмотра для работы с массивами](#3-7-5-цикл-просмотра-для-работы-с-массивами) + - [3.7.6. Инструкции continue и break](#3-7-6-инструкции-continue-и-break) +- [3.8. Инструкция goto (безусловный переход)](#3-8-инструкция-goto-безусловный-переход) +- [3.9. Инструкция with](#3-9-инструкция-with) +- [3.10. Инструкция return](#3-10-инструкция-return) +- [3.11. Обработка исключительных ситуаций](#3-11-обработка-исключительных-ситуаций) +- [3.12. Инструкция mixin](#3-12-инструкция-mixin) +- [3.13. Инструкция scope](#3-13-инструкция-scope) +- [3.14. Инструкция synchronized](#3-14-инструкция-synchronized) +- [3.15. Конструкция asm](#3-15-конструкция-asm) +- [3.16. Итоги и справочник](#3-16-итоги-и-справочник) Эта глава содержит обязательные определения всех инструкций языка D. D наследует внешний вид и функциональность языков семейства C – в нем есть привычные инструкции `if`, `while`, `for` и другие. Наряду с этим D предлагает ряд новых интересных инструкций и некоторое усовершенствование старых. Если неизбежное перечисление с подробным описанием каждой инструкции заранее нагоняет на вас скуку, то вот вам несколько «отступлений» – любопытных отличий D от других языков. @@ -302,10 +302,10 @@ switch (‹выражение›) ‹инструкция› `‹выражение›` может иметь числовой, перечисляемый или строковый тип; `‹инструкция›` может содержать метки (ярлыки, labels), определенные следующим образом: -1. `case ‹в›`: Перейти сюда, если `‹выражение› == ‹в›`. Чтобы можно было использовать внутри `в` запятые (см. раздел 2.3.18), все выражение требуется заключить в круглые скобки. -2. `case ‹в1›, ‹в2›, … , ‹вn›`: Каждая запись вида ‹вk› обозначает выражение. Рассматриваемая инструкция эквивалентна инструкции `case ‹элемент1›: case ‹элемент2›:, ... , case ‹элементn›:`. -3. `case ‹в1›: .. case ‹в2›`: Перейти сюда, если `‹выражение› >= ‹в1›` и `‹выражение› <= ‹в2›`. -4. `default`: Перейти сюда, если никакой другой переход невозможен. +1. `case ‹в›`: перейти сюда, если `‹выражение› == ‹в›`. Чтобы можно было использовать внутри `в` запятые (см. раздел 2.3.18), все выражение требуется заключить в круглые скобки. +2. `case ‹в1›, ‹в2›, … , ‹вn›`: каждая запись вида `‹вk›` обозначает выражение. Рассматриваемая инструкция эквивалентна инструкции `case ‹элемент1›: case ‹элемент2›:, ... , case ‹элементn›:`. +3. `case ‹в1›: .. case ‹в2›`: перейти сюда, если `‹выражение› >= ‹в1›` и `‹выражение› <= ‹в2›`. +4. `default`: перейти сюда, если никакой другой переход невозможен. `‹выражение›` вычисляется один раз для всех этих проверок. Выражение в каждой метке `case` – это любое не противоречащее правилам языка выражение, которое можно проверить на равенство выражению `‹выражение›`, а также на неравенство в случае использования синтаксиса с интервалом. Обычно `case`-выражения представлены константами, вычисляемыми во время компиляции, но D разрешает использовать и переменные, гарантируя, что вычисления будут производиться в порядке следования альтернатив до первого совпадения. По завершении вычислений выполняется переход к соответствующей метке `case` или `default` и выполнение программы продолжается из этой точки. Для того чтобы покинуть ветвление, используется инструкция break, осуществляющая выход из инструкции `switch`. В отличие от языков C и C++, D запрещает неявный переход к следующей метке и требует инструкции `break` или `return` после кода, соответствующего метке. @@ -388,5 +388,739 @@ Error: final switch statement must handle all values [В начало ⮍](#3-6-инструкция-final-switch) [Наверх ⮍](#3-инструкции) +## 3.7. Циклы + +### 3.7.1. Инструкция while (цикл с предусловием) + +Да, именно так: + +```d +while (‹выражение›) ‹инструкция› +``` + +Сначала вычисляется `‹выражение›`. Если оно ненулевое, выполняется `‹инструкция›` и цикл возобновляется: снова вычисляется `‹выражение›` и т. д. Иначе управление передается инструкции, расположенной сразу после цикла `while`. + +[В начало ⮍](#3-7-1-инструкция-while-цикл-с-предусловием) [Наверх ⮍](#3-инструкции) + +### 3.7.2. Инструкция do-while (цикл с постусловием) + +Если нужен цикл, который обязательно выполнится хотя бы раз, подойдет цикл с постусловием: + +```d +do ‹инструкция› while (‹выражение›); +``` + +Обратите внимание на обязательную точку с запятой в конце инструкции. Кроме того, после do должна быть хотя бы одна `‹инструкция›`. Цикл с постусловием эквивалентен циклу с предусловием, в котором сначала один раз выполняется `‹инструкция›`. + +[В начало ⮍](#3-7-2-инструкция-do-while-цикл-с-постусловием) [Наверх ⮍](#3-инструкции) + +### 3.7.3. Инструкция for (цикл со счетчиком) +Синтаксис цикла со счетчиком: + +```d +for (‹определение счетчика›; ‹выр1›; ‹выр2›) ‹инструкция› +``` + +Любое из выражений ‹определение счетчика›, `‹выр1›` и `‹выр2›` (или все сразу) можно опустить. Если нет выражения `‹выр1›`, считается, что оно истинно. Выражение `‹определение счетчика›` – это или объявление значения (например, `auto i = 0;` или `float w;`), или выражение с точкой с запятой в конце (например, `i = 10;`). По семантике использования цикл со счетчиком идентичен одноименным инструкциям из других языков: сначала вычисляется `‹определение счетчика›`, затем `‹выр1›`; если оно истинно, выполняется `‹инструкция›`, потом вычисляется `‹выр2›`, после чего выполнение цикла продолжается новым вычислением `‹выр1›`. + +[В начало ⮍](#3-7-3-инструкция-for-цикл-со-счетчиком) [Наверх ⮍](#3-инструкции) + +### 3.7.4. Инструкция foreach (цикл просмотра) + +Самое удобное, безопасное и зачастую быстрое средство просмотра значений в цикле – инструкция `foreach`[^3], у которой есть несколько вариантов. Простейший вариант цикла просмотра: + +```d +foreach (‹идентификатор›; ‹выражение1› .. ‹выражение2›) ‹инструкция› +``` + +`‹выражение1›` и `‹выражение2›` могут быть числами или указателями. Попросту говоря, `‹идентификатор›` проходит интервал от (включая) `‹выражения1›` до (не включая) `‹выражения2›`. Ради понятности этого неформального определения в нем не освещены некоторые детали. Например, сколько раз вычисляется `‹выражение2›` в процессе выполнения цикла – один или несколько? Или что происходит, если `‹выражение1› >= ‹выражение2›`? Все это легко узнать, взглянув на семантически эквивалентный код, приведенный ниже. Техника представления высокоуровневых конструкций в терминах эквивалентных конструкций более простого (под)языка (в виде абстракций более низкого уровня) называется *снижением* (*lowering*). Она будет широко использоваться на протяжении всей этой главы. + +```d +{ + auto __n = ‹выражение2›; + auto ‹идентификатор› = true ? ‹выражение1› : ‹выражение2›; + for (; ‹идентификатор› < __n; ++‹идентификатор› ) ‹инструкция› +} +``` + +Здесь идентификатор `__n` генерируется компилятором, что гарантирует отсутствие конфликтов с другими идентификаторами[^4] («свежее слово» в лексиконе тех, кто пишет компиляторы). + +(Зачем нужны внешние фигурные скобки? Они гарантируют, что `‹идентификатор›` не «просочится» за пределы цикла `foreach`, а также благодаря им вся эта конструкция – это одна инструкция.) + +Теперь ясно, что и `‹выражение1›`, и `‹выражение2›` вычисляются всего один раз, а тип значения `‹идентификатор›` определяется по правилам для тернарной условной операции (см. раздел 2.3.16) – вот в чем роль знаков `?:`, никак не проявляющих себя при исполнении программы. Осторожное «примирение» типов, достигнутое благодаря знакам `?:`, гарантирует предотвращение или, по крайней мере, выявление потенциальной неразберихи с числами разного размера и точности, а также конфликтов между типами со знаком и без знака. + +Заметим, что компилятор принудительно не назначает `__n` какой-либо особый тип, то есть вы можете использовать этот вариант цикла `foreach` с пользовательскими типами, для которых определены оператор сравнения «меньше» (`<`) и оператор увеличения на единицу (мы научимся делать это в главе 12). Кроме того, если для типа не определен оператор `<`, но определен оператор сравнения на равенство, компилятор автоматически заменит оператор `<` оператором `!=` при снижении. В этом случае не может быть проверена корректность задания интервала, поэтому вы должны удостовериться, что верхняя граница может быть достигнута с помощью повторного применения оператора `++`, начиная с нижней границы. Иначе результат будет непредсказуемым.[^5] + +Заметим, что вы можете определить нужный тип счетчика внутри части `‹идентификатор›` определения цикла. Обычно такое объявление излишне, но полезно, если вы хотите, чтобы тип счетчика удовлетворял ряду особых условий, исключал путаницу в знаковости/беззнаковости или при необходимости использования неявного преобразования типов: + +```d +import std.math, std.stdio; + +void main() +{ + foreach (float elem; 1.0 .. 100.0) + { + writeln(log(elem)); // Получает логарифм с одинарной точностью + } + foreach (double elem; 1.0 .. 100.0) + { + writeln(log(elem)); // Двойная точность + } + foreach (elem; 1.0 .. 100.0) + { + writeln(log(elem)); // То же самое + } +} +``` + +[В начало ⮍](#3-7-4-инструкция-foreach-цикл-просмотра) [Наверх ⮍](#3-инструкции) + +### 3.7.5. Цикл просмотра для работы с массивами + +Перейдем к другому варианту инструкции `foreach`, предназначенному для работы с массивами и срезами: + +```d +foreach (‹идентификатор›; ‹выражение›) ‹инструкция› +``` + +`‹выражение›` должно быть массивом (линейным или ассоциативным), срезом или иметь пользовательский тип. Последний случай мы рассмотрим в главе 12, а сейчас сосредоточимся на массивах и срезах. После того как `‹выражение›` было один раз вычислено, ссылка на него сохраняется в закрытой временной переменной. (Сам массив не копируется.) Затем переменной с именем `‹идентификатор›` по очереди присваивается каждый из элементов массива и выполняется `‹инструкция›`. Так же как и в случае с циклом просмотра с интервалами, допускается указание типа перед `‹идентификатором›`. + +Инструкция `foreach` предполагает, что во время итераций длина массива изменяться не будет; если вы задумали иное, возможно, вам стоит задействовать простой цикл просмотра и побольше внимания. + +**Обновление во время итерации** + +Присваивание переменной `‹идентификатор›` внутри `‹инструкции›` не отражается на состоянии массива. Если вы действительно хотите изменить элемент, рассматриваемый в текущей итерации, определите `‹идентификатор›` как ссылку, расположив перед ним `ref` или `ref ‹тип›`. Например: + +```d +void scale(float[] array, float s) +{ + foreach (ref e; array) + { + e *= s; // Обновляет массив "на месте" + } +} +``` + +В приведенный код можно после `ref` добавить полное определение переменной e (включая ее тип), например `ref float e`. Однако на этот раз соответствие должно быть *точным*: `ref` запрещает преобразования типов! + +```d +float[] arr = [ 1.0, 2.5, 4.0 ]; +foreach (ref float elem; arr) +{ + elem *= 2; // Без проблем +} +foreach (ref double elem; arr) // Ошибка! +{ + elem /= 2; +} +``` + +Причина такого поведения программы проста: чтобы гарантировать корректное присваивание, `ref` ожидает точного совпадения представления. Несмотря на то что из значения типа `float` всегда можно получить значение типа `double`, вы не вправе обновить значение типа `float` присваиванием типа `double` по нескольким причинам, самая очевидная из которых – разный размер этих типов. + +**Где я?** + +Иногда полезно иметь доступ к индексу итерации. Следующий вариант цикла просмотра позволяет привязать идентификатор к этому значению: + +```d +foreach (‹идентификатор1›, ‹идентификатор2›; ‹выражение›) ‹инструкция› +``` + +Так что можно написать: + +```d +void print(int[] array) +{ + foreach (i, e; array) + { + writefln("array[%s] = %s;", i, e); + } +} +``` + +Эта функция печатает содержание массива в виде, соответствующем коду на D. При выполнении `print([5, 2, 8])` выводится: + +```d +array[0] = 5; +array[1] = 2; +array[2] = 8; +``` + +Гораздо интереснее наблюдать за обращением к индексу элемента при работе с ассоциативными массивами: + +```d +void print(double[string] map) +{ + foreach (i, e; map) + { + writefln("array['%s'] = %s;", i, e); + } +} +``` + +Теперь `print(["Луна": 1.283, "Солнце": 499.307, "Проксима Центавра": 133814298.759])` выведет + +```d +array['Проксима Центавра'] = 1.33814e+08; +array['Солнце'] = 499.307; +array['Луна'] = 1.283; +``` + +Обратите внимание: элементы напечатаны не в том порядке, в каком они заданы в литерале. Интересно, что экспериментируя с тем же кодом, но в разных реализациях языка или разных версиях одной и той же реализации, можно наблюдать изменение порядка. Дело в том, что в ассоциативных массивах применяются таинственные методики, повышающие эффективность хранения и выборки элементов за счет отказа от гарантированного упорядочивания. + +Тип индекса и самого элемента определяются по контексту. Можно действовать и по-другому, «навязывая» нужные типы одной из переменных `‹идентификатор1›` и `‹идентификатор2›` или обеим сразу. Однако помните, что `‹идентификатор1›` не может быть ссылкой (перед ним нельзя поставить ключевое слово `ref`). + +**Проделки** + +Во время итерации можно по-разному изменять просматриваемый массив: +- *Изменение массива «на месте»*. Во время итерации будут «видны» изменения еще не посещенных ячеек массива. +- *Изменение размера массива*. Цикл повторяется, пока не будет просмотрено столько элементов массива, сколько в нем было до входа в цикл. Возможно, в результате изменения размера массив будет перемещен в другую область памяти; в этом случае последующее изменение массива не видно во время итерации, а также последующие изменения, вызванные во время самой итерации, не отразятся на массиве. Использовать такую технику не рекомендуется, поскольку правила перемещения массива в памяти зависят от реализации. +- *Освобождение выделенной под массив памяти (полное или частичное; в последнем случае говорят о «сжатии» массива) с помощью низкоуровневых функций управления памятью*. Желая получить полный контроль и достичь максимальной эффективности, вы не пожалели времени на изучение низкоуровневого управления памятью по документации к своей реализации языка. Все, что можно предположить: 1) вы знаете, что творите, и 2) не скучно с вами лишь тому, кто написал собственный сборщик мусора. + +[В начало ⮍](#3-7-5-цикл-просмотра-для-работы-с-массивами) [Наверх ⮍](#3-инструкции) + +### 3.7.6. Инструкции continue и break + +Инструкция `continue` выполняет переход к началу новой итерации цикла, определяемого ближайшей к ней инструкцией `while`, `do-while`, `for` или `foreach`. Инструкции, расположенные между `continue` и концом тела цикла не выполняются. + +Инструкция `break` выполняет переход к коду, расположенному сразу после ближайшей к ней инструкции `while`, `do-while`, `for`, `foreach`, `switch` или `final switch`, мгновенно завершая ее выполнение. + +Обе инструкции можно использовать с необязательной меткой, указывающей, к какой именно инструкции они относятся. «Пометка» инструкций `continue` и `break` значительно упрощает построение сложных вариантов итераций, позволяя обойтись без переменных состояния и инструкции `goto`, описанной в следующем разделе. + +```d +void fun(string[] strings) +{ + loop: foreach (s; strings) + { + switch (s) + { + default: ...; break; // Выйти из инструкции switch + case "ls": ...; break; // Выйти из инструкции switch + case "rm": ...; break; // Выйти из инструкции switch + ... + case "#": break loop; // Проигнорировать оставшиеся строки (прервать цикл foreach) + } + } + ... +} +``` + +[В начало ⮍](#3-7-6-инструкции-continue-и-break) [Наверх ⮍](#3-инструкции) + +## 3.8. Инструкция goto (безусловный переход) + +В связи с глобальным потеплением не будем горячиться по поводу инструкции `goto`. Достаточно сказать, что в D она имеет следующий синтаксис: + +```d +goto ‹метка›; +``` + +Идентификатор `‹метка›` должен быть виден внутри функции, где вызывается `goto`. Метка определяется явно как идентификатор с двоеточием, расположенный перед инструкцией. Например: + +```d +int a; +... +mylabel: a = 1; +... +if (a == 0) goto mylabel; +``` + +Нельзя переопределять метки внутри одной и той же функции. Другое ограничение состоит в том, что `goto` не может «перепрыгнуть» точку определения значения, видимого в точке «приземления». Например: + +```d +void main() +{ + goto target; + int x = 10; + target: {} // Ошибка! goto заставляет пропустить определение x! +} +``` + +Наконец, инструкция `goto` не может выполнить переход за границу исключения (см. раздел 3.11). Таким образом, у инструкции `goto` почти нет ограничений, и именно это делает ее опасной. С помощью `goto` можно перейти куда угодно: вперед или назад, внутрь или за пределы инструкций `if`, внутрь и за пределы циклов, включая пресловутый переход прямо в середину тела цикла. + +Тем не менее в D опасно не все, чего коснется `goto`. Если написать внутри конструкции `switch` инструкцию + +```d +goto case ‹выражение›; +``` + +то будет выполнен переход к соответствующей метке `case ‹выражение›`. Инструкция + +```d +goto case; +``` + +выполняет переход к следующей метке `case`. Инструкция + +```d +goto default; +``` + +выполняет переход к метке `default`. Несмотря на то что эти переходы ничуть не более структурированы, чем любые другие случаи использования `goto`, их легче отслеживать, поскольку они расположены в одном месте программы и значительно упрощают структуру инструкции `switch`: + +```d +enum Pref { superFast, veryFast, fast, accurate,regular, slow, slower }; +Pref preference; +double coarseness = 1; +... +switch (preference) +{ + case Pref.fast: ...; break; + case Pref.veryFast: coarseness = 1.5; goto case Pref.fast; + case Pref.superFast: coarseness = 3; goto case Pref.fast; + case Pref.accurate: ...; break; + case Pref.regular: goto default; + default: ... + ... +} +``` + +При наличии инструкций `break` и `continue` с метками (см. раздел 3.7.6), исключений (см. раздел 3.11) и инструкции `scope` (мощное средство управления порядком выполнения программы, см. раздел 3.13) поклонникам `goto` все труднее найти себе достойное оправдание. + +[В начало ⮍](#3-8-инструкция-goto-безусловный-переход) [Наверх ⮍](#3-инструкции) + +## 3.9. Инструкция with + +Созданная по примеру Паскаля инструкция `with` облегчает работу с конкретным объектом. + +Синтаксис: + +```d +with (‹выражение›) ‹инструкция› +``` + +сначала вычисляется `‹выражение›`, после чего внутренние элементы верхнего уровня вложенности полученного объекта делаются видимыми внутри `‹инструкции›`. Мы уже встречались со структурами в главе 1, поэтому рассмотрим пример с применением типа `struct`: + +```d +import std.math, std.stdio; + +struct Point +{ + double x, y; + double norm() { return sqrt(x * x + y * y); } +} + +void main() +{ + Point p; + int z; + with (p) + { + x = 3; // Присваивает значение полю p.x + p.y = 4; // Хорошо, что все еще можно явно использовать p + writeln(norm()); // Выводит значение поля p.norm, то есть 5 + z = 1; // Поле z осталось видимым + } +} +``` + +Изменения полей отражаются непосредственно на объекте, с которым работает инструкция `with`: она «распознает» в `p` l-значение и помнит об этом. + +Если один из идентификаторов, включенный в область видимости с помощью инструкции `with`, перекрывает ранее определенный в функции идентификатор, то из-за возникшей неопределенности компилятор запрещает доступ к такому идентификатору. При том же определении структуры `Point` следующий код не скомпилируется: + +```d +void fun() +{ + Point p; + string y = "Я занимаюсь точкой (острю)."; + with (p) + { + writeln(x, ":", y); // Ошибка! Полю p.y запрещено перекрывать переменную y! + } +} +``` + +Однако об ошибке сообщается только в случае *реальной*, а не *потенциальной* неопределенности. Например, если бы инструкция `with` в последнем примере вообще не использовала идентификатор `y`, этот код скомпилировался бы и запустился, несмотря на скрытую неопределенность. Кроме того, программа работала бы при замене строки `writeln(x, ":", y);` строкой `writeln(x, ":", p.y);`, поскольку явное указание принадлежности идентификатора `y` объекту `p` полностью исключает неопределенность. + +Инструкция `with` может перекрывать различные идентификаторы уровня модуля (то есть глобальные идентификаторы). Доступ к идентификаторам, перекрытым инструкцией `with`, осуществляется с помощью синтаксиса `.идентификатор`. + +Заметим, что можно сделать неявной множественную вложенность объектов, написав: + +```d +with (‹выр1›) with (‹выр2›) ... with (‹вырn›) ‹инструкция› +``` + +При использовании вложенных инструкций `with` нет угрозы неопределенности, так как язык запрещает во внутренней инструкции `with` перекрывать идентификатор, определенный во внешней инструкции `with`. В двух словах: в D локальному идентификатору запрещено перекрывать другой локальный идентификатор. + +[В начало ⮍](#3-9-инструкция-with) [Наверх ⮍](#3-инструкции) + +## 3.10. Инструкция return + +Чтобы немедленно вернуть значение из текущей функции, напишите + +```d +return ‹выражение›; +``` + +Эта инструкция вычисляет `‹выражение›` и возвращает полученное значение в точку вызова функции, предварительно неявно преобразовав его (если требуется) к типу, возвращаемому этой функцией. + +Если текущая функция имеет тип `void`, `‹выражение›` должно быть опущено или представлять собой вызов функции, которая в свою очередь имеет тип `void`. + +Выход из функции, возвращающей не `void`, должен осуществляться посредством инструкции `return`. Во время компиляции это трудно эффективно отследить, так что, возможно, иногда вы будете получать от компилятора необоснованные претензии. + +[В начало ⮍](#3-10-инструкция-return) [Наверх ⮍](#3-инструкции) + +## 3.11. Обработка исключительных ситуаций + +Язык программирования D поддерживает обработку ошибок с помощью механизма исключительных ситуаций, или исключений (exceptions). Исключение инициируется инструкцией `throw`, а обрабатывается инструкцией `try`. Чтобы породить исключение, обычно пишут: + +```d +throw new SomeException("Произошло нечто подозрительное"); +``` + +Тип `SomeException` должен наследовать от встроенного класса `Throwable`. D не поддерживает создание исключительных ситуаций произвольных типов, отчасти потому, что, как мы скоро увидим, назначение определенного класса корневым облегчает обработку цепочек исключений разных типов. + +Чтобы обработать исключение или просто быть в курсе, что оно произошло, используйте инструкцию `try`, которая в общем виде выглядит так: + +```d +try ‹инструкция› +catch (‹И1› ‹и1›) ‹инструкция1› +catch (‹И2› ‹и2›) ‹инструкция2› +... +catch (‹Иn› ‹иn›) ‹инструкцияn› +finally ‹инструкцияf› +``` + +Можно опустить компонент `finally`, как и любой из компонентов `catch` (или даже все компоненты `catch`). Однако должно соблюдаться условие: в инструкции `try` должен быть хотя бы один компонент `finally` или `catch`. `‹Иk›` – это типы, которые, как уже сказано, должны наследовать от `Throwable`. Идентификаторы `‹иk›` связаны с захваченным объектом-исключением и могут отсутствовать. + +Семантика всей инструкции такова. Сначала выполняется `‹инструкция›`. Если при ее выполнении возникает исключение (назовем его `‹их›` и будем считать, что оно имеет тип `‹Их›`), то предпринимаются попытки сопоставить типы `‹И1›, ‹И2›, ..., ‹Иn›` с типом `‹Их›`. «Побеждает» первый тип `‹Иk›`, который совпадает с `‹Их›` или является его предком. С объектом-исключением `‹их›` связывается идентификатор `‹иk›` и выполняется `‹инструкцияk›`. Исключение считается обработанным, поэтому если во время выполнения самой `‹инструкцииk›` также возникнет исключение, информация о нем не будет разослана компонентам `catch`, содержащим возможные обработчики текущего исключения. Если ни один из типов `‹Иk›` не подходит, исключение `‹их›` всплывает по стеку вызовов с целью поиска обработчика. + +Если компонент `finally` присутствует, `‹инструкцияf›` выполняется абсолютно во всех случаях: независимо от того, порождается исключение или нет, и даже если исключение было обработано одним из компонентов `catch` и в результате было порождено новое исключение. Этот код гарантированно выполняется (если, конечно, не помешают бесконечные циклы и системные вызовы, вызывающие останов программы). Если и `‹инструкцияf›` порождает исключение, оно будет присоединено к текущей цепочке исключений. Механизм исключений языка D подробно описан в главе 9. + +Инструкция `goto` (см. раздел 3.8) не может совершить переход внутрь инструкций `‹инструкция›`, `‹инструкция1›`, `...`, `‹инструкцияn›` и `‹инструкцияf›`, кроме случая, когда `goto` находится внутри самой инструкции. + +[В начало ⮍](#3-11-обработка-исключительных-ситуаций) [Наверх ⮍](#3-инструкции) + +## 3.12. Инструкция mixin + +Благодаря главе 2 (см. раздел 2.3.4.2) мы узнали, что с помощью выражений `mixin` можно преобразовывать строки, известные во время компиляции, в выражения на D, которые компилируются как обычный код. Инструкции с `mixin` предоставляют еще больше возможностей, позволяя создавать с помощью `mixin` не только выражения, но также объявления и инструкции. + +Предположим, вы хотите как можно быстрее выяснить число ненулевых разрядов в байте. Это число, называемое весом Хемминга, используется в решении множества прикладных задач, таких как шифрование, распределенные вычисления и приближенный поиск в базе данных. Простейший способ подсчета ненулевых битов в байте: последовательно суммировать значения младшего бита, сдвигая каждый раз введенное число на один разряд вправо. Более быстрый метод был впервые предложен Питером Вегнером и популяризирован Керниганом и Ричи в их классическом труде: + +```d +uint bitsSet(uint value) +{ + uint result; + for (; value; ++result) + { + value &= value - 1; + } + return result; +} + +unittest +{ + assert(bitsSet(10) == 2); + assert(bitsSet(0) == 0); + assert(bitsSet(255) == 8); +} +``` + +Этот метод быстрее, чем самый очевидный, потому что цикл выполняется ровно столько раз, сколько ненулевых битов во введенном значении. Но функция `bitsSet` все равно тратит время на управление инструкциями; более быстрый метод – это обращение к нужной ячейке таблицы (в терминах D – к нужному элементу массива). Можно улучшить результат, заполнив таблицу еще во время компиляции; вот здесь и пригодится объявление с помощью инструкции `mixin`. Задумка в том, чтобы сначала создать строку, которая выглядит как объявление линейного массива, а затем с помощью `mixin` скомпилировать эту строку в обычный код. Генератор таблицы может выглядеть так: + +```d +import std.conv; + +string makeHammingWeightsTable(string name, uint max = 255) +{ + string result = "immutable ubyte["~to!string(max + 1)~"] "~name~" = [ "; + foreach (b; 0 .. max + 1) + { + result ~= to!string(bitsSet(b)) ~ ", "; + } + return result ~ "];"; +} +``` + +Вызов функции `makeHammingWeightsTable` возвращает строку `"immutable ubyte[256] t = [ 0, 1, 1, 2, ..., 7, 7, 8, ];"`. Квалификатор `immutable` (см. главу 8) указывает, что таблица никогда не изменится после инициализации. С библиотечной функцией `to!string` мы впервые встретились в разделе 1.6. Эта функция преобразует в строку любое значение (в данном случае значения типа `uint`, возвращаемые функцией `bitsSet`). Теперь, когда у нас есть нужный код в виде строки, для определения таблицы достаточно выполнить всего одно действие: + +```d +mixin(makeHammingWeightsTable("hwTable")); + +unittest +{ + assert(hwTable[10] == 2); + assert(hwTable[0] == 0); + assert(hwTable[255] == 8); +} +``` + +Теоретически можно строить таблицы любого размера, но полученную программу всегда рекомендуется тестировать: из-за кэширования работа со слишком большими таблицами может на самом деле выполняться медленнее вычислений. + +В качестве последнего средства (как тренер по айкидо скрепя сердце рекомендует ученикам газовый баллончик) стоит упомянуть сочетание импорта строки (инструкция `import`, см. раздел 2.2.5.1) и объявлений, созданных с помощью `mixin`, которое позволяет реализовать самую примитивную форму модульности – текстовое включение. Полюбуйтесь: + +```d +mixin(import("widget.d")); +``` + +Выражение `import` считывает текст файла `widget.d` в строковый литерал, который выражение `mixin` тут же преобразует в код. Используйте этот трюк, только если действительно считаете, что без него ваша честь хакера поставлена на карту. + +[В начало ⮍](#3-12-инструкция-mixin) [Наверх ⮍](#3-инструкции) + +## 3.13. Инструкция scope + +Инструкция `scope` – нововведение D, хотя и другие языки в той или иной форме реализуют подобную функциональность. Инструкция `scope` позволяет легко писать на D корректно работающий код и, главное, без проблем читать и понимать его впоследствии. Можно и другими средствами достичь свойственной коду со `scope` корректности, однако, за исключением самых заурядных примеров, результат окажется непостижимым. + +Синтаксис: + +```d +scope(exit) ‹инструкция› +``` + +`‹инструкция›` принудительно выполняется после того, как поток управления покинет текущую область видимости (контекст). Результат будет таким же, что и при использовании компонента `finally` инструкции `try`, но в общем случае инструкция `scope` более масштабируема. С помощью `scope(exit)` удобно гарантировать, что, оставляя контекст, вы «навели порядок». Допустим, в вашем приложении используется флаг `g_verbose` («говорливый»), который вы хотите временно отключить. Тогда можно написать: + +```d +bool g_verbose; +... +void silentFunction() +{ + auto oldVerbose = g_verbose; + scope(exit) g_verbose = oldVerbose; + g_verbose = false; + ... +} +``` + +Остаток кода «молчаливой» функции `silentFunction` может быть любым, с досрочными выходами и возможными исключениями, но вы можете быть полностью уверены, что по окончании ее выполнения, даже если наступит конец света или начнется потоп, флаг `g_verbose` будет корректно восстановлен. + +Чтобы в общих чертах представить действие инструкции `scope(exit)`, определим для нее *снижение*, то есть общий метод преобразования кода, содержащего `scope(exit)`, в эквивалентный код с другими инструкциями, такими как `try`. Мы уже неформально применяли технику снижения, рассматривая работу цикла со счетчиком в терминах цикла с предусловием, а цикла просмотра – в терминах цикла со счетчиком. + +Рассмотрим блок, содержащий инструкцию `scope(exit)`: + +```d +{ + ‹инструкции1› + scope(exit) ‹инструкция2› + ‹инструкции3› +} +``` + +Пусть явно отображенный вызов `scope` – первый в этом блоке, то есть `‹инструкции1›` не содержат вызовов `scope` (но инструкции `‹инструкция2›` и `‹инструкции3›` могут его содержать). Применив технику снижения, преобразуем этот код в код следующего вида: + +```d +{ + ‹инструкции1› + try + { + ‹инструкции3› + } + finally + { + ‹инструкция2› + } +} +``` + +На этом преобразование не заканчивается. Инструкции `‹инструкция2›` и `‹инструкции3›` также подвергаются снижению, поскольку могут содержать дополнительные инструкции `scope`. (Процесс снижения всегда конечен, так как фрагменты всегда строго меньше исходной последовательности.) Это означает, что код, содержащий несколько инструкций `scope(exit)`, вполне корректен, даже в таких странных случаях, как `scope(exit) scope(exit) scope(exit) writeln("?")`. Посмотрим, что происходит в любопытном случае, когда в одном и том же блоке встречаются две инструкции `scope(exit)`: + +```d +{ + ‹инструкции1› + scope(exit) ‹инструкция2› + ‹инструкции3› + scope(exit) ‹инструкция4› + ‹инструкции5› +} +``` + +Предположим, что ни одна из инструкций не содержит ни одного дополнительного вызова инструкции `scope`. Воспользовавшись снижением, получим: + +```d +{ + ‹инструкции1› + try + { + ‹инструкции3› + try + { + ‹инструкции5› + } + finally + { + ‹инструкция4› + } + } + finally + { + ‹инструкция2› + } +} +``` + +Этот громоздкий код поможет нам выяснить порядок выполнения нескольких инструкций `scope(exit)` в одном блоке. Проследив порядок выполнения инструкций в полученном коде, можно сделать вывод, что инструкция `‹инструкция4›` выполняется до инструкции `‹инструкция2›`. Обобщенно, инструкции `scope(exit)` выполняются по схеме LIFO[^6]: в порядке, обратном их следованию в тексте программы. + +Отслеживать порядок выполнения инструкций `scope` гораздо легче, чем порядок выполнения эквивалентного кода с конструкцией `try/finally`; элементарно: инструкция `scope` гарантирует, что управляемая ею инструкция будет выполнена при выходе из контекста. Это позволяет вам защитить свой код от ошибок без неудобной иерархии конструкций `try/finally` – простым перечислением нужных действий в одной строке. + +Предыдущий пример демонстрирует еще одно прекрасное свойство инструкции `scope` – масштабируемость. С учетом огромной масштабируемости эта инструкция просто неотразима. (В конце концов, если бы требовалось лишь изредка выполнять одну-единственную инструкцию `scope`, можно было бы вручную написать ее сниженный эквивалент по указанной выше методике.) Функциональность нескольких инструкций `scope(exit)` требует увеличения длины кода программы – при использовании самих инструкций `scope(exit)` и одновременного увеличения как длины, так и глубины кода – при использовании эквивалентных инструкций `try`. Причем в глубину код масштабируется очень слабо, к тому же приходится делить «владения» с другими составными инструкциями, такими как `if` или `for`. Еще один подходящий вариант масштабируемого решения – применение деструкторов в стиле C++ (также поддерживаемых D; см. главу 7), если только вам удастся снизить стоимость определения новых типов. Но если приходится определять класс только потому, что понадобился его деструктор (а зачем еще нужен класс типа `CleanerUpper`[^7]?), то в плане масштабируемости это решение даже хуже вложенных инструкций `try`. Вкратце, если классы – вакуумная сварка, а инструкции `try` – жевательная резинка, то инструкция `scope(exit)` – эпоксидный суперклей. + +Инструкция `scope(success) ‹инструкция›` включает `‹инструкцию›` в «график» программы только в случае обычного выхода из текущей области видимости (не в результате исключения). Выполним снижение для инструкции `scope(success)`. Код + +```d +{ + ‹инструкции1› + scope(success) ‹инструкция2› + ‹инструкции3› +} +``` + +превращается в + +```d +{ + ‹инструкции1› + bool __succeeded = true; + try + { + ‹инструкции3› + } + catch(Exception e) + { + __succeeded = false; + throw e; + } + finally + { + if (__succeeded) ‹инструкция2› + } +} +``` + +Далее, инструкции `‹инструкция2›` и `‹инструкции3›` также подвергаются снижению; процесс повторяется, пока не останется вложенных инструкций `scope`. + +Перейдем к более мрачному варианту инструкции `scope` – инструкции `scope(failure) ‹инструкция›`. Такая запись предписывает выполнить `‹инструкцию›` только при выходе из текущего контекста в результате возникшей исключительной ситуации. + +Снижение для инструкции `scope(failure)` практически идентично снижению для инструкции `scope(success)`, с тем лишь отличием, что флаг `__succeeded` проверяется на равенство `false`, а не `true`. Код + +```d +{ + ‹инструкции1› + scope(failure) ‹инструкция2› + ‹инструкции3› +} +``` + +превращается в + +```d +{ + ‹инструкции1› + bool __succeeded = true; + try + { + ‹инструкции3› + } + catch(Exception e) + { + __succeeded = false; + throw e; + } + finally + { + if (!__succeeded) ‹инструкция2› + } +} +``` + +Далее выполняется снижение инструкций `‹инструкция2›` и `‹инструкции3›`. + +Инструкция `scope` может пригодиться во многих ситуациях. Предположим, вы хотите создать файл способом транзакции – то есть не оставляя на диске «частично созданный» файл, если в процессе его создания произойдет сбой. Здесь можно поступить так: + +```d +import std.contracts, std.stdio; + +void transactionalCreate(string filename) +{ + string tempFilename = filename ~ ".fragment"; + scope(success) + { + std.file.rename(tempFilename, filename); + } + auto f = File(tempFilename, "w"); + ... // Спокойно пишете в f +} +``` + +Инструкция `scope(success)` заранее определяет цель работы функции. Эквивалентный код без `scope` получился бы гораздо более замысловатым; к тому же обычно программист слишком занят кодом, выполняющимся при ожидаемых условиях, чтобы найти время для обработки маловероятных ситуаций. Поэтому необходимо, чтобы язык максимально облегчал обработку ошибок. + +Большой плюс такого стиля программирования состоит в том, что весь код обработки ошибок собран в начале функции `transactionalCreate` и никак не затрагивает основной код. При всей своей простоте функция `transactionalCreate` очень надежна: вы получаете или готовый файл, или временный файл-фрагмент, но только не «битый» файл, который кажется нормальным. + +[В начало ⮍](#3-13-инструкция-scope) [Наверх ⮍](#3-инструкции) + +## 3.14. Инструкция synchronized + +Инструкция `synchronized` имеет следующий синтаксис: + +```d +synchronized (‹выражение1›, ‹выражение2›...) ‹инструкция› +``` + +С ее помощью можно расставлять контекстные блокировки в многопоточных программах. Семантика инструкции `synchronized` определена в главе 13. + +[В начало ⮍](#3-14-инструкция-synchronized) [Наверх ⮍](#3-инструкции) + +## 3.15. Конструкция asm + +D нарушил бы свой обет быть языком для системного программирования, если бы не предоставил некоторые средства для взаимодействия с ассемблером. И если вы любите трудности, то будете счастливы узнать, что в D есть тщательно определенный встроенный язык ассемблера для Intel x86. Кроме того, этот язык переносим между всеми реализациями D, работающими на машинах x86. Поскольку ассемблер зависит только от машины, а не от операционной системы, на первый взгляд это средство D не кажется революционным, тем не менее вы будете удивлены. Исторически сложилось, что каждая операционная система определяет собственный синтаксис ассемблера, не совместимый с другими ОС, поэтому, например, код, написанный для Windows, не будет работать под управлением Linux, так как синтаксисы ассемблеров этих операционных систем разительно отличаются друг от друга (что вряд ли оправданно). D разрубает этот гордиев узел, отказавшись от внешнего ассемблера, специфичного для каждой системы. Вместо этого компилятор сам выполняет синтаксический анализ и распознает инструкции ассемблерного языка. Чтобы написать код на ассемблере, делайте так: + +```d +asm ‹инструкция на ассемблере› +``` + +или так: + +```d +asm { ‹инструкции на ассемблере› } +``` + +Идентификаторы, видимые перед конструкцией `asm`, доступны и внутри нее: ассемблерный код может использовать сущности D. Ассемблер D описывается в главе 11; он покажется знакомым любому, кто работал с ассемблером x86. Всю информацию по ассемблеру D вы найдете в документации. + +[В начало ⮍](#3-15-конструкция-asm) [Наверх ⮍](#3-инструкции) + +## 3.16. Итоги и справочник + +D предоставляет все ожидаемые обычные инструкции, предлагая при этом и несколько новинок, таких как `static if`, `final switch` и `scope`. Таблица 3.1 – краткий справочник по всем инструкциям языка D (за подробностями обращайтесь к соответствующим разделам этой главы). + +*Таблица 3.1. Справочник по инструкциям (`‹и›` – инструкция, `‹в›` – выражение, `‹o›` – объявление, `‹х›` – идентификатор)* + +|Инструкция|Описание| +|-|-| +|`‹в›;`|Вычисляет `‹в›`. Ничего не изменяющие выражения, включающие лишь встроенные типы и операторы, запрещены ([см. раздел 3.1]())| +|`{‹и1› ... ‹и2›}`|Выполняет инструкции от `‹и1›` до `‹и2›` по порядку, пока управление не будет явно передано в другую область видимости (например, инструкцией `return`) ([см. раздел 3.2]())| +|`asm ‹и›`|Машиннозависимый ассемблерный код (здесь `‹и›` обозначает ассемблерный код, а не инструкцию на языке D). В настоящее время поддерживается ассемблер x86 с единым синтаксисом для всех поддерживаемых операционных систем ([см. раздел 3.15]())| +|`break;`|Прерывает выполнение инструкции `switch`, `for`, `foreach`, `while` или `do-while` с переходом к инструкции, следующей сразу за ней ([см. раздел 3.7.6]())| +|`break ‹x›;`|Прерывает выполнение инструкции `switch`, `for`, `foreach`, `while` или `do-while`, имеющей метку `‹x›:`, с переходом к инструкции, следующей сразу за ней ([см. раздел 3.7.6]())| +|`continue;`|Начинает новую итерацию текущего (ближайшего к ней) цикла `for`, `foreach`, `while` или `do-while` с пропуском оставшейся части этого цикла ([см. раздел 3.7.6]())| +|`continue ‹x›;`|Начинает новую итерацию цикла `for`, `foreach`, `while` или `do-while`, снабженного меткой `‹x›:`, с пропуском оставшейся части этого цикла ([см. раздел 3.7.6]())| +|`do ‹и› while (‹в›);`|Выполняет `‹и›` один раз и продолжает ее выполнять, пока `‹в›` истинно| +|`for (‹и1› ‹в1›; ‹в2›) ‹и2›`|Выполняет `‹и1›`, которая может быть инструкцией-выражением, определением значения или просто точкой с запятой, и пока `‹в1›` истинно, выполняет `‹и2›`, после чего вычисляет `‹в2›`| +|`foreach (‹x›; ‹в1› .. ‹в2›) ‹и›`|Выполняет `‹и›`, инициализируя переменную `‹x›` значением `‹в1›` и затем последовательно увеличивая ее на 1, пока `‹x› ‹ ‹в2›`. Цикл не выполняется, если `‹в1› ›= ‹в2›`. Как `‹в1›`, так и `‹в2›` вычисляются всего один раз ([см. раздел 3.7.4]())| +|`foreach (refопц ‹x›; ‹в›) ‹и›`|Выполняет `‹и›`, объявляя переменную `‹x›` и привязывая ее к каждому из элементов `‹в›` поочередно. Результатом вычисления `‹в›` должен быть массив или любой пользовательский тип-диапазон. Если присутствует ключевое слово `ref`, изменения `‹x›` будут отражаться и на просматриваемой сущности ([см. раздел 3.7.5]())| +|`foreach (‹x1›, refопц ‹x2›; ‹в›) ‹и›`|Аналогична предыдущей, но вводит дополнительное значение `‹x1›`. Если `‹в›` – это ассоциативный массив, то `‹x1›` привязывается к ключу, а `‹x2›` – к рассматриваемому значению. Иначе `‹x1›` привязывается к целому числу, показывающему количество проходов цикла (начиная с 0) ([см. раздел 3.7.5]())| +|`goto ‹x›;`|Выполняет переход к метке `‹x›`, которая должна быть определена в текущей функции как `‹x›:` ([см. раздел 3.8]())| +|`goto case;`|Выполняет переход к следующей метке `case` текущей инструкции `switch` ([см. раздел 3.8]())| +|`goto case ‹x›;`|Выполняет переход к метке `case ‹x›` текущей инструкции `switch` ([см. раздел 3.8]())| +|`goto default;`|Выполняет переход к метке обработчика по умолчанию `default` текущей инструкции `switch` ([см. раздел 3.8]())| +|`if (‹в›) ‹и›`|Выполняет `‹и›`, если `‹в›` ненулевое ([см. раздел 3.3]())| +|`if (‹в›) ‹и1› else ‹и2›`|Выполняет `‹и1›`, если `‹в›` ненулевое, иначе выполняет `‹и2›`. Компонент `else`, расположенный в конце, относится к последней инструкции `if` или `static if` ([см. раздел 3.3]())| +|`static if (‹в›)‹о/и›`|Вычисляет `‹в›` во время компиляции и, если `‹в›` ненулевое, компилирует объявление или инструкцию `‹о/и›`. Если объявление или инструкция `‹о/и›` заключены в `{` и `}`, то одна пара таких скобок срезается ([см. раздел 3.4]())| +|`static if (‹в›)‹о/и1› else ‹о/к2›`|Аналогична предыдущей плюс в случае ложности `‹в›` компилирует `‹о/и2›`. Часть `else`, расположенная в конце, относится к последней инструкции `if` или `static if` ([см. раздел 3.4]())| +|`return ‹в›опц;`|Возврат из текущей функции. Возвращаемое значение должно быть таким, чтобы его можно было неявно преобразовать к объявленному возвращаемому типу. `‹в›` может быть опущено, если возвращаемый тип функции – `void`| +|`scope(exit) ‹и›`|Выполняет `‹и›`, каким бы образом ни был осуществлен выход из текущего контекста (то есть с помощью `return`, из-за необработанной ошибки или по исключительной ситуации). Вложенные инструкции `scope` (в том числе с ключевыми словами `failure` и `success`) выполняются в порядке, обратном их определению в коде программы ([см. раздел 3.13]())| +|`scope(failure) ‹и›`|Выполняет `‹и›`, если выход из текущего контекста осуществлен по исключительной ситуации ([см. раздел 3.13]())| +|`scope(success) ‹и›`|Выполняет `‹и›` при нормальном выходе из текущего контекста (через `return` или по достижении конца контекста) ([см. раздел 3.13]())| +|`switch (‹в›) ‹и›`|Вычисляет `‹в›` и выполняет переход к метке `case`, соответствующей `‹в›` и расположенной внутри `‹и›` ([см. раздел 3.5]())| +|`final switch (‹в›) ‹и›`|Аналогична предыдущей, но работает только с перечисляемым и значениями и во время компиляции проверяет, обработаны ли все возможные значения с помощью меток `case` ([см. раздел 3.6]())| +|`synchronized (‹в1›, ‹в2›…)‹и›`|Выполняет `‹и›`, в то время как объекты, возвращаемые `‹в1›`, `‹в2›` и т.д., заблокированы. Выражения `‹вi›` должны возвращать объект типа `class` ([см. раздел 3.14]())| +|`throw (‹в›);`|Вычисляет `‹в›` и порождает соответствующее исключение с переходом в ближайший подходящий обработчик `catch`. `‹в›` должно иметь тип `Throwable` или наследующий от него ([см. раздел 3.11]())| +|`try ‹и› catch(‹Т1› ‹x1›) ‹и1› ... catch(‹Тn› ‹xn›) ‹иn› finally ‹иf›`|Выполняет `‹и›`. Если при этом возникает исключение, пытается сопоставить его тип с типами `‹Т1›`, `...`, `‹Тn›` по порядку. Если `k`-е сопоставление оказалось удачным, то далее сопоставления не производятся и выполняется `‹иk›`. В любом случае (завершилось выполнение `‹и›` исключением или нет) перед выходом из `try` выполняется `‹иf›`. Все компоненты `catch` и `finally` (но не то и другое одновременно) могут быть опущены ([см. раздел 3.11]())| +|`while (‹в›) ‹и›`|Выполняет `‹и›`, пока `‹в›` ненулевое (цикл не выполняется, если уже при первом вычислении `‹в›` оказывается нулевым)| +|`with (‹в›) ‹и›`|Вычисляет `‹в›`, затем выполняет `‹и›`, как если бы она была членом типа `‹в›`: все используемые в `‹и›` идентификаторы сначала ищутся в пространстве имен, определенном `‹в›` ([см. раздел 3.9]())| + +[В начало ⮍](#3-16-итоги-и-справочник) [Наверх ⮍](#3-инструкции) + [^1]: Да-да, это «еще одно место, где используется ключевое слово `static»`. [^2]: Тип `enum` будет рассмотрен позже. Для понимания примера надо знать, что значения объявленные как `enum`, определены на этапе компиляции, неизменны и могут использоваться в конструкциях, вычисляемых на этапе компиляции. – *Прим. науч. ред.* +[^3]: Существует также цикл `foreach_reverse`, который работает аналогично `foreach`, но перебирает значения в обратном порядке. +[^4]: Идентификаторы, начинающиеся с двух подчеркиваний, описаны в разделе 2.1. – *Прим. пер.* +[^5]: В стандартной библиотеке (STL) C++ для определения завершения цикла последовательно используется оператор `!=` на том основании, что (не)равенство – более общая форма сравнения, так как она применима к большему количеству типов. Подход D не менее общий, но при этом, когда это возможно, для повышения безопасности вычислений использует `<`, не проигрывая ни в обобщенности, ни в эффективности. +[^6]: LIFO – акроним «Last In – First Out» (последним пришел – первым ушел). – *Прим. пер.* +[^7]: CleanerUpper – «уборщик» (от англ. clean up – убирать, чистить). – *Прим. пер.*