2.3-2.3.3.
This commit is contained in:
parent
b64e6a47cb
commit
ed90f97c42
|
@ -11,11 +11,11 @@
|
|||
- [2.2.5.1. Строковые литералы: WYSIWYG, с разделителями, строки токенов и импортированные](#2-2-5-1-строковые-литералы-wysiwyg-с-разделителями-строки-токенов-шестнадцатеричные-и-импортированные)
|
||||
- [2.2.5.2. Тип строкового литерала](#2-2-5-2-тип-строкового-литерала)
|
||||
- [2.2.6. Литералы массивов и ассоциативных массивов](#2-2-6-литералы-массивов-и-ассоциативных-массивов)
|
||||
- [2.2.7. Функциональные литералы (лямбда-функция)]()
|
||||
- [2.3. Операции]()
|
||||
- [2.3.1. l-значения и r-значения]()
|
||||
- [2.3.2. Неявные преобразования чисел]()
|
||||
- [2.3.2.1. Распространение интервала значений]()
|
||||
- [2.2.7. Функциональные литералы (лямбда-функция)](#2-2-7-функциональные-литералы)
|
||||
- [2.3. Операции](#2-3-операции)
|
||||
- [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]()
|
||||
|
@ -482,6 +482,104 @@ assert(b == 1.5);
|
|||
|
||||
[В начало ⮍](#2-2-7-функциональные-литералы) [Наверх ⮍](#2-основные-типы-данных-выражения)
|
||||
|
||||
## 2.3. Операции
|
||||
|
||||
В следующих главах подробно описаны все операторы D в порядке убывания приоритета. Это естественный порядок, в котором вы бы группировали и вычисляли небольшие подвыражения в группах все большего размера.
|
||||
|
||||
С операторами тесно связаны две независимые темы: l- и r-значения и правила преобразования чисел. Необходимые определения приведены в следующих двух разделах.
|
||||
|
||||
[В начало ⮍](#2-3-операции) [Наверх ⮍](#2-основные-типы-данных-выражения)
|
||||
|
||||
### 2.3.1. l-значения и r-значения
|
||||
|
||||
Множество операторов срабатывает только тогда, когда l-значения удовлетворяют ряду условий. Например, не нужно быть гением, чтобы понять: присваивание `5 = 10` не соответствует правилам. Для успеха присваивания необходимо, чтобы левый операнд был *l-значением*. Пора дать точное определение l-значения (а заодно и сопутствующего ему *r-значения*). Названия терминов происходят от реального положения этих значений относительно оператора присваивания. Например, в инструкции `a = b` значение `a` расположено слева от оператора присваивания, поэтому оно называется l-значением; соответственно значение `b`, расположенное справа, – это r-значение[^17].
|
||||
|
||||
К l-значениям относятся:
|
||||
- все переменные, включая параметры функций, даже те, которые запрещено изменять (то есть определенные с квалификатором `immutable`);
|
||||
- элементы массивов и ассоциативных массивов;
|
||||
- поля структур и классов (о них мы поговорим позже);
|
||||
- возвращаемые функциями значения, помеченные ключевым словом `ref`;
|
||||
- разыменованные указатели.
|
||||
|
||||
Любое l-значение может выступить в роли r-значения. К r-значениям также относится все, что не вошло в этот список: литералы, перечисляемые значения (которые вводятся с помощью ключевого слова `enum`; см. раздел 7.3) и результаты таких выражений, как `x + 5`. Обратите внимание: для присваивания быть l-значением необходимо, но не достаточно – нужно успешно пройти еще несколько семантических проверок, таких как проверка прав на доступ (см. главу 6) и проверка прав на изменение (см. главу 8).
|
||||
|
||||
[В начало ⮍](#2-3-1-l-значения-и-r-значения) [Наверх ⮍](#2-основные-типы-данных-выражения)
|
||||
|
||||
### 2.3.2. Неявные преобразования чисел
|
||||
|
||||
Мы только что коснулись темы преобразований; теперь пора рассмотреть ее подробнее. Здесь достаточно запомнить всего несколько простых правил:
|
||||
|
||||
1. Если числовое выражение компилируется в C и *также* компилируется в D, то его тип будет одинаковым в обоих языках (обратите внимание: D не обязан принимать все выражения на C).
|
||||
2. Никакое целое значение не преобразуется к типу меньшего размера.
|
||||
3. Никакое значение с плавающей запятой не преобразуется неявно в целое значение.
|
||||
4. Любое числовое значение (целое или с плавающей запятой) неявно преобразуется к любому значению с плавающей запятой.
|
||||
|
||||
Правило 1 лишь незначительно усложняет работу компилятора, и это обоснованное усложнение. Поскольку D достаточно сильно «пересекается» с C и C++, это вдохновляет людей на бездумное копирование целых функций на этих языках в программы на D. Так пусть уж лучше D из соображений безопасности и переносимости отказывается время от времени от некоторых конструкций, чем если бы компилятор «проглотил» модуль из 2000 строк, а полученная программа заработала бы не так, как ожидалось, что определенно осложнило бы жизнь незадачливому программисту. Однако с помощью правила 2 язык D закручивает гайки посильнее, чем C и C++. Так что при переносе кода из этих языков на D диагностирующие сообщения время от времени будут указывать вам на «сырые» куски кода, рекомендуя вставить подходящие проверки и явные преобразования типов.
|
||||
|
||||
Рисунок 2.3 иллюстрирует правила преобразования для всех числовых типов. Для преобразования выбирается кратчайший путь; для двух путей одинаковой длины результаты преобразований совпадают. Независимо от количества шагов преобразование считается одношаговым процессом, преобразования неупорядочены и им не назначены приоритеты – тип или преобразуется к другому типу, или нет.
|
||||
|
||||
[В начало ⮍](#2-3-2-неявные-преобразования-чисел) [Наверх ⮍](#2-основные-типы-данных-выражения)
|
||||
|
||||
#### 2.3.2.1. Распространение интервала значений
|
||||
|
||||
В соответствии с приведенными выше правилами обыкновенное число, такое как 42, будет недвусмысленно оценено как число типа `int`. А теперь взгляните на столь же заурядную инициализацию:
|
||||
|
||||
```d
|
||||
ubyte x = 42;
|
||||
```
|
||||
|
||||
По неумолимым законам проверки типов вначале 42 распознается как `int`. Затем это число типа `int` будет присвоено переменной `x`, а это уже влечет насильственное преобразование типов. Разрешать такое грубое преобразование опасно (ведь многие значения типа `int` на самом деле не поместятся в `ubyte`). С другой стороны, требовать преобразования типов для очевидно безошибочного кода было бы очень неприятно.
|
||||
|
||||
![image-2-3](images/image-2-3.png)
|
||||
|
||||
***Рис. 2.3.*** *Неявные преобразования чисел. Значение одного типа может быть автоматически преобразовано в значение другого типа тогда и только тогда, когда существует направленный путь от исходного типа до желаемого. Выбирается кратчайший путь, и преобразование считается одношаговым независимо от действительной длины пути. Преобразование в обратном направлении возможно, если оно осуществимо на основе метода распространения интервала значений (см. раздел 2.3.2.1)*
|
||||
|
||||
Язык D элегантно разрешает эту проблему с помощью способа, прообразом которого послужила техника оптимизации компиляторов, известная как *распространение интервала значений (value range propagation)*: каждому значению в выражении ставится в соответствие интервал с границами в виде наименьшего и наибольшего возможных значений. Эти границы отслеживаются во время компиляции. Компилятор разрешает присвоить значение некоторого типа значению более «узкого» типа тогда и только тогда, когда интервальная оценка присваиваемого значения покрывается «целевым» типом. Очевидно, что для такой константы, как 42, как наибольшим, так и наименьшим значением будет 42, поэтому для присваивания нет преград.
|
||||
|
||||
Конечно же, в такой типовой ситуации можно было бы использовать гораздо более простой алгоритм, однако в общем случае логично применять метод распространения интервала значений, так как он прекрасно справляется и со сложными ситуациями. Рассмотрим функцию, которая извлекает из значения типа `int` младший и старший байты:
|
||||
|
||||
```d
|
||||
void fun(int val)
|
||||
{
|
||||
ubyte lsByte = val & 0xFF;
|
||||
ubyte hsByte = val >>> 24;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Этот код корректен независимо от того, каким будет введенное значение `val`. В первом выражении на значение накладывается маска, сбрасывающая все биты его старшего байта, а во втором делается сдвиг, в результате которого старший байт `val` перемещается на место младшего, а оставшиеся биты обнуляются.
|
||||
|
||||
И в самом деле, компилятор правильно типизирует функцию `fun`, так как сначала он вычисляет интервал `val & 0xFF` и получает [0; 255] независимо от `val`, затем вычисляет интервал для `val >>> 24` и получает то же самое. Если бы вместо этих операций вы поставили операции, результат которых необязательно вместится в `ubyte` (например, `val & 0x1FF` или `val >>> 23`), компилятор не принял бы такой код.
|
||||
|
||||
Метод распространения интервала значений применим для всех арифметических и логических операций; например, значение типа `uint`, разделенное на 100 000, всегда вместится в `ushort`. Кроме того, этот метод правильно работает и со сложными выражениями, такими как маскирование, после которого следует деление. Например:
|
||||
|
||||
```d
|
||||
void fun(int val)
|
||||
{
|
||||
ubyte x = (val & 0xF0F0) / 300;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
В приведенном примере оператор `&` устанавливает границы интервала в 0 и `0хF0F0` (то есть 61 680 в десятичной системе счисления). Затем операция деления устанавливает границы в 0 и 205. Любое число из этого диапазона вмещается в `ubyte`.
|
||||
|
||||
Определение корректности преобразований к меньшему типу по методу распространения интервала значений – несовершенный и консервативный подход. Одна из причин в том, что интервалы значений отслеживаются близоруко, внутри одного выражения, а не в нескольких смежных выражениях. Например:
|
||||
|
||||
```d
|
||||
void fun(int x)
|
||||
{
|
||||
if (x >= 0 && x < 42)
|
||||
{
|
||||
ubyte y = x; // Ошибка! Нельзя втиснуть int в ubyte!
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Совершенно ясно, что инициализация не содержит ошибок, но компилятор не поймет этого. Он бы мог, но это серьезно усложнило бы реализацию и замедлило процесс компиляции. Выбор был сделан в пользу менее чувствительного распространения интервала значений в рамках одного выражения. Проведенный нами опыт показал, что такой умеренный анализ помогает программе избежать самых грубых ошибок, возникающих из-за ненадлежащего преобразования типов. Для оставшихся ошибок первого рода вы можете использовать выражения `cast` (см. раздел 2.3.6.7).
|
||||
|
||||
[В начало ⮍](#2-3-2-1-распространение-интервала-значений) [Наверх ⮍](#2-основные-типы-данных-выражения)
|
||||
|
||||
[^1]: Впрочем, использование нелатинских букв является дурным тоном. – *Прим. науч. ред.*
|
||||
[^2]: С99 – обновленная спецификация C, в том числе добавляющая поддержку знаков Юникода. – *Прим. пер.*
|
||||
[^3]: Сам язык не поддерживает восьмеричные литералы, но поскольку они присутствуют в некоторых C-подобных языках, в стандартную библиотеку был добавлен соответствующий шаблон. Теперь запись `std.conv.octal!777` аналогична записи `0777` в C. – *Прим. науч. ред.*
|
||||
|
@ -498,3 +596,4 @@ assert(b == 1.5);
|
|||
[^14]: В литерале массива допустима запятая, после которой нет элемента, например [1, 2,] – длина этого массива равна 2, а последняя запятая попросту игнорируется. Это сделано для удобства автоматических генераторов кода: при генерации текста литерала массива они конкатенируют строки вида `"очередной_элемент"`, не обрабатывая отдельно последний элемент, запятая после которого была бы не нужна. – *Прим. науч. ред.*
|
||||
[^15]: Заключенное в 1989 году соглашение между коммунистами и демократами, ознаменовавшее собой достижение компромисса между двумя партиями. В данном случае также ищется «компромиссный» тип. – *Прим. пер.*
|
||||
[^16]: In situ (лат.) – на месте. – *Прим. пер.*
|
||||
[^17]: От англ. left-value и right-value. – *Прим. науч. ред.*
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
Loading…
Reference in New Issue