Alexander Zhirov 2 years ago
parent
commit
c0dec81098
1 changed files with 136 additions and 5 deletions
  1. 136 5
      03-инструкция/README.md

+ 136 - 5
03-инструкция/README.md

@@ -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`, определены на этапе компиляции, неизменны и могут использоваться в конструкциях, вычисляемых на этапе компиляции. – *Прим. науч. ред.*