3.4-3.6
This commit is contained in:
parent
235bd723c2
commit
c0dec81098
|
@ -1,11 +1,11 @@
|
||||||
# 3. Инструкции
|
# 3. Инструкции
|
||||||
|
|
||||||
- [3.1. Инструкция-выражение](#3-1-инструкция-выражение)
|
- [3.1. Инструкция-выражение](#3-1-инструкция-выражение)
|
||||||
- [3.2. Составная инструкция]()
|
- [3.2. Составная инструкция](#3-2-составная-инструкция)
|
||||||
- [3.3. Инструкция if]()
|
- [3.3. Инструкция if](#3-3-инструкция-if)
|
||||||
- [3.4. Инструкция static if]()
|
- [3.4. Инструкция static if](#3-4-инструкция-static-if)
|
||||||
- [3.5. Инструкция switch]()
|
- [3.5. Инструкция switch](#3-5-инструкция-switch)
|
||||||
- [3.6. Инструкция final switch]()
|
- [3.6. Инструкция final switch](#3-6-инструкция-final-switch)
|
||||||
- [3.7. Циклы]()
|
- [3.7. Циклы]()
|
||||||
- [3.7.1. Инструкция while (цикл с предусловием)]()
|
- [3.7.1. Инструкция while (цикл с предусловием)]()
|
||||||
- [3.7.2. Инструкция do-while (цикл с постусловием)]()
|
- [3.7.2. Инструкция do-while (цикл с постусловием)]()
|
||||||
|
@ -97,6 +97,8 @@ void main()
|
||||||
|
|
||||||
Такой подход объясняется просто. Возможность перекрывать глобальные идентификаторы необходима, чтобы писать качественный модульный код, который собирается из нескольких отдельно скомпилированных частей; вы же не хотите, чтобы добавленная в локальное пространство имен глобальная переменная внезапно спутала все карты, запретив компиляцию невинных локальных переменных. С другой стороны, перекрытие локальных идентификаторов бесполезно с точки зрения модульности (поскольку в D составная инструкция никогда не простирается на несколько модулей) и обычно указывает либо на недосмотр (который вот-вот превратится в ошибку), либо на злокачественную функцию, вышедшую из-под контроля.
|
Такой подход объясняется просто. Возможность перекрывать глобальные идентификаторы необходима, чтобы писать качественный модульный код, который собирается из нескольких отдельно скомпилированных частей; вы же не хотите, чтобы добавленная в локальное пространство имен глобальная переменная внезапно спутала все карты, запретив компиляцию невинных локальных переменных. С другой стороны, перекрытие локальных идентификаторов бесполезно с точки зрения модульности (поскольку в D составная инструкция никогда не простирается на несколько модулей) и обычно указывает либо на недосмотр (который вот-вот превратится в ошибку), либо на злокачественную функцию, вышедшую из-под контроля.
|
||||||
|
|
||||||
|
[В начало ⮍](#3-2-составная-инструкция) [Наверх ⮍](#3-инструкции)
|
||||||
|
|
||||||
## 3.3. Инструкция if
|
## 3.3. Инструкция if
|
||||||
|
|
||||||
Во многих примерах уже встречалась условная инструкция D `if`, которая очень похожа на то, чего вы могли от нее ожидать:
|
Во многих примерах уже встречалась условная инструкция D `if`, которая очень похожа на то, чего вы могли от нее ожидать:
|
||||||
|
@ -170,6 +172,8 @@ else
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[В начало ⮍](#3-3-инструкция-if) [Наверх ⮍](#3-инструкции)
|
||||||
|
|
||||||
## 3.4. Инструкция static if
|
## 3.4. Инструкция static if
|
||||||
|
|
||||||
Теперь, когда вы уже разогрелись на нескольких простых инструкциях (спасибо, что подавили этот зевок), можно взглянуть на нечто более необычное.
|
Теперь, когда вы уже разогрелись на нескольких простых инструкциях (спасибо, что подавили этот зевок), можно взглянуть на нечто более необычное.
|
||||||
|
@ -257,5 +261,132 @@ if (a)
|
||||||
else writeln("b равно нулю");
|
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»`.
|
[^1]: Да-да, это «еще одно место, где используется ключевое слово `static»`.
|
||||||
[^2]: Тип `enum` будет рассмотрен позже. Для понимания примера надо знать, что значения объявленные как `enum`, определены на этапе компиляции, неизменны и могут использоваться в конструкциях, вычисляемых на этапе компиляции. – *Прим. науч. ред.*
|
[^2]: Тип `enum` будет рассмотрен позже. Для понимания примера надо знать, что значения объявленные как `enum`, определены на этапе компиляции, неизменны и могут использоваться в конструкциях, вычисляемых на этапе компиляции. – *Прим. науч. ред.*
|
||||||
|
|
Loading…
Reference in New Issue