|
@@ -1,11 +1,11 @@
|
|
|
# 3. Инструкции
|
|
|
|
|
|
- [3.1. Инструкция-выражение](#3-1-инструкция-выражение)
|
|
|
-- [3.2. Составная инструкция]()
|
|
|
-- [3.3. Инструкция if]()
|
|
|
-- [3.4. Инструкция static if]()
|
|
|
-- [3.5. Инструкция switch]()
|
|
|
-- [3.6. Инструкция final switch]()
|
|
|
+- [3.2. Составная инструкция](#3-2-составная-инструкция)
|
|
|
+- [3.3. Инструкция if](#3-3-инструкция-if)
|
|
|
+- [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 (цикл с постусловием)]()
|
|
@@ -97,6 +97,8 @@ void main()
|
|
|
|
|
|
Такой подход объясняется просто. Возможность перекрывать глобальные идентификаторы необходима, чтобы писать качественный модульный код, который собирается из нескольких отдельно скомпилированных частей; вы же не хотите, чтобы добавленная в локальное пространство имен глобальная переменная внезапно спутала все карты, запретив компиляцию невинных локальных переменных. С другой стороны, перекрытие локальных идентификаторов бесполезно с точки зрения модульности (поскольку в D составная инструкция никогда не простирается на несколько модулей) и обычно указывает либо на недосмотр (который вот-вот превратится в ошибку), либо на злокачественную функцию, вышедшую из-под контроля.
|
|
|
|
|
|
+[В начало ⮍](#3-2-составная-инструкция) [Наверх ⮍](#3-инструкции)
|
|
|
+
|
|
|
## 3.3. Инструкция if
|
|
|
|
|
|
Во многих примерах уже встречалась условная инструкция D `if`, которая очень похожа на то, чего вы могли от нее ожидать:
|
|
@@ -170,6 +172,8 @@ else
|
|
|
}
|
|
|
```
|
|
|
|
|
|
+[В начало ⮍](#3-3-инструкция-if) [Наверх ⮍](#3-инструкции)
|
|
|
+
|
|
|
## 3.4. Инструкция static if
|
|
|
|
|
|
Теперь, когда вы уже разогрелись на нескольких простых инструкциях (спасибо, что подавили этот зевок), можно взглянуть на нечто более необычное.
|
|
@@ -257,5 +261,132 @@ if (a)
|
|
|
else writeln("b равно нулю");
|
|
|
```
|
|
|
|
|
|
+[В начало ⮍](#3-4-инструкция-static-if) [Наверх ⮍](#3-инструкции)
|
|
|
+
|
|
|
+## 3.5. Инструкция switch
|
|
|
+
|
|
|
+Лучше всего сразу проиллюстрировать работу инструкции `switch` примером:
|
|
|
+
|
|
|
+```d
|
|
|
+import std.stdio;
|
|
|
+
|
|
|
+void classify(char c)
|
|
|
+{
|
|
|
+ write("Вы передали ");
|
|
|
+ switch (c)
|
|
|
+ {
|
|
|
+ case '#':
|
|
|
+ writeln("знак решетки.");
|
|
|
+ break;
|
|
|
+ case '0': .. case '9':
|
|
|
+ writeln("цифру.");
|
|
|
+ break;
|
|
|
+ case 'A': .. case 'Z': case 'a': .. case 'z':
|
|
|
+ writeln("ASCII-знак.");
|
|
|
+ break;
|
|
|
+ case '.', ',', ':', ';', '!', '?':
|
|
|
+ writeln("знак препинания.");
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ writeln("всем знакам знак!");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+В общем виде инструкция `switch` выглядит так:
|
|
|
+
|
|
|
+```d
|
|
|
+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`: Перейти сюда, если никакой другой переход невозможен.
|
|
|
+
|
|
|
+`‹выражение›` вычисляется один раз для всех этих проверок. Выражение в каждой метке `case` – это любое не противоречащее правилам языка выражение, которое можно проверить на равенство выражению `‹выражение›`, а также на неравенство в случае использования синтаксиса с интервалом. Обычно `case`-выражения представлены константами, вычисляемыми во время компиляции, но D разрешает использовать и переменные, гарантируя, что вычисления будут производиться в порядке следования альтернатив до первого совпадения. По завершении вычислений выполняется переход к соответствующей метке `case` или `default` и выполнение программы продолжается из этой точки. Для того чтобы покинуть ветвление, используется инструкция break, осуществляющая выход из инструкции `switch`. В отличие от языков C и C++, D запрещает неявный переход к следующей метке и требует инструкции `break` или `return` после кода, соответствующего метке.
|
|
|
+
|
|
|
+```d
|
|
|
+switch (s)
|
|
|
+{
|
|
|
+ case 'a': writeln("a"); // Вывести "a" и перейти к следующей метке
|
|
|
+ case 'b': writeln("b"); // Ошибка! Неявный переход запрещен!
|
|
|
+ default: break;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Если вы действительно хотите, чтобы после кода метки `'a'` выполнился код метки `'b'`, вам придется явно указать это компилятору с помощью особой формы инструкции `goto`:
|
|
|
+
|
|
|
+```d
|
|
|
+switch (s)
|
|
|
+{
|
|
|
+ case 'a': writeln("a"); goto case; // Вывести "a" и перейти к следующей метке
|
|
|
+ case 'b': writeln("b"); // После выполнения 'a' мы попадем сюда
|
|
|
+ default: break;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Если же вы случайно забыли написать `break` или `return`, компилятор любезно напомнит вам об этом. Можно было бы вообще отказаться от использования инструкции `break` в конструкции `switch`, но это нарушило бы обязательство компилировать C-подобный код по правилам языка C либо не компилировать его вообще.
|
|
|
+
|
|
|
+Для меток, вычисляемых во время компиляции, действует запрет: вычисленные значения не должны пересекаться. Пример некорректного кода:
|
|
|
+
|
|
|
+```d
|
|
|
+switch (s)
|
|
|
+{
|
|
|
+ case 'a' .. case 'z': ... break;
|
|
|
+ // Попытка задать особую обработку для 'w'
|
|
|
+ case 'w': ... break; // Ошибка! Case-метки не могут пересекаться!
|
|
|
+ default: break;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Метка `default` должна быть обязательно объявлена. Если она не объявлена, компилятор сообщит об ошибке. Это сделано для того, чтобы предотвратить типичную для программистов ошибку – пропуск некоторого подмножества значений по недосмотру. Если такой опасности не существует, используйте `default: break;`, таким образом, аккуратно оформив ваше предположение. В следующем разделе описано, как статически гарантировать обработку всех возможных значений `switch`-условия.
|
|
|
+
|
|
|
+[В начало ⮍](#3-5-инструкция-switch) [Наверх ⮍](#3-инструкции)
|
|
|
+
|
|
|
+## 3.6. Инструкция final switch
|
|
|
+
|
|
|
+Инструкция `switch` обычно используется в связке с перечисляемым типом для обработки каждого из всех его возможных значений. Если во время эксплуатации число вариантов меняется, все зависимые переключатели неожиданно перестают соответствовать новому положению дел; каждую такую инструкцию необходимо вручную найти и изменить.
|
|
|
+
|
|
|
+Теперь очевидно, что для получения масштабируемого решения следует заменить «переключение» на основе меток виртуальными функциями; в этом случае нет необходимости обрабатывать различные случаи в одном месте, но вместо этого обработка распределяется по разным реализациям интерфейса. Но в жизни не бывает все идеально: определение интерфейсов и классов требует серьезных усилий на начальном этапе работы над программой, чего можно избежать, остановившись на альтернативном решении с переключателем `switch`. В таких ситуациях может пригодиться инструкция `final switch`, статически «принуждающая» метки `case` покрывать все возможные значения перечисляемого типа:
|
|
|
+
|
|
|
+```d
|
|
|
+enum DeviceStatus { ready, busy, fail }
|
|
|
+...
|
|
|
+void process(DeviceStatus status)
|
|
|
+{
|
|
|
+ final switch (status)
|
|
|
+ {
|
|
|
+ case DeviceStatus.ready:
|
|
|
+ ...
|
|
|
+ case DeviceStatus.busy:
|
|
|
+ ...
|
|
|
+ case DeviceStatus.fail:
|
|
|
+ ...
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Предположим, что при эксплуатации кода было добавлено еще одно возможное состояние устройства:
|
|
|
+
|
|
|
+```d
|
|
|
+enum DeviceStatus { ready, busy, fail, initializing /* добавлено */ }
|
|
|
+```
|
|
|
+
|
|
|
+После этого изменения попытка перекомпилировать функцию `process` будет встречена отказом на следующем основании:
|
|
|
+
|
|
|
+```sh
|
|
|
+Error: final switch statement must handle all values
|
|
|
+```
|
|
|
+
|
|
|
+*(Ошибка: инструкция final switch должна обрабатывать все значения)*
|
|
|
+
|
|
|
+Инструкция `final switch` требует, чтобы все значения типа `enum` были явно обработаны. Метки с интервалами вида `case ‹в1›: .. case ‹в2›:`, а также метку `default`: использовать запрещено.
|
|
|
+
|
|
|
+[В начало ⮍](#3-6-инструкция-final-switch) [Наверх ⮍](#3-инструкции)
|
|
|
+
|
|
|
[^1]: Да-да, это «еще одно место, где используется ключевое слово `static»`.
|
|
|
[^2]: Тип `enum` будет рассмотрен позже. Для понимания примера надо знать, что значения объявленные как `enum`, определены на этапе компиляции, неизменны и могут использоваться в конструкциях, вычисляемых на этапе компиляции. – *Прим. науч. ред.*
|