diff --git a/02-основные-типы-данных-выражения/README.md b/02-основные-типы-данных-выражения/README.md index 98c34d7..ee9f180 100644 --- a/02-основные-типы-данных-выражения/README.md +++ b/02-основные-типы-данных-выражения/README.md @@ -16,12 +16,12 @@ - [2.3.1. l-значения и r-значения](#2-3-1-l-значения-и-r-значения) - [2.3.2. Неявные преобразования чисел](#2-3-2-неявные-преобразования-чисел) - [2.3.2.1. Распространение интервала значений](#2-3-2-1-распространение-интервала-значений) - - [2.3.3. Типы числовых операций]() - - [2.3.4. Первичные выражения]() - - [2.3.4.1. Выражение assert]() - - [2.3.4.2. Выражение mixin]() - - [2.3.4.3. Выражения is]() - - [2.3.4.4. Выражения в круглых скобках]() + - [2.3.3. Типы числовых операций](#2-3-3-типы-числовых-операций) + - [2.3.4. Первичные выражения](#2-3-4-первичные-выражения) + - [2.3.4.1. Выражение assert](#2-3-4-1-выражение-assert) + - [2.3.4.2. Выражение mixin](#2-3-4-2-выражение-mixin) + - [2.3.4.3. Выражения is](#2-3-4-3-выражения-is) + - [2.3.4.4. Выражения в круглых скобках](#2-3-4-4-выражения-в-круглых-скобках) - [2.3.5. Постфиксные операции]() - [2.3.5.1. Доступ ко внутренним элементам]() - [2.3.5.2. Увеличение и уменьшение на единицу]() @@ -580,6 +580,145 @@ void fun(int x) [В начало ⮍](#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]: Впрочем, использование нелатинских букв является дурным тоном. – *Прим. науч. ред.* [^2]: С99 – обновленная спецификация C, в том числе добавляющая поддержку знаков Юникода. – *Прим. пер.* [^3]: Сам язык не поддерживает восьмеричные литералы, но поскольку они присутствуют в некоторых C-подобных языках, в стандартную библиотеку был добавлен соответствующий шаблон. Теперь запись `std.conv.octal!777` аналогична записи `0777` в C. – *Прим. науч. ред.* @@ -597,3 +736,4 @@ void fun(int x) [^15]: Заключенное в 1989 году соглашение между коммунистами и демократами, ознаменовавшее собой достижение компромисса между двумя партиями. В данном случае также ищется «компромиссный» тип. – *Прим. пер.* [^16]: In situ (лат.) – на месте. – *Прим. пер.* [^17]: От англ. left-value и right-value. – *Прим. науч. ред.* +[^18]: Domain-specific embedded language (DSEL) – предметно-ориентированный встроенный язык. – *Прим. пер.*