2.3.6-2.4
This commit is contained in:
parent
79765a3d33
commit
42b2af19c0
|
@ -29,30 +29,30 @@
|
|||
- [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.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.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 у вас не будет никаких затруднений. Операции со значениями основных типов – неотъемлемая часть решений многих задач программирования. Эти средства языка, в зависимости от ваших предпочтений, могут сильно облегчать либо отравлять вам жизнь. Совершенного подхода не существует; нередко поставленные цели противоречат друг другу, заставляя руководствоваться собственным субъективным мнением. Это, в свою очередь, лишает язык возможности угодить всем до единого. Слишком строгая система обременяет программиста своими запретами: он вынужден бороться с компилятором, чтобы тот принял простейшие выражения. А сделай систему типизации чересчур снисходительной – и не заметишь, как окажешься по ту сторону корректности, эффективности или того и другого вместе.
|
||||
|
||||
|
@ -767,6 +767,467 @@ assert(a == [ 0, 0, 0, 1, 3 ]); // a был изменен
|
|||
|
||||
[В начало ⮍](#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. – *Прим. науч. ред.*
|
||||
|
@ -785,3 +1246,4 @@ assert(a == [ 0, 0, 0, 1, 3 ]); // a был изменен
|
|||
[^16]: In situ (лат.) – на месте. – *Прим. пер.*
|
||||
[^17]: От англ. left-value и right-value. – *Прим. науч. ред.*
|
||||
[^18]: Domain-specific embedded language (DSEL) – предметно-ориентированный встроенный язык. – *Прим. пер.*
|
||||
[^19]: Стандарт IEEE 754 определяет для чисел с плавающей запятой два разных двоичных представления для нуля: -0 и +0. Это порождает ряд неудобств, таких как исключение при сравнении чисел, рассмотренное здесь. С другой стороны, скорость многих вычислений увеличивается. Вы, скорее всего, будете редко использовать литерал `-0.0` в коде на D, но это значение может получиться неявно как результат вычислений, асимптотически приближающих отрицательные значения к нулю.
|
||||
|
|
Loading…
Reference in New Issue