Compare commits

...

2 Commits

Author SHA1 Message Date
Alexander Zhirov 42b2af19c0 2.3.6-2.4 2023-01-22 21:27:18 +03:00
Alexander Zhirov 79765a3d33 2.3.5.-2.3.6 2023-01-22 19:15:40 +03:00
1 changed files with 541 additions and 31 deletions

View File

@ -22,37 +22,37 @@
- [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. Увеличение и уменьшение на единицу]()
- [2.3.5.3. Вызов функции]()
- [2.3.5.4. Индексация]()
- [2.3.5.5. Срезы массивов]()
- [2.3.5.6. Создание вложенного класса]()
- [2.3.6. Унарные операции]()
- [2.3.6.1. Выражение new]()
- [2.3.6.2. Получение адреса и разыменование]()
- [2.3.6.3. Увеличение и уменьшение на единицу (префиксный вариант)]()
- [2.3.6.4. Поразрядное отрицание]()
- [2.3.6.5. Унарный плюс и унарный минус]()
- [2.3.6.6. Отрицание]()
- [2.3.6.7. Приведение типов]()
- [2.3.7. Возведение в степень]()
- [2.3.8. Мультипликативные операции]()
- [2.3.9. Аддитивные операции]()
- [2.3.10. Сдвиг]()
- [2.3.11. Выражения in]()
- [2.3.12. Сравнение]()
- [2.3.12.1. Проверка на равенство]()
- [2.3.12.2. Сравнение для упорядочивания]()
- [2.3.12.3. Неассоциативность]()
- [2.3.13. Поразрядные ИЛИ, ИСКЛЮЧАЮЩЕЕ ИЛИ и И]()
- [2.3.14. Логическое И]()
- [2.3.15. Логическое ИЛИ]()
- [2.3.16. Тернарная условная операция]()
- [2.3.17. Присваивание]()
- [2.3.18. Выражения с запятой]()
- [2.4. Итоги и справочник]()
- [2.3.5. Постфиксные операции](#2-3-5-постфиксные-операции)
- [2.3.5.1. Доступ ко внутренним элементам](#2-3-5-1-доступ-ко-внутренним-элементам)
- [2.3.5.2. Увеличение и уменьшение на единицу](#2-3-5-2-увеличение-и-уменьшение-на-единицу)
- [2.3.5.3. Вызов функции](#2-3-5-3-вызов-функции)
- [2.3.5.4. Индексация](#2-3-5-4-индексация)
- [2.3.5.5. Срезы массивов](#2-3-5-5-срезы-массивов)
- [2.3.5.6. Создание вложенного класса](#2-3-5-6-создание-вложенного-класса)
- [2.3.6. Унарные операции](#2-3-6-унарные-операции)
- [2.3.6.1. Выражение new](#2-3-6-1-выражение-new)
- [2.3.6.2. Получение адреса и разыменование](#2-3-6-2-получение-адреса-и-разыменование)
- [2.3.6.3. Увеличение и уменьшение на единицу (префиксный вариант)](#2-3-6-3-увеличение-и-уменьшение-на-единицу-префиксный-вариант)
- [2.3.6.4. Поразрядное отрицание](#2-3-6-4-поразрядное-отрицание)
- [2.3.6.5. Унарный плюс и унарный минус](#2-3-6-5-унарный-плюс-и-унарный-минус)
- [2.3.6.6. Отрицание](#2-3-6-6-отрицание)
- [2.3.6.7. Приведение типов](#2-3-6-7-приведение-типов)
- [2.3.7. Возведение в степень](#2-3-7-возведение-в-степень)
- [2.3.8. Мультипликативные операции](#2-3-8-мультипликативные-операции)
- [2.3.9. Аддитивные операции](#2-3-9-аддитивные-операции)
- [2.3.10. Сдвиг](#2-3-10-сдвиг)
- [2.3.11. Выражения in](#2-3-11-выражения-in)
- [2.3.12. Сравнение](#2-3-12-сравнение)
- [2.3.12.1. Проверка на равенство](#2-3-12-1-проверка-на-равенство)
- [2.3.12.2. Сравнение для упорядочивания](#2-3-12-2-сравнение-для-упорядочивания)
- [2.3.12.3. Неассоциативность](#2-3-12-3-неассоциативность)
- [2.3.13. Поразрядные ИЛИ, ИСКЛЮЧАЮЩЕЕ ИЛИ и И](#2-3-13-поразрядные-или-исключающее-или-и-и)
- [2.3.14. Логическое И](#2-3-14-логическое-и)
- [2.3.15. Логическое ИЛИ](#2-3-15-логическое-или)
- [2.3.16. Тернарная условная операция](#2-3-16-тернарная-условная-операция)
- [2.3.17. Присваивание](#2-3-17-присваивание)
- [2.3.18. Выражения с запятой](#2-3-18-выражения-с-запятой)
- [2.4. Итоги и справочник](#2-4-итоги-и-справочник)
Если вы когда-нибудь программировали на C, C++, Java или C#, то с основными типами данных и выражениями D у вас не будет никаких затруднений. Операции со значениями основных типов неотъемлемая часть решений многих задач программирования. Эти средства языка, в зависимости от ваших предпочтений, могут сильно облегчать либо отравлять вам жизнь. Совершенного подхода не существует; нередко поставленные цели противоречат друг другу, заставляя руководствоваться собственным субъективным мнением. Это, в свою очередь, лишает язык возможности угодить всем до единого. Слишком строгая система обременяет программиста своими запретами: он вынужден бороться с компилятором, чтобы тот принял простейшие выражения. А сделай систему типизации чересчур снисходительной и не заметишь, как окажешься по ту сторону корректности, эффективности или того и другого вместе.
@ -719,6 +719,515 @@ bool
[В начало ⮍](#2-3-4-4-выражения-в-круглых-скобках) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.5. Постфиксные операции
#### 2.3.5.1. Доступ ко внутренним элементам
Оператор доступа ко внутренним элементам `a.b` предоставляет доступ к элементу с именем `b`, расположенному внутри объекта или типа `a`. Если `a` сложное значение или сложный тип, допустимо заключить его в круглые скобки. В качестве `b` также может выступать выражение с ключевым словом `new` (см. главу 6).
[В начало ⮍](#2-3-5-1-доступ-ко-внутренним-элементам) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.5.2. Увеличение и уменьшение на единицу
Постфиксный вариант операции увеличения и уменьшения на единицу (`значение++` и `значение--` соответственно) определен для всех числовых типов и указателей и имеет тот же смысл, что и одноименная операция в C и C++: применение этой операции увеличивает или уменьшает на единицу `значение` (которое должно быть l-значением), возвращая копию этого значения до его изменения. (Аналогичный префиксный вариант операции увеличения и уменьшения на единицу описан в разделе 2.3.6.3.)
[В начало ⮍](#2-3-5-2-увеличение-и-уменьшение-на-единицу) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.5.3. Вызов функции
Уже знакомый оператор вызова функции `fun()` инициирует выполнение кода функции `fun`. Синтаксис `fun(<список аргументов, разделенных запятыми>)` передает в тело `fun` список аргументов. Все аргументы вычисляются слева направо перед вызовом `fun`. Количество и типы значений в списке аргументов должны соответствовать количеству и типам формальных параметров. Если функция определена с атрибутом `@property`, то указание просто имени функции эквивалентно вызову этой функции без аргументов. Обычно `fun` это имя функции, указанное в ее определении, но может быть и функциональным литералом (см. раздел 2.2.7) или выражением, возвращающим указатель на функцию или `delegate`. Подробно функции описаны в главе 5.
[В начало ⮍](#2-3-5-3-вызов-функции) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.5.4. Индексация
Выражение `arr[i]` позволяет получить доступ к `i`-му элементу массива или ассоциативного массива `arr` (элементы массива индексируются начиная с 0). Если массив неассоциативный, то значение `i` должно быть целым. Иначе значение `i` должно иметь тип, который может быть неявно преобразован к типу ключа массива `arr`. Если индексирующее выражение находится слева от оператора присваивания (например, `arr[i] = e`) и `arr` ассоциативный массив, выполняется вставка элемента в массив, если его там не было. Иначе если i относится к элементу, которого нет в массиве `arr`, выражение порождает исключение типа `RangeError`. В качестве `arr` и `i` также могут выступать указатель и целое соответственно. Операции индексации с помощью указателей автоматически не проверяются. В некоторых режимах сборки (небезопасные итоговые сборки; см. раздел 4.1.2) отменяется проверка границ и в случае неассоциативных массивов.
[В начало ⮍](#2-3-5-4-индексация) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.5.5. Срезы массивов
Если `arr` линейный (неассоциативный) массив, выражение `arr[i .. j]` возвращает массив, ссылающийся на интервал внутри `arr` от `i`-го до `j`-го элемента (не включая последний). Значения `i` и `j`, отмечающие границы среза, должны допускать неявное преобразование в целое. Выражение `arr[]` позволяет адресовать срез массива величиной в целый массив `arr`. Данные не копируются «по-настоящему», поэтому изменение среза массива влечет к изменению содержимого исходного массива `arr`. Например:
```d
int[] a = new int[5]; // Создать массив из пяти целых чисел
int[] b = a[3 .. 5]; // b ссылается на два последних элемента a
b[0] = 1;
b[1] = 3;
assert(a == [ 0, 0, 0, 1, 3 ]); // a был изменен
```
Если `i > j` или `j > a.length`, генерируется исключение типа `RangeError`. Иначе если `i == j`, будет возвращен пустой массив. В качестве `arr` в выражении `arr[i .. j]` можно использовать указатель. В этом случае будет возвращен массив, отражающий область памяти начиная с адреса `arr + i` до `arr + j` (не включая элемент с адресом `arr + j`). Если `i > j`, генерируется ошибка `RangeError`, иначе при получении среза указателя границы не проверяются. И снова в некоторых режимах сборки (небезопасные итоговые сборки, см. раздел 4.1.2) все проверки границ при получении срезов могут быть отключены.
[В начало ⮍](#2-3-5-5-срезы-массивов) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.5.6. Создание вложенного класса
Выражение вида `a.new T`, где a значение типа `class`, создает объект типа `T`, чье определение вложено в определение `a`. Что-то непонятно? Это потому что мы еще не определили ни классы, ни вложенные классы, и даже сами выражения `new` пока не рассмотрели. Определение выражения `new` уже совсем близко (в разделе 2.3.6.1), а чтобы познакомиться с определениями классов и вложенных классов, придется подождать до главы 6 (точнее до раздела 6.11). А до тех пор считайте этот раздел просто заглушкой, необходимой для целостности изложения.
[В начало ⮍](#2-3-5-6-создание-вложенного-класса) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.6. Унарные операции
#### 2.3.6.1. Выражение new
Допустимы несколько вариантов выражения new:
```d
new (‹адрес›)опционально ‹Тип›
new (‹адрес›)опционально ‹Тип›(список_аргументовопционально)
new (‹адрес›)опционально ‹Тип›[список_аргументов]
new (‹адрес›)опционально ‹Анонимный класс›
```
Забудем на время про необязательный `(<адрес>)`. Два первых варианта `new T` и `new T(<список_аргументов>опционально)` динамически выделяют память для объекта типа `T`. Второй вариант позволяет передать аргументы конструктору `T`. (Формы `new T` и `new T()` тождественны друг другу и создают объект, инициализированный по умолчанию.) Мы пока что не рассматривали типы с конструкторами, поэтому давайте отложим этот разговор до главы 6, предоставляющей подробную информацию о классах (см. раздел 6.3), и главы 7, посвященной пользовательским типам (см. раздел 7.1.3). Также отложим вопрос создания анонимных классов (последний в списке вариант выражения `new`), см. раздел 6.11.3.
А здесь сосредоточимся на создании уже хорошо известных массивов. Выражение `new T[n]` выделяет непрерывную область памяти, достаточную для размещения `n` объектов типа `T` подряд, заполняет эти места значениями `T.init` и возвращает ссылку на них в виде значения типа `T[]`. Например:
```d
auto arr = new int[4];
assert(arr.length == 4);
assert(arr == [ 0, 0, 0, 0 ]); // Инициализирован по умолчанию
```
Тот же результат можно получить, чуть изменив синтаксис:
```d
auto arr = new int[](4);
```
На этот раз выражение интерпретируется как `new T(4)`, где под `T` понимается `int[]`. Опять же результатом будет массив из четырех элементов, доступ к которому предоставляет переменная `arr` типа `int[]`.
У второго варианта действительно больше возможностей, чем у первого. Если требуется выделить память под массив массивов, в круглых скобках можно указать несколько аргументов. Эти значения инициализируют массивы по строкам. Например, память под массив, состоящий из четырех массивов по восемь элементов, можно выделить так:
```d
auto matrix = new int[][](4, 8);
assert(matrix.length == 4);
assert(matrix[0].length == 8);
```
Первая строка в рассмотренном коде заменяет более многословную запись:
```d
auto matrix = new int[][](4);
foreach (ref row; matrix)
{
row = new int[](8);
}
```
Во всех рассмотренных случаях память выделяется из кучи с автоматической сборкой мусора. Память, которая больше не используется и недоступна программе, отправляется обратно в кучу. Библиотека времени исполнения из эталонной реализации предоставляет множество специализированных средств для управления памятью в модуле `core.gc`, в том числе изменение размера только что выделенного блока памяти и освобождение памяти вручную. Управление памятью вручную рискованное занятие, поэтому всегда избегайте его, кроме случаев, когда это абсолютно необходимо.
Необязательный `адрес`, расположенный сразу после ключевого слова `new`, вводит конструкцию, называемую *новым размещением*. По смыслу вариант `new(адрес) T` отличается от других: вместо выделения памяти под новый объект происходит размещение объекта по заданному `адресу`. Такие низкоуровневые средства в обычном коде не применяются. Вы можете использовать их, например, чтобы распределять память из кучи C с помощью `malloc` и затем использовать ее для хранения значений языка D.
[В начало ⮍](#2-3-6-1-выражение-new) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.6.2. Получение адреса и разыменование
Поскольку мы еще будем говорить об указателях, сейчас упомянем лишь о парных операторах получения адреса и разыменования. Выражение `&значение` получает адрес значения (которое должно быть l-значением) и возвращает указатель с типом `T*`, если `значение` имеет тип `T`.
Обратная операция `*p` разыменовывает указатель, отменяя операцию получения адреса; выражение `*&значение` преобразуется к виду `значение`. Подробный разговор об указателях намеренно отложен до главы 7, потому что в D можно многого добиться и без использования указателей низкоуровневых и опасныx средств языка.
[В начало ⮍](#2-3-6-2-получение-адреса-и-разыменование) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.6.3. Увеличение и уменьшение на единицу (префиксный вариант)
Выражения `++значение` и `--значение` соответственно увеличивают и уменьшают значение (которое должно быть числом или указателем) на единицу, возвращая в качестве результата только что измененное значение.
[В начало ⮍](#2-3-6-3-увеличение-и-уменьшение-на-единицу-префиксный-вариант) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.6.4. Поразрядное отрицание
Выражение `~a` инвертирует (изменяет на противоположное значение) каждый бит в `a` и возвращает значение того же типа, что и `a`. `a` должно быть целым числом.
[В начало ⮍](#2-3-6-4-поразрядное-отрицание) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.6.5. Унарный плюс и унарный минус
Выражение `+значение` не делает ничего особенного: оператор унарный плюс включен в язык лишь из соображений сохранения целостности. Выражение `-значение` равносильно выражению `0 - значение`; унарный минус используется только с операндом-числом.
Одна из странностей поведения унарного минуса: применив этот оператор к числу без знака, получим также число без знака (по правилам, изложенным в разделе 2.3.3), например `-55u` это `4_294_967_241`, то есть `uint.max - 55 + 1`.
То, что числа без знака на самом деле не являются натуральными, суровая правда жизни. Для D, как и для других языков, двоичная арифметика со своими простыми правилами переполнения неизбежная реальность, от которой пользователя не защитят никакие абстракции. Один из способов не ошибиться в трактовке выражения `-значение`, где `значение` любое целое число, считать его краткой записью `~значение + 1`; другими словами, инвертировать каждый из разрядов `значения` и прибавить 1 к полученному результату. Такая процедура не вызывает вопросов, связанных с наличием знака у типа переменной `значение`.
[В начало ⮍](#2-3-6-5-унарный-плюс-и-унарный-минус) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.6.6. Отрицание
Выражение `!значение` имеет тип `bool` и возвращает `false`, если `значение` ненулевое (определение ненулевого значения приведено в разделе 2.3.4.1), иначе возвращается `true`.
[В начало ⮍](#2-3-6-6-отрицание) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.6.7. Приведение типов
Оператор приведения типов подобен могущественному доброму Джинну из лампы, который всегда рад выручить. При этом, как и герой мультфильма, он своенравен, чуть глуховат и не упустит случая развлечься, слишком буквально выполняя нечетко сформулированные желания, что обычно приводит к катастрофе.
Несмотря на сказанное, в редких случаях приведение типов бывает полезно, если система статической типизации недостаточно проницательна, чтобы отследить все ваши «эксплойты». Выглядит приведение типов так: `cast(Тип) a`.
Перечислим виды приведения типов по убыванию безопасности использования.
- *Приведение ссылок* преобразование между ссылками на объекты `class` и `interface`. Такие приведения всегда динамически проверяются.
- *Приведение чисел* принудительное преобразование данных любого числового типа в данные любого другого числового типа.
- *Приведение массивов* преобразование между разными типами массивов; общий размер исходного массива должен быть кратен размеру элементов целевого массива.
- *Приведение указателей* преобразование указателя одного типа в указатель другого типа.
- *Приведение указатель/число* перевод указателя в целый тип достаточного размера, чтобы вместить этот указатель, и наоборот.
Будьте предельно осторожны со всеми непроверяемыми приведениями типов, особенно с тремя последними, поскольку они могут нарушить целостность системы типов.
[В начало ⮍](#2-3-6-7-приведение-типов) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.7. Возведение в степень
Синтаксис выражения возведения в степень: `основание ^^ показатель` (`основание` возводится в степень `показатель`). И `основание`, и `показатель` должны быть числами. То же самое делает функция `pow(основание, показатель)`, которую можно найти в стандартных библиотеках языков C и D (обратитесь к документации для своего модуля `std.math`). Тем не менее запись некоторых числовых выражений действительно выигрывает от синтаксического упрощения.
Результат возведения нуля в нулевую степень единица, а в любую другую ноль.
[В начало ⮍](#2-3-7-возведение-в-степень) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.8. Мультипликативные операции
К мультипликативным операциям относятся умножение (`a * b`), деление (`a / b`) и получение остатка от деления (`a % b`). Они применимы исключительно к числовым типам.
Если в целочисленных операциях `a / b` или `a % b` в качестве `b` участвует ноль, будет сгенерирована аппаратная ошибка. Дробный результат деления всегда округляется в меньшую сторону (например, в результате `7 / 3` получим `2`, а результатом `-7 / 3` будет `-1`). Операция `a % b` определена так, что `a == (a / b) * b + a % b`, поэтому в результате `7 % 3` получим `1`, а результатом `-7 % 3` будет `-1`.
В языке D также можно определить остаток от деления для чисел с плавающей запятой. Это более запутанное определение. Если в выражении `a % b` в качестве `а` или `b` выступает число с плавающей запятой, результатом становится наибольшее (по модулю) число с плавающей запятой `r`, удовлетворяющее следующим условиям:
- `a` и `r` не имеют противоположных знаков;
- `r` меньше `b` по модулю, то есть `abs(r) < abs(b)`;
- существует такое целое число `q`, что `r == a - q * b`.
Если такое число найти невозможно, результатом `a % b` будет особое значение NaN.
[В начало ⮍](#2-3-8-мультипликативные-операции) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.9. Аддитивные операции
Аддитивные операции это сложение `a + b`, вычитание `a - b` и конкатенация `a ~ b`.
Сложение и вычитание применимы только к числам. Тип результата определяется в соответствии с правилами из раздела 2.3.3.
Операция конкатенации может быть применена к операндам `a` и `b`, если хотя бы один из них является массивом элементов некоторого типа `T`. В качестве другого операнда должен выступать либо массив элементов типа `T`, либо значение типа, неявно преобразуемого к типу `T`. В результате получается новый массив, созданный из размещенных друг за другом `a` и `b`.
[В начало ⮍](#2-3-9-аддитивные-операции) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.10. Сдвиг
В языке D есть три операции сдвига, в каждой из которых участвуют
два целочисленных операнда: `a << b`, `a >> b` и `a >>> b`. Во всех случаях
значение `b` должно иметь тип без знака; значение со знаком необходимо
привести к значению беззнакового типа (разумеется, предварительно
убедившись, что `b >= 0`; результат сдвига на отрицательное количество
разрядов непредсказуем). `a << b` сдвигает a влево (то есть в направлении
самого старшего разряда `a`) на `b` бит, а `a >> b` сдвигает `a` вправо на `b` бит.
Если `a` отрицательное число, знак после сдвига сохраняется.
`a >>> b` это беззнаковый сдвиг независимо от знаковости `a`. Это означа
ет, что ноль гарантированно займет самый старший разряд `a`. Проил
люстрируем сюрпризы, которые готовит применение операции сдвига
к числам со знаком:
```d
int a = -1; // То есть 0xFFFF_FFFF
int b = a << 1;
assert(b == -2); // 0xFFFF_FFFE
int с = a >> 1;
assert(c == -1); // 0xFFFF_FFFF
int d = a >>> 1;
assert(d == +2147483647); // 0x7FFF_FFFF
```
Сдвиг на число разрядов большее, чем в типе `a`, запрещается во время компиляции, если `b` статически заданное, заранее известное значение. Если же `b` определяется во время исполнения программы, то результат такого сдвига зависит от реализации компилятора:
```d
int a = 50;
uint b = 35;
a << 33; // Ошибка во время компиляции
auto c = a << b; // Результат зависит от реализации
auto d = a >> b; // Результат зависит от реализации
```
В любом случае тип результата определяется в соответствии с правилами из раздела 2.3.3.
Раньше было популярно с помощью операции сдвига реализовывать быстрое целочисленное умножение на 2 (`a << 1`) или деление на 2 (`a >> 1`) или в общем случае умножение и деление на различные степени 2. Эта техника вышла из употребления, подобно видеокассетам. Пишите просто: `a * k` или `a / k`; если значение `k` известно на этапе компиляции, компилятор гарантированно сгенерирует для вас оптимальный код с операциями сдвига и всем, что еще нужно, избавив вас от волнений по поводу тонкостей работы со знаком. Не ищите сдвига на свою голову.
[В начало ⮍](#2-3-10-сдвиг) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.11. Выражения in
Если `ключ` это значение типа `K`, a `массив` ассоциативный массив типа `V[K]`, то выражение вида `ключ in массив` порождает значение типа `V*` (указатель на `V`). Если ассоциативный массив содержит пару `〈ключ, значение〉`, то указатель указывает на `значение`. В противном случае полученный указатель `null`.
Для обратной, отрицательной проверки вы, конечно, можете написать `!(ключ in массив)`, но есть и более сжатая форма `ключ !in массив` с тем же приоритетом, что и у `ключ in массив`.
Зачем нужны все эти сложности с указателями, когда выражение `a in b` может просто возвращать значение логического типа? Ответ: для эффективности. Довольно часто требуется узнать, есть ли в массиве нужный индекс, и если есть, то использовать соответствующий ему элемент. Можно написать что-то вроде:
```d
double[string] table;
...
if ("hello" in table)
{
++table["hello"];
}
else
{
table["hello"] = 0;
}
```
Проблема этого кода в том, что он дважды обращается к массиву в случае, если запрошенный индекс найден. Используя возвращенный указатель, можно создавать более эффективный код, например:
```d
double[string] table;
...
auto p = "hello" in table;
if (p)
{
++*p;
}
else
{
table["hello"] = 1;
}
```
[В начало ⮍](#2-3-11-выражения-in) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.12. Сравнение
#### 2.3.12.1. Проверка на равенство
Операция вида `a == b`, возвращающая значение типа `bool`, имеет следующую семантику. Во-первых, если операнды имеют разный тип, сначала они неявно преобразуются к одному типу. Затем операнды проверяются на равенство следующим образом:
- для целых чисел и указателей выполняется точное поразрядное сравнение операндов;
- для чисел с плавающей запятой приняты правила: `-0` считается равным `+0`, а NaN не равным NaN[^19]; во всех остальных случаях операнды сравниваются поразрядно;
- равенство объектов типа `class` определяется с помощью оператора `opEquals` (см. раздел 6.8.3);
- равенство массивов означает поэлементное равенство;
- по умолчанию равенство объектов типа `struct` определяется как равенство всех полей операндов; пользовательские типы могут переопределять это поведение (см. главу 12).
Операция вида `a != b` служит для проверки на неравенство.
С помощью выражения `a is b` проверяется *равенство ссылок* (*alias equality*): если `a` и `b` ссылаются на один и тот же объект, выражение возвращает `true`:
- если `a` и `b` массивы или ссылки на классы, результатом будет `true`, только если `a` и `b` это два имени для одного реального объекта;
- в остальных случаях для `a` и `b` должно быть истинно выражение `a == b`.
Мы пока не касались классов, но пример с массивами может быть полезным:
```d
import std.stdio;
void main()
{
auto a = "какая-то строка";
auto b = a; // a и b ссылаются на один и тот же массив
a is b && writeln("Ага, это действительно одно и то же.");
auto c = "какая-то (другая) строка";
a is c || writeln("Действительно... не одно и то же.");
}
```
Этот код печатает оба сообщения, потому что `a` и `b` связаны с одним и тем же массивом, в то время как с ссылается на другой массив. В общем случае возможно, чтобы два массива обладали одинаковым содержимым (тогда выражение `a == b` истинно), но они указывают на разные области памяти, поэтому проверка вида `a is b` вернет `false`. Разумеется, когда выражение `a is b` истинно, то и `a == b` также истинно (если только ваши планки оперативной памяти не куплены по дешевке).
Вместо выражения проверки на неравенство `!(a is b)` можно использовать его краткий вариант `a !is b`.
[В начало ⮍](#2-3-12-1-проверка-на-равенство) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.12.2. Сравнение для упорядочивания
В языке D определены стандартные логические операции вида `a < b`, `a <= b`, `a > b` и `a >= b` (меньше, меньше или равно, больше, больше или равно). При сравнении чисел одно из них должно быть неявно преобразовано к типу другого. Для операндов с плавающей запятой считается, что `-0` равен `0`, то есть результатом сравнения `-0 < 0` будет `false`. Если хотя бы один из операндов равен NaN, то любая операция упорядочивающего сравнения вернет `false` (пусть это и кажется парадоксом).
Как обычно NaN портит добропорядочным числам с плавающей запятой весь праздник. Все сравнения, в которых участвует хотя бы одно значение NaN, порождают «исключение в операции с плавающей запятой» (floating-point exception). С точки зрения терминологии языков программирования это не совсем обычное исключение: возникает лишь особая ситуация на уровне аппаратного обеспечения, которую можно отдельно обработать. D предоставляет интерфейс для математического сопроцессора через модуль `std.c.fenv`.
[В начало ⮍](#2-3-12-2-сравнение-для-упорядочивания) [Наверх ⮍](#2-основные-типы-данных-выражения)
#### 2.3.12.3. Неассоциативность
Одно из важных свойств операторов сравнения в языке D их *неассоциативность*. Любая цепочка операторов сравнения вида `a <= b < c` некорректна.
Простой способ определить операторы сравнения сделать так, чтобы они возвращали значение типа `bool`. Возможность сравнивать логические значения друг с другом имеет не очень приятное следствие: смысл выражения `a <= b < c` не совпадает с привычным для маленького математика внутри нас, который то и дело пытается напомнить о себе. Вместо «`b` больше или равно `a` и меньше `c`» выражение будет распознано как `(a <= b) < c`, то есть «логический результат сравнения `a <= b` сравнить с `c`». Например, выражение `3 <= 4 < 2` было бы истинно! Такая семантика вряд ли желательна.
Можно было бы решить эту проблему, разрешив выражение `a <= b < c` и наделив его истинным математическим значением: `a <= b && b < c`, с тем чтобы `b` вычислялось только один раз. В языках Python и Perl 6 принята именно такая семантика, позволяющая использовать произвольные цепочки сравнений, такие как `a < b == c > d < e`. Но D наследник не этих языков. Разрешение использовать выражения на C, но со слегка измененной семантикой (хотя Python и Perl 6, скорее всего, выбрали верное направление), добавило бы больше неразберихи, чем удобства, поэтому разработчики D решили просто-напросто запретить такую конструкцию.
[В начало ⮍](#2-3-12-3-неассоциативность) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.13. Поразрядные ИЛИ, ИСКЛЮЧАЮЩЕЕ ИЛИ и И
Выражения `a | b`, `a ^ b` и `a & b` представляют собой поразрядные операции ИЛИ, ИСКЛЮЧАЮЩЕЕ ИЛИ и И соответственно. Перед выполнением операции вычисляются оба операнда (неполное вычисление логических выражений не допускается), даже если результат определяется уже по одному из них.
И `a`, и `b` должны быть целыми числами. Тип результата определяется в соответствии с правилами из раздела 2.3.3.
[В начало ⮍](#2-3-13-поразрядные-или-исключающее-или-и-и) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.14. Логическое И
В свете вышесказанного неудивительно, что значение выражения `a && b` зависит от типа `b`.
- Если тип `b` не `void`, то результатом выражения будет логическое значение. Если операнд `a` ненулевой, вычисляется `b`, и только в том случае, если он также ненулевой, возвращается `true`, иначе возвращается `false`.
- Если `b` имеет тип `void`, то и все выражение имеет тип `void`. Если операнд `a` ненулевой, то вычисляется операнд `b`. Иначе `b` не вычисляется.
Оператор `&&` с выражением типа `void` в качестве правого операнда можно использовать в роли краткой инструкции `if`:
```d
string line;
...
line == "#\n" && writeln("Успешно принята строка #. ");
```
[В начало ⮍](#2-3-14-логическое-и) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.15. Логическое ИЛИ
Семантика выражения `a || b` зависит от типа `b`.
- Если тип `b` не `void`, выражение имеет тип `bool`. Если операнд `a` ненулевой, выражение возвращает `true`. Иначе вычисляется `b`, и только в том случае, если он также ненулевой, возвращается `true`.
- Если `b` имеет тип `void`, то и все выражение имеет тип `void`. Если операнд `a` ненулевой, то операнд `b` не вычисляется. Иначе `b` вычисляется.
Второе правило можно применять для обработки непредсказуемых обстоятельств:
```d
string line;
...
line.length > 0 || line = "\n";
```
[В начало ⮍](#2-3-15-логическое-или) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.16. Тернарная условная операция
Тернарная условная операция это конструкция типа `if-then-else` с синтаксисом `a ? b : c`, с которой вы, возможно, знакомы. Если операнд `a` ненулевой, условное выражение вычисляется и возвращается `b`; иначе выражение вычисляется и возвращается `c`. Ценой героических усилий компилятор определяет наиболее «узкий» тип для `b` и `c`, который становится типом всего выражения. Этот тип (назовем его `T`) вычисляется с помощью простого алгоритма (показанного на примерах):
1. Если `b` и `c` одного типа, он выступает и в роли `T`.
2. Иначе если `b` и `c` целые числа, сначала типы меньше 32 разрядов расширяются до `int`, затем `T` присваивается больший тип; при одинаковых размерах приоритет имеет тип без знака.
3. Иначе если один операнд целого типа, а другой с плавающей запятой, в качестве `T` выбирается тип с плавающей запятой.
4. Если оба операнда относятся к типу с плавающей запятой, то `T` наибольший из этих типов.
5. Иначе если типы имеют один и тот же супертип (то есть базовый тип), `T` принимает вид этого супертипа (к этой теме мы вернемся в главе 6).
6. Иначе делается попытка неявно привести `c` к типу `b` и `b` к типу `c`; если удается только что-то одно, в роли `T` выступает тип, полученный в результате удачного приведения.
7. Иначе выражение содержит ошибку.
Более того, если `b` и `с` одного типа и являются l-значениями, результатом также будет l-значение, что позволяет написать:
```d
int x = 5, y = 5;
bool which = true;
(which ? x : y) += 5;
assert(x == 10);
```
Многие концептуальные примеры обобщенного программирования используют тернарную операцию сравнения для нахождения общего типа двух значений.
[В начало ⮍](#2-3-16-тернарная-условная-операция) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.17. Присваивание
Операции присваивания имеют вид `a = b` или `a ω= b`, где буква `ω` выступает в качестве одного из операторов `^^`, `*`, `/`, `%`, `+`, `-`, `~`, `<<`, `>>`, `>>>`, `|`, `^` или `&`, а также «за обязательное использование греческих букв в книгах по программированию». С применением самостоятельных вариантов этих операторов вы уже познакомились в предыдущих разделах.
Выражение `a ω= b` семантически идентично выражению вида `a = a ω b`, однако между этими формами записи все же есть серьезное различие: в первом случае `a` вычисляется всего один раз (представьте, что `a` и `b` достаточно сложные выражения, например: `array[i * 5 + j] *= sqrt(x)`).
Независимо от приоритета `ω` оператор `ω=` имеет тот же приоритет, что и сам оператор `=`, то есть ниже, чем у оператора сравнения (о котором речь шла выше), и чуть выше, чем у запятой (о которой речь пойдет ниже). Также независимо от ассоциативности `ω` все операторы вида `ω=` (в том числе `=`) ассоциативны слева, например `a /= b = c -= d` это то же самое, что и `a /= (b = (c -= d))`.
[В начало ⮍](#2-3-17-присваивание) [Наверх ⮍](#2-основные-типы-данных-выражения)
### 2.3.18. Выражения с запятой
Выражения, разделенные запятыми, вычисляются последовательно друг за другом. Результат выражения в целом это результат самого правого подвыражения. Например:
```d
int a = 5;
int b = 10;
int c = (a = b, b = 7, 8);
```
После выполнения этого фрагмента кода переменные `a`, `b` и `c` примут значения `10`, `7` и `8` соответственно.
[В начало ⮍](#2-3-18-выражения-с-запятой) [Наверх ⮍](#2-основные-типы-данных-выражения)
## 2.4. Итоги и справочник
На этом мы завершаем описание богатых возможностей языка D по построению выражений. В табл. 2.5. собраны все операторы языка D. Вы можете использовать ее в качестве краткого справочника.
*Таблица 2.5. Выражения D порядке убывания приоритета*
|Выражение|Описание|
|-|-|
|`<идентификатор>`|Идентификатор ([см. раздел 2.1](#2-1-идентификаторы))|
|`.<идентификатор>`|Идентификатор, доступный в пространстве имен модуля (в обход всех друг пространств имен) ([см. раздел 2.1](#2-1-идентификаторы))|
|`this`|Текущий объект внутри метода ([см. раздел 2.1.1](#2-1-1-ключевые-слова))|
|`super`|Направляет поиск идентификаторов и динамический поиск методов в пространство имен объекта-родителя ([см. раздел 2.1.1](#2-1-1-ключевые-слова))|
|`$`|Текущий размер массива (допустимо использовать `$` внутри индексирующего выражения или выражения получения среза) ([см. раздел 2.1.1](#2-1-1-ключевые-слова))|
|`null`|«Нулевая» ссылка, массив или указатель ([см. раздел 2.1.1](#2-1-1-ключевые-слова))|
|`typedi(T)`|Получить объект `TypeInfo`, ассоциированный с `T` ([см. раздел 2.1.1](#2-1-1-ключевые-слова))|
|`true`|Логическое значение «истина» ([см. раздел 2.2.1](#2-2-1-логические-литералы))|
|`false`|Логическое значение «ложь» ([см. раздел 2.2.1](#2-2-1-логические-литералы))|
|`<число>`|Числовой литерал ([см. раздел 2.2.2](#2-2-2-целые-литералы), [см. раздел 2.2.3](#2-2-3-литералы-с-плавающей-запятой))|
|`<знак>`|Знаковый литерал ([см. раздел 2.2.4](#2-2-4-знаковые-литералы))|
|`<строка>`|Строковый литерал ([см. раздел 2.2.5](#2-2-5-строковые-литералы))|
|`<массив>`|Литерал массива ([см. раздел 2.2.6](#2-2-6-литералы-массивов-и-ассоциативных-массивов))|
|`<функция>`|Функциональный литерал ([см. раздел 2.2.7](#2-2-7-функциональные-литералы))|
|`assert(a)`|В режиме отладки, если a не является ненулевым значением, выполнение программы прерывается; в режиме итоговой сборки (release) ничего не происходит ([см. раздел 2.3.4.1](#2-3-4-1-выражение-assert))|
|`assert(a, b)`|То же, но к сообщению об ошибке добавляется `b` ([см. раздел 2.3.4.1](#2-3-4-1-выражение-assert))|
|`mixin(a)`|Выражение `mixin` ([см. раздел 2.3.4.2](#2-3-4-2-выражение-mixin))|
|`<IsExpr>`|Выражение `is` ([см. раздел 2.3.4.3](#2-3-4-3-выражения-is))|
|`( a )`|Выражение в круглых скобках ([см. раздел 2.3.4.4](#2-3-4-4-выражения-в-круглых-скобках))|
|`a.b`|Доступ к вложенным элементам ([см. раздел 2.3.5.1](#2-3-5-1-доступ-ко-внутренним-элементам))|
|`a++`|Постфиксный вариа нт операции увеличения на единицу ([см. раздел 2.3.5.2](#2-3-5-2-увеличение-и-уменьшение-на-единицу))|
|`a--`|Постфиксный вариант операции уменьшения на единицу ([см. раздел 2.3.5.2](#2-3-5-2-увеличение-и-уменьшение-на-единицу))|
|`a(<арг>)`|Оператор вызова функции (`<арг> = ` необязательный список аргументов, разделенных запятыми) ([см. раздел 2.3.5.3](#2-3-5-3-вызов-функции))|
|`a[<арг>]`|Оператор индексации (`<арг> = ` список аргументов, разделенных запятыми) ([см. раздел 2.3.5.4](#2-3-5-4-индексация))|
|`a[]`|Срез в размере всего массива ([см. раздел 2.3.5.5](#2-3-5-5-срезы-массивов))|
|`a[b .. c]`|Срез ([см. раздел 2.3.5.5](#2-3-5-5-срезы-массивов))|
|`a.<выражение new>`|Создание экземпляра вложенного класса ([см. раздел 2.3.5.6](#2-3-5-6-создание-вложенного-класса))|
|`&a`|Получение адреса ([см. раздел 2.3.6.2](#2-3-6-2-получение-адреса-и-разыменование))|
|`++a`|Префиксный вариант операции увеличения на единицу ([см. раздел 2.3.6.3](#2-3-6-3-увеличение-и-уменьшение-на-единицу-префиксный-вариант))|
|`--a`|Префиксный вариант операции уменьшения на единицу ([см. раздел 2.3.6.3](#2-3-6-3-увеличение-и-уменьшение-на-единицу-префиксный-вариант))|
|`*a`|Разыменование ([см. раздел 2.3.6.2](#2-3-6-2-получение-адреса-и-разыменование))|
|`-a`|Унарный минус ([см. раздел 2.3.6.5](#2-3-6-5-унарный-плюс-и-унарный-минус))|
|`+a`|Унарный плюс ([см. раздел 2.3.6.5](#2-3-6-5-унарный-плюс-и-унарный-минус))|
|`!a`|Отрицание ([см. раздел 2.3.6.6](#2-3-6-6-отрицание))|
|`~a`|Поразрядное отрицание ([см. раздел 2.3.6.4](#2-3-6-4-поразрядное-отрицание))|
|`(T).a`|Доступ к статическим внутренним элементам|
|`cast(T) a`|Приведение выражения `a` к типу `T`|
|`<выражение new>`|Создание объекта ([см. раздел 2.3.6.1](#2-3-6-1-выражение-new))|
|`a ^^ b`|Возведение в степень ([см. раздел 2.3.7](#2-3-7-возведение-в-степень))|
|`a * b`|Умножение ([см. раздел 2.3.8](#2-3-8-мультипликативные-операции))|
|`a / b`|Деление ([см. раздел 2.3.8](#2-3-8-мультипликативные-операции))|
|`a % b`|Получение остатка от деления ([см. раздел 2.3.8](#2-3-8-мультипликативные-операции))|
|`a + b`|Сложение ([см. раздел 2.3.9](#2-3-9-аддитивные-операции))|
|`a - b`|Вычитание ([см. раздел 2.3.9](#2-3-9-аддитивные-операции))|
|`a ~ b`|Конкатенация([см. раздел 2.3.9](#2-3-9-аддитивные-операции))|
|`a << b`|Сдвиг влево ([см. раздел 2.3.10](#2-3-10-сдвиг))|
|`a >> b`|Сдвиг вправо ([см. раздел 2.3.10](#2-3-10-сдвиг))|
|`a >>> b`|Беззнаковый сдвиг вправо (старший разряд сбрасывается независимо от типа и значения `a`) ([см. раздел 2.3.10](#2-3-10-сдвиг))|
|`a in b`|Проверка на принадлежность для ассоциативных массивов ([см. раздел 2.3.11](#2-3-11-выражения-in))|
|`a == b`|Проверка на равенство; все операторы этой группы неассоциативны; например, выражение `a == b == c` некорректно ([см. раздел 2.3.12.1](#2-3-12-1-проверка-на-равенство))|
|`a != b`|Проверка на неравенство ([см. раздел 2.3.12.1](#2-3-12-1-проверка-на-равенство))|
|`a is b`|Проверка на идентичность (`true`, если и только если `a` и `b` ссылаются на один и тот же объект) ([см. раздел 2.3.12.1](#2-3-12-1-проверка-на-равенство))|
|`a !is b`|То же, что `!(a is b)`|
|`a < b`|Меньше ([см. раздел 2.3.12.2](#2-3-12-2-сравнение-для-упорядочивания))|
|`a <= b`|Меньше или равно ([см. раздел 2.3.12.2](#2-3-12-2-сравнение-для-упорядочивания))|
|`a > b`|Больше ([см. раздел 2.3.12.2](#2-3-12-2-сравнение-для-упорядочивания))|
|`a >= b`|Больше или равно ([см. раздел 2.3.12.2](#2-3-12-2-сравнение-для-упорядочивания))|
|`a \| b`|Поразрядное **ИЛИ** ([см. раздел 2.3.13](#2-3-13-поразрядные-или-исключающее-или-и-и))|
|`a ^ b`|Поразрядное **ИСКЛЮЧАЮЩЕЕ ИЛИ** ([см. раздел 2.3.13](#2-3-13-поразрядные-или-исключающее-или-и-и))|
|`a & b`|Поразрядное **И** ([см. раздел 2.3.13](#2-3-13-поразрядные-или-исключающее-или-и-и))|
|`a && b`|Логическое **И** (`b` может иметь тип `void`) ([см. раздел 2.3.14](#2-3-14-логическое-и))|
|`a \|\| b`|Логическое **ИЛИ** (`b` может иметь тип `void`) ([см. раздел 2.3.15](#2-3-15-логическое-или))|
|`a ? b : c`|Тернарная условная операция; если операнд `a` имеет ненулевое значение, то `b`, иначе `с` ([см. раздел 2.3.16](#2-3-16-тернарная-условная-операция))|
|`a = b`|Присваивание; все операторы присваивания этой группы ассоциативны справа; например `a *= b += c` то же, что и `a *= (b += c)` ([см. раздел 2.3.17](#2-3-17-присваивание))|
|`a += b`|Сложение «на месте»; выражения со всеми операторами вида `a ω= b`, работающими по принципу «вычислить и присвоить», вычисляются в следующем порядке: **1)** `a` (должно быть l-значением), **2)** `b` и **3)** `al = al ω b`, где `al` l-значение, получившееся в результате вычисления `a`|
|`a -= b`|Вычитание «на месте»|
|`a *= b`|Умножение «на месте»|
|`a /= b`|Деление «на месте»|
|`a %= b`|Получение остатка от деления «на месте»|
|`a &= b`|Поразрядное **И** «на месте»|
|`a \|= b`|Поразрядное **ИЛИ** «на месте»|
|`a ^= b`|Поразрядное **ИСКЛЮЧАЮЩЕЕ ИЛИ** «на месте»|
|`a ~= b`|Конкатенация «на месте» (присоединение `b` к `a`)|
|`a <<= b`|Сдвиг влево «на месте»|
|`a >>= b`|Сдвиг вправо «на месте»|
|`a >>>= b`|Беззнаковый сдвиг вправо «на месте»|
|`a, b`|Последовательность выражений; выражения вычисляются слева направо, результатом операции становится самое правое выражение ([см. раздел 2.3.18](#2-3-18-выражения-с-запятой))|
[В начало ⮍](#2-4-итоги-и-справочник) [Наверх ⮍](#2-основные-типы-данных-выражения)
[^1]: Впрочем, использование нелатинских букв является дурным тоном. *Прим. науч. ред.*
[^2]: С99 обновленная спецификация C, в том числе добавляющая поддержку знаков Юникода. *Прим. пер.*
[^3]: Сам язык не поддерживает восьмеричные литералы, но поскольку они присутствуют в некоторых C-подобных языках, в стандартную библиотеку был добавлен соответствующий шаблон. Теперь запись `std.conv.octal!777` аналогична записи `0777` в C. *Прим. науч. ред.*
@ -737,3 +1246,4 @@ bool
[^16]: In situ (лат.) на месте. *Прим. пер.*
[^17]: От англ. left-value и right-value. *Прим. науч. ред.*
[^18]: Domain-specific embedded language (DSEL) предметно-ориентированный встроенный язык. *Прим. пер.*
[^19]: Стандарт IEEE 754 определяет для чисел с плавающей запятой два разных двоичных представления для нуля: -0 и +0. Это порождает ряд неудобств, таких как исключение при сравнении чисел, рассмотренное здесь. С другой стороны, скорость многих вычислений увеличивается. Вы, скорее всего, будете редко использовать литерал `-0.0` в коде на D, но это значение может получиться неявно как результат вычислений, асимптотически приближающих отрицательные значения к нулю.