2.3.3.-2.3.5.

This commit is contained in:
Alexander Zhirov 2023-01-22 19:00:24 +03:00
parent ed90f97c42
commit 8ed1cd13fa
1 changed files with 146 additions and 6 deletions

View File

@ -16,12 +16,12 @@
- [2.3.1. l-значения и r-значения](#2-3-1-l-значения-и-r-значения) - [2.3.1. l-значения и r-значения](#2-3-1-l-значения-и-r-значения)
- [2.3.2. Неявные преобразования чисел](#2-3-2-неявные-преобразования-чисел) - [2.3.2. Неявные преобразования чисел](#2-3-2-неявные-преобразования-чисел)
- [2.3.2.1. Распространение интервала значений](#2-3-2-1-распространение-интервала-значений) - [2.3.2.1. Распространение интервала значений](#2-3-2-1-распространение-интервала-значений)
- [2.3.3. Типы числовых операций]() - [2.3.3. Типы числовых операций](#2-3-3-типы-числовых-операций)
- [2.3.4. Первичные выражения]() - [2.3.4. Первичные выражения](#2-3-4-первичные-выражения)
- [2.3.4.1. Выражение assert]() - [2.3.4.1. Выражение assert](#2-3-4-1-выражение-assert)
- [2.3.4.2. Выражение mixin]() - [2.3.4.2. Выражение mixin](#2-3-4-2-выражение-mixin)
- [2.3.4.3. Выражения is]() - [2.3.4.3. Выражения is](#2-3-4-3-выражения-is)
- [2.3.4.4. Выражения в круглых скобках]() - [2.3.4.4. Выражения в круглых скобках](#2-3-4-4-выражения-в-круглых-скобках)
- [2.3.5. Постфиксные операции]() - [2.3.5. Постфиксные операции]()
- [2.3.5.1. Доступ ко внутренним элементам]() - [2.3.5.1. Доступ ко внутренним элементам]()
- [2.3.5.2. Увеличение и уменьшение на единицу]() - [2.3.5.2. Увеличение и уменьшение на единицу]()
@ -580,6 +580,145 @@ void fun(int x)
[В начало ⮍](#2-3-2-1-распространение-интервала-значений) [Наверх ⮍](#2-основные-типы-данных-выражения) [В начало ⮍](#2-3-2-1-распространение-интервала-значений) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.3. Типы числовых операций
В следующих разделах представлены операторы, применимые к числовым типам. Тип значения как результат различных операций с числами определяется с помощью нескольких правил. Это не лучшие правила, которые можно было бы придумать, но они достаточно просты, единообразны и систематичны.
В результате унарной операции всегда получается тот же тип, что и у операнда, кроме случая с оператором отрицания `!` (см. раздел 2.3.6.6), применение которого всегда дает значения типа `bool`. Тип результата бинарных операций рассчитывается так:
- Если хотя бы один из операндов число с плавающей запятой, то тип результата наибольший из задействованных типов с плавающей запятой.
- Иначе если хотя бы один из операндов имеет тип `ulong`, то другой операнд до выполнения операции неявно преобразуется к типу `ulong` и результат также имеет тип `ulong`.
- Иначе если хотя бы один из операндов имеет тип `long`, то другой операнд до выполнения операции неявно преобразуется к типу `long` и результат также имеет тип `long`.
- Иначе если хотя бы один из операндов имеет тип `uint`, то другой операнд до выполнения операции неявно преобразуется к типу `uint` и результат также имеет тип `uint`.
- Иначе оба операнда до выполнения операции неявно преобразуются к типу `int` и результат имеет тип `int`.
Для всех неявных преобразований выбирается кратчайший путь (см. рис. 2.3). Это важная деталь. Например:
```d
ushort x = 60_000;
assert(x / 10 == 6000);
```
В операции деления 10 имеет тип `int` и в соответствии с указанными правилами `x` неявно преобразуется к типу `int` до выполнения операции. На рис. 2.3 есть несколько возможных путей, в том числе прямое преобразование `ushort``int` и более длинное (на один шаг) `ushort``short``int`. Второе нежелательно, так как преобразование числа 60 000 к типу `short` породит значение 5536, которое затем будет расширено до `int` и приведет инструкцию `assert` к ошибке. Выбор кратчайшего пути в графе преобразований помогает лучше защитить значение от порчи.
[В начало ⮍](#2-3-3-типы-числовых-операций) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.4. Первичные выражения
Первичные выражения элементарные частицы вычислений. Нам уже встречались идентификаторы (см. раздел 2.1), логические литералы `true` и `false` (см. раздел 2.2.1), целые литералы (см. раздел 2.2.2), литералы с плавающей запятой (см. раздел 2.2.3), знаковые литералы (см. раздел 2.2.4), строковые литералы (см. раздел 2.2.5), литералы массивов (см. раздел 2.2.6) и функциональные литералы (см. раздел 2.2.7); все это первичные выражения, так же как и литерал `null`. В следующих разделах описаны другие первичные подвыражения: `assert`, `mixin`, `is` и выражения в круглых скобках.
[В начало ⮍](#2-3-4-первичные-выражения) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.4.1. Выражение assert
Некоторые выражения и инструкции, включая `assert`, используют нотацию *ненулевых* значений. Эти значения могут: 1) иметь числовой или знаковый тип (в этом случае смысл термина «ненулевое значение» очевиден), 2) иметь логический тип («ненулевое значение» интерпретируется как `true`) или 3) быть массивом, ссылкой или указателем (и тогда «ненулевым значением» считается не-`null`).
Выражение `assert(выражение)` вычисляет `выражение`. Если результат ненулевой, ничего не происходит. В противном случае выражение `assert` порождает исключение типа `AssertError`. Форма вызова `assert(выражение, сообщение)` делает `сообщение` (которое должно быть приводимо к типу `string`) частью сообщения об ошибке, хранимого внутри объекта типа `AssertError` (`сообщение` не вычисляется, если `выражение` ненулевое). Во всех случаях собственный тип `assert void`.
Для сборки наиболее эффективного варианта программы компилятор D предоставляет специальный флаг (`-release` в случае эталонной реализации `dmd`), позволяющий игнорировать все выражения `assert` в компилируемом модуле (то есть вообще не вычислять `выражение`). Учитывая этот факт, к `assert` следует относиться как к инструменту отладки, а не как к средству проверки условий, поскольку оно может дать законный сбой. По той же причине некорректно использовать внутри выражений `assert` выражения с побочными эффектами, если поведение программы зависит от этих побочных эффектов. Более подробную информацию об итоговых сборках вы найдете в главе 11.
Ситуации, наподобие `assert(false)`, `assert(0)` и других, когда функция `assert` вызывается с заранее известным статическим нулевым значением, обрабатываются особым образом. Такие проверки всегда в силе (независимо от значений флагов компилятора) и порождают машинный код с инструкцией `HLT`, которая аварийно останавливает выполнение процесса. Такое прерывание может дать операционной системе подсказку сгенерировать дамп памяти или запустить отладчик с пометкой на виновной строке.
Предвосхищая рассказ о логических выражениях с логическим ИЛИ (см. раздел 2.3.15), упомянем простейшую концептуальную идею всегда вычислять выражение и гарантировать его результат с помощью конструкции `(выражение) || assert(false)`.
В главе 10 подробно обсуждаются механизмы обеспечения корректности программы, в том числе выражения `assert`.
[В начало ⮍](#2-3-4-1-выражение-assert) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.4.2. Выражение mixin
Если бы выражения были отвертками разных видов, выражение `mixin` было бы электрической отверткой со сменными насадками, регулятором скоростей, адаптером для операций на мозге, встроенной беспроводной камерой и функцией распознавания речи. Оно на самом деле *такое* мощное.
Короче говоря, выражение `mixin` позволяет вам превратить строку в исполняемый код. Синтаксис выражения выглядит как `mixin(выражение)`, где `выражение` должно быть строкой, известной во время компиляции. Это ограничение исключает возможность динамически создавать программный код, например читать строку с терминала и интерпретировать ее. Нет, D не интерпретируемый язык, и его компилятор не является частью средств стандартной библиотеки времени исполнения. Хорошие новости заключаются в том, что D на самом деле запускает полноценный интерпретатор *во время компиляции*, а значит, вы можете собирать строки настолько изощренными способами, насколько этого требуют условия вашей задачи.
Возможность манипулировать строками и преобразовывать их в код во время компиляции позволяет создавать так называемые предметно-ориентированные встроенные языки программирования, которые их фанаты любовно обозначают аббревиатурой DSEL[^18]. Типичный DSEL, реализованный на D, принимал бы инструкции в качестве строковых литералов, обрабатывал их в процессе компиляции, создавал соответствующий код на D в виде строки и с помощью `mixin` преобразовывал ее в готовый к исполнению код на D. Хорошим примером полезных DSEL могут служить SQL-команды, регулярные выражения и спецификации грамматик (а-ля `yacc`). На самом деле, даже вызывая `printf`, вы каждый раз используете DSEL. Спецификатор формата, применяемый функцией `printf`, это настоящий маленький язык, ориентированный на описание шаблонов для текстовых данных.
D позволяет вам создать какой угодно DSEL без дополнительных инструментов (таких как синтаксические анализаторы, сборщики, генераторы кода и т. д.); например, функция `bitfields` из стандартной библиотеки (модуль `std.bitmanip`) принимает определения битовых полей и генерирует оптимальный код на D для их чтения и записи, хотя сам язык не поддерживает битовые поля.
[В начало ⮍](#2-3-4-2-выражение-mixin) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.4.3. Выражения is
Выражения `is` отвечают на вопросы о типах («Существует ли тип `Widget`?» или «Наследует ли `Widget` от `Gadget`?») и являются важной частью мощного механизма интроспекции во время компиляции, реализованного в D. Все выражения `is` вычисляются во время компиляции и возвращают логическое значение. Как показано ниже, есть несколько видов выражения `is`.
**1.**
Выражения `is(Тип)` и `is(Тип Идентификатор)` проверяют, существует ли указанный `Тип`. Тип может быть недопустим или, гораздо чаще, просто не существует. Примеры:
```d
bool
a = is(int[]), // True, int[] допустимый тип
b = is(int[5]), // True, int[5] также допустимый тип
c = is(int[-3]), // False, размер массива задан неверно
d = is(Blah); // False (если тип с именем Blah не был определен)
```
Во всех случаях `Тип` должен быть записан корректно с точки зрения синтаксиса, даже если запись в целом лишена смысла; например, выражение `is([[]x[]])` породит ошибку во время компиляции, а не вернет значение `false`. Другими словами, вы можете наводить справки только о том, что синтаксически выглядит как тип.
Если присутствует `Идентификатор`, он становится псевдонимом типа `Тип` в случае истинности выражения `is`. Пока что неизвестная команда `static if` позволяет различать случаи истинности и ложности этого выражения. Подробное описание `static if` вы найдете в главе 3, но на самом деле все просто: `static if` вычисляет свое условие во время компиляции и позволяет компилировать вложенные в него инструкции, только если тестируемое выражение истинно.
```d
static if (is(Widget[100][100] ManyWidgets))
{
ManyWidgets lotsOfWidgets;
...
}
```
**2.**
Выражения `is(Тип1 == Тип2)` и `is(Тип1 Идентификатор == Тип2)` возвращают `True`, если `Тип1` и `Тип2` идентичны. (Они могут иметь различные имена в результате применения `alias`.)
```d
alias uint UInt;
assert(is(uint == UInt));
```
Если присутствует `Идентификатор`, он становится псевдонимом типа `Тип1` в случае истинности выражения `is`.
**3.**
Выражения `is(Тип1 : Тип2)` и `is(Тип1 Идентификатор : Тип2)` возвращают `True`, если `Тип1` идентичен или может быть неявно преобразован к типу `Тип2`. Например:
```d
bool
a = is(int[5] : int[]), // true, int[5] может быть преобразован к int[]
b = is(int[5] == int[]), // false; это разные типы
c = is(uint : long), // true
d = is(ulong : long); // true
```
Аналогично, если присутствует `Идентификатор`, он становится псевдонимом типа `Тип1` в случае истинности выражения `is`.
**4.**
Выражения `is(Тип == Вид)` и `is(Тип Идентификатор == Вид)` проверяют, принадлежит ли `Тип` к категории `Вид`. `Вид` это одно из следующих ключевых слов: `struct`, `union`, `class`, `interface`, `enum`, `function`, `delegate`, `super`, `const`, `immutable`, `inout`, `shared` и `return`. Выражение `is` истинно, если `Тип` соответствует указанному `Виду`. Если присутствует `Идентификатор`, он должен быть задан в зависимости от значения `Вид` (табл. 2.4).
*Таблица 2.4. Зависимости для значения `Идентификатор` в выражении `is(Тип Идентификатор == Вид)`*
|Вид|Идентификатор псевдоним для...|
|-|-|
|`struct`|`Тип`|
|`union`|`Тип`|
|`class`|`Тип`|
|`interface`|`Тип`|
|`enum`|Базовый тип перечисления|
|`function`|Кортеж типов аргументов функции|
|`delegate`|Функциональный тип `delegate`|
|`super`|Родительский класс|
|`const`|`Тип`|
|`immutable`|`Тип`|
|`inout`|`Тип`|
|`shared`|`Тип`|
|`return`|Тип, возвращаемый функцией, оператором `delegate` или указателем на функцию|
[В начало ⮍](#2-3-4-3-выражения-is) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.4.4. Выражения в круглых скобках
Круглые скобки переопределяют обычный порядок выполнения операций: для любых выражений, `(<выражение>)` обладает более высоким приоритетом, чем `<выражение>`.
[В начало ⮍](#2-3-4-4-выражения-в-круглых-скобках) [Наверх ⮍](#2-основные-типы-данных-выражения)
[^1]: Впрочем, использование нелатинских букв является дурным тоном. *Прим. науч. ред.* [^1]: Впрочем, использование нелатинских букв является дурным тоном. *Прим. науч. ред.*
[^2]: С99 обновленная спецификация C, в том числе добавляющая поддержку знаков Юникода. *Прим. пер.* [^2]: С99 обновленная спецификация C, в том числе добавляющая поддержку знаков Юникода. *Прим. пер.*
[^3]: Сам язык не поддерживает восьмеричные литералы, но поскольку они присутствуют в некоторых C-подобных языках, в стандартную библиотеку был добавлен соответствующий шаблон. Теперь запись `std.conv.octal!777` аналогична записи `0777` в C. *Прим. науч. ред.* [^3]: Сам язык не поддерживает восьмеричные литералы, но поскольку они присутствуют в некоторых C-подобных языках, в стандартную библиотеку был добавлен соответствующий шаблон. Теперь запись `std.conv.octal!777` аналогична записи `0777` в C. *Прим. науч. ред.*
@ -597,3 +736,4 @@ void fun(int x)
[^15]: Заключенное в 1989 году соглашение между коммунистами и демократами, ознаменовавшее собой достижение компромисса между двумя партиями. В данном случае также ищется «компромиссный» тип. *Прим. пер.* [^15]: Заключенное в 1989 году соглашение между коммунистами и демократами, ознаменовавшее собой достижение компромисса между двумя партиями. В данном случае также ищется «компромиссный» тип. *Прим. пер.*
[^16]: In situ (лат.) на месте. *Прим. пер.* [^16]: In situ (лат.) на месте. *Прим. пер.*
[^17]: От англ. left-value и right-value. *Прим. науч. ред.* [^17]: От англ. left-value и right-value. *Прим. науч. ред.*
[^18]: Domain-specific embedded language (DSEL) предметно-ориентированный встроенный язык. *Прим. пер.*