3.6-3.16
This commit is contained in:
parent
c0dec81098
commit
3f69cd0457
|
@ -6,22 +6,22 @@
|
||||||
- [3.4. Инструкция static if](#3-4-инструкция-static-if)
|
- [3.4. Инструкция static if](#3-4-инструкция-static-if)
|
||||||
- [3.5. Инструкция switch](#3-5-инструкция-switch)
|
- [3.5. Инструкция switch](#3-5-инструкция-switch)
|
||||||
- [3.6. Инструкция final switch](#3-6-инструкция-final-switch)
|
- [3.6. Инструкция final switch](#3-6-инструкция-final-switch)
|
||||||
- [3.7. Циклы]()
|
- [3.7. Циклы](#3-7-циклы)
|
||||||
- [3.7.1. Инструкция while (цикл с предусловием)]()
|
- [3.7.1. Инструкция while (цикл с предусловием)](#3-7-1-инструкция-while-цикл-с-предусловием)
|
||||||
- [3.7.2. Инструкция do-while (цикл с постусловием)]()
|
- [3.7.2. Инструкция do-while (цикл с постусловием)](#3-7-2-инструкция-do-while-цикл-с-постусловием)
|
||||||
- [3.7.3. Инструкция for (цикл со счетчиком)]()
|
- [3.7.3. Инструкция for (цикл со счетчиком)](#3-7-3-инструкция-for-цикл-со-счетчиком)
|
||||||
- [3.7.4. Инструкция foreach (цикл просмотра)]()
|
- [3.7.4. Инструкция foreach (цикл просмотра)](#3-7-4-инструкция-foreach-цикл-просмотра)
|
||||||
- [3.7.5. Цикл просмотра для работы с массивами]()
|
- [3.7.5. Цикл просмотра для работы с массивами](#3-7-5-цикл-просмотра-для-работы-с-массивами)
|
||||||
- [3.7.6. Инструкции continue и break]()
|
- [3.7.6. Инструкции continue и break](#3-7-6-инструкции-continue-и-break)
|
||||||
- [3.8. Инструкция goto (безусловный переход)]()
|
- [3.8. Инструкция goto (безусловный переход)](#3-8-инструкция-goto-безусловный-переход)
|
||||||
- [3.9. Инструкция with]()
|
- [3.9. Инструкция with](#3-9-инструкция-with)
|
||||||
- [3.10. Инструкция return]()
|
- [3.10. Инструкция return](#3-10-инструкция-return)
|
||||||
- [3.11. Обработка исключительных ситуаций]()
|
- [3.11. Обработка исключительных ситуаций](#3-11-обработка-исключительных-ситуаций)
|
||||||
- [3.12. Инструкция mixin]()
|
- [3.12. Инструкция mixin](#3-12-инструкция-mixin)
|
||||||
- [3.13. Инструкция scope]()
|
- [3.13. Инструкция scope](#3-13-инструкция-scope)
|
||||||
- [3.14. Инструкция synchronized]()
|
- [3.14. Инструкция synchronized](#3-14-инструкция-synchronized)
|
||||||
- [3.15. Конструкция asm]()
|
- [3.15. Конструкция asm](#3-15-конструкция-asm)
|
||||||
- [3.16. Итоги и справочник]()
|
- [3.16. Итоги и справочник](#3-16-итоги-и-справочник)
|
||||||
|
|
||||||
Эта глава содержит обязательные определения всех инструкций языка D. D наследует внешний вид и функциональность языков семейства C – в нем есть привычные инструкции `if`, `while`, `for` и другие. Наряду с этим D предлагает ряд новых интересных инструкций и некоторое усовершенствование старых. Если неизбежное перечисление с подробным описанием каждой инструкции заранее нагоняет на вас скуку, то вот вам несколько «отступлений» – любопытных отличий D от других языков.
|
Эта глава содержит обязательные определения всех инструкций языка D. D наследует внешний вид и функциональность языков семейства C – в нем есть привычные инструкции `if`, `while`, `for` и другие. Наряду с этим D предлагает ряд новых интересных инструкций и некоторое усовершенствование старых. Если неизбежное перечисление с подробным описанием каждой инструкции заранее нагоняет на вас скуку, то вот вам несколько «отступлений» – любопытных отличий D от других языков.
|
||||||
|
|
||||||
|
@ -302,10 +302,10 @@ switch (‹выражение›) ‹инструкция›
|
||||||
|
|
||||||
`‹выражение›` может иметь числовой, перечисляемый или строковый тип; `‹инструкция›` может содержать метки (ярлыки, labels), определенные следующим образом:
|
`‹выражение›` может иметь числовой, перечисляемый или строковый тип; `‹инструкция›` может содержать метки (ярлыки, labels), определенные следующим образом:
|
||||||
|
|
||||||
1. `case ‹в›`: Перейти сюда, если `‹выражение› == ‹в›`. Чтобы можно было использовать внутри `в` запятые (см. раздел 2.3.18), все выражение требуется заключить в круглые скобки.
|
1. `case ‹в›`: перейти сюда, если `‹выражение› == ‹в›`. Чтобы можно было использовать внутри `в` запятые (см. раздел 2.3.18), все выражение требуется заключить в круглые скобки.
|
||||||
2. `case ‹в1›, ‹в2›, … , ‹вn›`: Каждая запись вида ‹вk› обозначает выражение. Рассматриваемая инструкция эквивалентна инструкции `case ‹элемент1›: case ‹элемент2›:, ... , case ‹элементn›:`.
|
2. `case ‹в1›, ‹в2›, … , ‹вn›`: каждая запись вида `‹вk›` обозначает выражение. Рассматриваемая инструкция эквивалентна инструкции `case ‹элемент1›: case ‹элемент2›:, ... , case ‹элементn›:`.
|
||||||
3. `case ‹в1›: .. case ‹в2›`: Перейти сюда, если `‹выражение› >= ‹в1›` и `‹выражение› <= ‹в2›`.
|
3. `case ‹в1›: .. case ‹в2›`: перейти сюда, если `‹выражение› >= ‹в1›` и `‹выражение› <= ‹в2›`.
|
||||||
4. `default`: Перейти сюда, если никакой другой переход невозможен.
|
4. `default`: перейти сюда, если никакой другой переход невозможен.
|
||||||
|
|
||||||
`‹выражение›` вычисляется один раз для всех этих проверок. Выражение в каждой метке `case` – это любое не противоречащее правилам языка выражение, которое можно проверить на равенство выражению `‹выражение›`, а также на неравенство в случае использования синтаксиса с интервалом. Обычно `case`-выражения представлены константами, вычисляемыми во время компиляции, но D разрешает использовать и переменные, гарантируя, что вычисления будут производиться в порядке следования альтернатив до первого совпадения. По завершении вычислений выполняется переход к соответствующей метке `case` или `default` и выполнение программы продолжается из этой точки. Для того чтобы покинуть ветвление, используется инструкция break, осуществляющая выход из инструкции `switch`. В отличие от языков C и C++, D запрещает неявный переход к следующей метке и требует инструкции `break` или `return` после кода, соответствующего метке.
|
`‹выражение›` вычисляется один раз для всех этих проверок. Выражение в каждой метке `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-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»`.
|
[^1]: Да-да, это «еще одно место, где используется ключевое слово `static»`.
|
||||||
[^2]: Тип `enum` будет рассмотрен позже. Для понимания примера надо знать, что значения объявленные как `enum`, определены на этапе компиляции, неизменны и могут использоваться в конструкциях, вычисляемых на этапе компиляции. – *Прим. науч. ред.*
|
[^2]: Тип `enum` будет рассмотрен позже. Для понимания примера надо знать, что значения объявленные как `enum`, определены на этапе компиляции, неизменны и могут использоваться в конструкциях, вычисляемых на этапе компиляции. – *Прим. науч. ред.*
|
||||||
|
[^3]: Существует также цикл `foreach_reverse`, который работает аналогично `foreach`, но перебирает значения в обратном порядке.
|
||||||
|
[^4]: Идентификаторы, начинающиеся с двух подчеркиваний, описаны в разделе 2.1. – *Прим. пер.*
|
||||||
|
[^5]: В стандартной библиотеке (STL) C++ для определения завершения цикла последовательно используется оператор `!=` на том основании, что (не)равенство – более общая форма сравнения, так как она применима к большему количеству типов. Подход D не менее общий, но при этом, когда это возможно, для повышения безопасности вычислений использует `<`, не проигрывая ни в обобщенности, ни в эффективности.
|
||||||
|
[^6]: LIFO – акроним «Last In – First Out» (последним пришел – первым ушел). – *Прим. пер.*
|
||||||
|
[^7]: CleanerUpper – «уборщик» (от англ. clean up – убирать, чистить). – *Прим. пер.*
|
||||||
|
|
Loading…
Reference in New Issue