# 2. Основные типы данных. Выражения - [2.1. Идентификаторы](#2-1-идентификаторы) - [2.1.1. Ключевые слова](#2-1-1-ключевые-слова) - [2.2. Литералы](#2-2-литералы) - [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.5.1. Строковые литералы: WYSIWYG, с разделителями, строки токенов и импортированные]() - [2.2.5.2. Тип строкового литерала]() - [2.2.6. Литералы массивов и ассоциативных массивов]() - [2.2.7. Функциональные литералы (лямбда-функция)]() - [2.3. Операции]() - [2.3.1. l-значения и r-значения]() - [2.3.2. Неявные преобразования чисел]() - [2.3.2.1. Распространение интервала значений]() - [2.3.3. Типы числовых операций]() - [2.3.4. Первичные выражения]() - [2.3.4.1. Выражение assert]() - [2.3.4.2. Выражение mixin]() - [2.3.4.3. Выражения is]() - [2.3.4.4. Выражения в круглых скобках]() - [2.3.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. Итоги и справочник]() Если вы когда-нибудь программировали на C, C++, Java или C#, то с основными типами данных и выражениями D у вас не будет никаких затруднений. Операции со значениями основных типов – неотъемлемая часть решений многих задач программирования. Эти средства языка, в зависимости от ваших предпочтений, могут сильно облегчать либо отравлять вам жизнь. Совершенного подхода не существует; нередко поставленные цели противоречат друг другу, заставляя руководствоваться собственным субъективным мнением. Это, в свою очередь, лишает язык возможности угодить всем до единого. Слишком строгая система обременяет программиста своими запретами: он вынужден бороться с компилятором, чтобы тот принял простейшие выражения. А сделай систему типизации чересчур снисходительной – и не заметишь, как окажешься по ту сторону корректности, эффективности или того и другого вместе. Система основных типов D творит маленькие чудеса в границах, задаваемых его принадлежностью к семейству статически типизированных компилируемых языков. Определение типа по контексту, распространение интервала значений, всевозможные стратегии перегрузки операторов и тщательно спроектированная сеть автоматических преобразований вместе делают систему типизации D дотошным, но сдержанным помощником, который если и придирается, требуя внимания, то обычно не зря. Основные типы данных можно распределить по следующим категориям: - *Тип без значения*: `void`, используется во всех случаях, когда формально требуется указать тип, но никакое осмысленное значение не порождается. - *Тип null*: `typeof(null)` – тип константы `null`, используется в основном в шаблонах, неявно приводится к указателям, массивам, ассоциативным массивам и объектным типам. - *Логический (булев) тип*: `bool` с двумя возможными значениями `true` и `false`. - *Целые типы*: `byte`, `short`, `int` и `long`, а также их эквиваленты без знака `ubyte`, `ushort`, `uint` и `ulong`. - *Вещественные типы с плавающей запятой*: `float`, `double` и `real`. - *Знаковые типы*: `char`, `wchar` и `dchar`, которые на самом деле содержат числа, предназначенные для кодирования знаков Юникода. В табл. 2.1 вкратце описаны основные типы данных D с указанием их размеров и начальных значений по умолчанию. В языке D переменная инициализируется автоматически, если вы просто определили ее, не указав начального значения. Значение по умолчанию доступно для любого типа как `<тип>.init`; например `int.init` – это ноль. *Таблица 2.1. Основные типы данных D* |Тип данных|Описание|Начальное значение по умолчанию| |-|-|-| |`void`|Без значения|`n/a`| |`typeof(null)`|Тип константы `null`|`n/a`| |`bool`|Логическое (булево) значение|`false`| |`byte`|Со знаком, 8 бит|`0`| |`ubyte`|Без знака, 8 бит|`0`| |`short`|Со знаком, 16 бит|`0`| |`ushort`|Без знака, 16 бит|`0`| |`int`|Со знаком, 32 бита|`0`| |`uint`|Без знака, 32 бита|`0`| |`long`|Со знаком, 64 бита|`0`| |`ulong`|Без знака, 64 бита|`0`| |`float`|32 бита, с плавающей запятой|`float.nan`| |`double`|64 бита, с плавающей запятой|`double.nan`| |`real`|Наибольшее, какое только может позволить аппаратное обеспечение|`real.nan`| |`char`|Без знака, 8 бит, в UTF-8|`0xFF`| |`wchar`|Без знака, 16 бит, в UTF-16|`0xFFFF`| |`dchar`|Без знака, 32 бита, в UTF-32|`0x0000FFFF`| [В начало ⮍](#2-основные-типы-данных-выражения) ## 2.1. Идентификаторы Идентификатор, или символ – это чувствительная к регистру строка знаков, начинающаяся с буквы или знака подчеркивания, после чего следует любое количество букв, знаков подчеркивания или цифр. Единственное исключение из этого правила: идентификаторы, начинающиеся с двух знаков подчеркивания, зарезервированы под ключевые слова самого D. Идентификаторы, начинающиеся с одного знака подчеркивания, разрешены, и в настоящее время даже принято именовать поля классов таким способом. Интересная особенность идентификаторов D – их интернациональность: «буква» в определении выше – это не только буква латинского алфавита (от A до Z и от a до z), но и знак из универсального набора[^1], определенного в стандарте C99[^2]. Например, `abc`, `α5`, `_`, `Γ_1`, `_AbC`, `Ab9C` и `_9x` – допустимые идентификаторы, а `9abc` и `__abc` – нет. Если перед идентификатором стоит точка (`.какЗдесь`), то компилятор ищет его в пространстве имен модуля, а не в текущем лексически близком пространстве имен. Этот префиксный оператор-точка имеет тот же приоритет, что и обычный идентификатор. [В начало ⮍](#2-1-идентификаторы) [Наверх ⮍](#2-основные-типы-данных-выражения) ### 2.1.1. Ключевые слова Приведенные в табл. 2.2 идентификаторы – это ключевые слова, зарезервированные языком для специального использования. Пользовательский код не может переопределять их ни при каких условиях. *Таблица 2.2. Ключевые слова языка D* ```d abstract else macro switch alias enum mixin synchronized align export module asm extern template assert new this auto false nothrow throw final null true body finally try bool float out typeid break for override typeof byte foreach function package ubyte case pragma uint cast goto private ulong catch protected union char ifIf public unittest class immutable pure ushort const import continue in real version inout ref void dchar int return debug interface wchar default invariant scope while delegate isIs short with deprecated static do long struct double lazy super ``` Некоторые из ключевых слов распознаются как первичные выражения. Например, ключевое слово `this` внутри определения метода означает текущий объект, а ключевое слово `super` как статически, так и динамически заставляет компилятор обратиться к классу-родителю текущего класса (см. главу 6). Идентификатор `$` разрешен только внутри индексного выражения или выражения получения среза и обозначает длину индексируемого массива. Идентификатор `null` обозначает пустой объект, массив или указатель. Первичное выражение `typeid(T)` возвращает информацию о типе `T` (за дополнительной информацией обращайтесь к документации для вашей реализации компилятора). [В начало ⮍](#2-1-1-ключевые-слова) [Наверх ⮍](#2-основные-типы-данных-выражения) ## 2.2. Литералы ### 2.2.1. Логические литералы Логические (булевы) литералы – это `true` («истина») и `false` («ложь»). [В начало ⮍](#2-2-1-логические-литералы) [Наверх ⮍](#2-основные-типы-данных-выражения) ### 2.2.2. Целые литералы D работает с десятичными, восьмеричными[^3], шестнадцатеричными и двоичными целыми литералами. Десятичная константа - это последовательность цифр, возможно, с суффиксом `L`, `U`, `u`, `LU`, `Lu`, `UL` или `ul`. Вывод о типе десятичного литерала делается исходя из следующих правил: - *нет суффикса*: если значение «помещается» в `int`, то `int`, иначе `long`; - *только* `U`/`u`: если значение «помещается» в `uint`, то `uint`, иначе `ulong`. - *только* `L`: тип константы - `long`. - `U`/`u` *и* `L` *совместно*: тип константы - `ulong`. Например: ```d auto a = 42, // a имеет тип int b = 42u, // b имеет тип uint c = 42UL, // c имеет тип ulong d = 4_000_000_000, // long; в int не поместится e = 4_000_000_000u, // uint; в uint не поместится f = 5_000_000_000u; // ulong; в uint не поместится ``` Вы можете свободно вставлять в числа знаки подчеркивания (только не ставьте их в начало, иначе вы на самом деле создадите идентификатор). Знаки подчеркивания помогают сделать большое число более наглядным: ```d auto targetSalary = 15_000_000; ``` Чтобы написать шестнадцатеричное число, используйте префикс `0x` или `0X`, за которым следует последовательность знаков `0–9`, `a–f`, `A–F` или `_`. Двоичный литерал создается с помощью префикса `0b` или `0B`, за которым идет последовательность из `0`, `1` и тех же знаков подчеркивания. Как и у десятичных чисел, у всех этих литералов может быть суффикс. Правила, с помощью которых их типы определяются по контексту, идентичны правилам для десятичных чисел. Рисунок 2.1, заменяющий 1024 слова, кратко и точно определяет синтаксис целых литералов. Правила интерпретации автомата таковы: 1) каждое ребро «поглощает» знаки, соответствующие его ребру, 2) автомат пытается «расходовать» как можно больше знаков из входной последовательности[^4]. Достижение конечного состояния (двойной кружок) означает, что число успешно распознано. ![image-2-1](images/image-2-1.png) ***Рис. 2.1.*** *Распознавание целых литералов в языке D. Автомат пытается сделать ряд последовательных шагов (поглощая знаки, соответствующие данному ребру), пока не остановится. Останов в конечном состоянии (двойной кружок) означает, что число успешно распознано. s обозначает суффикс вида U|u|L|UL|uL|Lu|LU* [В начало ⮍](#2-2-2-целые-литералы) [Наверх ⮍](#2-основные-типы-данных-выражения) ### 2.2.3. Литералы с плавающей запятой Литералы с плавающей запятой могут быть десятичными и шестнадцатеричными. Десятичные литералы с плавающей запятой легко определить по аналогии с только что определенными десятичными целыми числами: десятичный литерал с плавающей запятой состоит из десятичного литерала, который также может содержать точку[^5] в любой позиции, за ней могут следовать показатель степени (характеристика) и суффикс. Показатель степени[^6] – это то, что обозначается как `e`, `E`, `e+`, `E+`, `e-` или `E-`, после чего следует целый десятичный литерал без знака[^7]. В качестве суффикса может выступать `f`, `F` или `L`. Разумеется, хотя бы что-то одно из `e/E` и `f/F` должно присутствовать, иначе если в числе нет точки, вместо числа с плавающей запятой получим целое. Суффикс `f/F` заставляет компилятор определить тип литерала как `float`, а суффикс `L` – как `real`. Иначе литералу будет присвоен тип `double`. Может показаться, что шестнадцатеричные константы с плавающей запятой – вещь странноватая. Однако, как показывает практика, они очень удобны, если нужно записать число очень *точно*. Внутреннее представление чисел с плавающей запятой характеризуется тем, что числа хранятся в двоичном виде, поэтому запись вещественного числа в десятичном виде повлечет преобразования, невозможные без округлений, поскольку 10 – не степень 2. Шестнадцатеричная форма записи, напротив, позволяет записать число с плавающей запятой точно так, как оно будет представлено. Полный курс по представлению чисел с плавающей запятой выходит за рамки этой книги; отметим лишь, что все реализации D гарантированно используют формат IEEE 754, полную информацию о котором можно найти в Сети (сделайте запрос «формат чисел с плавающей запятой IEEE 754»). Шестнадцатеричный литерал с плавающей запятой состоит из префикса `0x` или `0X`, за которым следует строка шестнадцатеричных цифр, содержащая точку в любой позиции. Затем идет обязательный показатель степени[^8], который начинается с `p`, `P`, `p+`, `P+`, `p-` или `P-` и заканчивается десятичными (не шестнадцатеричными!) цифрами. Только так называемая мантисса – дробная часть перед показателем степени – выражается шестнадцатеричным числом; сам показатель степени – целое десятичное число. Показатель степени шестнадцатеричной константы с плавающей запятой означает степень 2 (а не 10, как в случае с десятичным представлением). Завершается литерал необязательным суффиксом `f`, `F` или `L`[^9]. Рассмотрим несколько подходящих примеров: ```d auto a = 1.0, // a имеет тип double b = .345E2f, // b = 34.5 имеет тип float c = 10f, // c имеет тип float из-за суффикса d = 10., // d имеет тип double e = 0x1.fffffffffffffp1023, // наибольшее возможное значение типа double f = 0XFp1F; // f = 30.0, тип float ``` Рисунок 2.2 без лишних слов описывает литералы с плавающей запятой языка D. Правила интерпретации автомата те же, что и для автомата, иллюстрирующего распознавание целых литералов: переход выполняется по мере чтения знаков литерала с целью прочитать как можно больше. Представление в виде автомата проясняет несколько фактов, которые было бы утомительно описывать, не используя формальный аппарат. Например, `0x.p1` и `0xp1` – вполне приемлемые, хотя и странные формы записи нуля, а конструкции типа `0e1`, `.e1` и `0x0.0` запрещены. ![image-2-2](images/image-2-2.png) ***Рис. 2.2.*** *Распознавание литералов с плавающей запятой* [В начало ⮍](#2-2-3-литералы-с-плавающей-запятой) [Наверх ⮍](#2-основные-типы-данных-выражения) ### 2.2.4. Знаковые литералы Знаковый литерал – это один знак, заключенный в одиночные кавычки, например `'a'`. Если в качестве знака выступают сами кавычки, их нужно экранировать с помощью обратной косой черты: `'\''`. На самом деле в D, как и в других языках, определены несколько разных escape-последовательностей[^10] (см. табл. 2.3). В дополнение к стандартному набору управляющих непечатаемых символов D предоставляет следующие возможности записать знаки Юникода: `'\u03C9'` (знаки `\u`, за которыми следуют ровно 4 шестнадцатеричные цифры), `'\U0000211C'` (знаки `\U`, за которыми следуют ровно 8 шестнадцатеричных цифр) и `'\©'` (имя, окруженное знаками `\&` и `;`). Первый из этих примеров – знак `ω` в Юникоде, второй – красивая письменная `ℬ`, а последний – грозный знак `©`. Если вам нужен полный список знаков, которые можно отобразить, поищите в Интернете информацию о таблице знаков Юникода. *Таблица 2.3. Экранирующие последовательности в D* |Escape-последовательность|Тип|Описание| |-|-|-| |`\"`|`char`|Двойная кавычка (если двусмысленно)| |`\\`|`char`|Обратная косая черта| |`\a`|`char`|Звуковой сигнал (Bell, ASCII 7)| |`\b`|`char`|Backspace (ASCII 8)| |`\f`|`char`|Смена страницы (ASCII 12)| |`\n`|`char`|Перевод строки (ASCII 10)| |`\r`|`char`|Возврат каретки (ASCII 13)| |`\t`|`char`|Табуляция (ASCII 9)| |`\v`|`char`|Вертикальная табуляция (ASCII 11)| |`\<1–3 восьмеричные цифры>`|`char`|Знак UTF-8 в восьмеричном представлении (не больше 3778)| |`\x<2 шестнадцатеричные цифры>`|`char`|Знак UTF-8 в шестнадцатеричном представлении| |`\u<4 шестнадцатеричные цифры>`|`wchar`|Знак UTF-16 в шестнадцатеричном представлении| |`\U<8 шестнадцатеричных цифр>`|`dchar`|Знак UTF-32 в шестнадцатеричном представлении| |`\&<имя знака>;`|`dchar`|Имя знака Юникод| [В начало ⮍](#2-2-4-знаковые-литералы) [Наверх ⮍](#2-основные-типы-данных-выражения) ### 2.2.5. Строковые литералы Теперь, когда мы знаем, как представляются отдельные знаки, строковые литералы для нас пустяк. D прекрасно справляется с обработкой строк отчасти благодаря своим мощным средствам представления строковых литералов. Как и другие языки, работающие со строками, D различает строки, заключенные в кавычки (внутри которых можно размещать экранированные последовательности из табл. 2.3), и WYSIWYG-строки[^11] (которые компилятор распознает «вслепую», не пытаясь обнаружить и расшифровать никакие escape-последовательности). Стиль WYSIWYG очень удобен для представления строк, где иначе пришлось бы использовать множество экранированных знаков; два выдающихся примера – регулярные выражения и пути к файлам в системе Windows. Строки, заключенные в кавычки (quoted strings), – это последовательности знаков в двойных кавычках, `"как в этом примере"`. В таких строках все escape-последовательности из табл. 2.3 являются значимыми. Строки всех видов, расположенные подряд, автоматически подвергаются конкатенации: ```d auto crlf = "\r\n"; auto a = "В этой строке есть \"двойные кавычки\", а также перевод строки, даже два" "\n"; ``` Текст умышленно перенесен на новую строку после слова `также`: строковый литерал может содержать знак перевода строки (реальное начало новой строки в исходном коде, а не комбинацию `\n`), который будет сохранен именно в этом качестве. [В начало ⮍](#2-2-5-строковые-литералы) [Наверх ⮍](#2-основные-типы-данных-выражения) [^1]: Впрочем, использование нелатинских букв является дурным тоном. – *Прим. науч. ред.* [^2]: С99 – обновленная спецификация C, в том числе добавляющая поддержку знаков Юникода. – *Прим. пер.* [^3]: Сам язык не поддерживает восьмеричные литералы, но поскольку они присутствуют в некоторых C-подобных языках, в стандартную библиотеку был добавлен соответствующий шаблон. Теперь запись `std.conv.octal!777` аналогична записи `0777` в C. – *Прим. науч. ред.* [^4]: Для тех, кто готов воспринимать теорию: автоматы на рис. 2.1 и 2.2 – это детерминированные конечные автоматы (ДКА). [^5]: В России в качестве разделителя целой и дробной части чисел с плавающей запятой принята запятая (поэтому и говорят: «числа с плавающей *запятой*»), однако в англоговорящих странах для этого служит точка, поэтому в языках программирования (обычно основанных на английском – международном языке информатики) разделителем является точка. – *Прим. пер.* [^6]: Показатель степени 10 по-английски – exponent, поэтому для его обозначения и используется буква `e`. – *Прим. пер.* [^7]: Запись `Ep` означает «умножить на 10 в степени `p`», то есть `p` – это порядок. – *Прим. пер.* [^8]: Степень по-английски – power, поэтому показатель степени 2 обозначается буквой `p`. – *Прим. пер.* [^9]: Да, синтаксис странноватый, но D скопировал его из стандарта C99, чтобы не изобретать свою нотацию с собственными выкрутасами, которых все равно не избежать. [^10]: Escape-последовательность (от англ. escape – избежать), экранирующая/управляющая последовательность – специальная комбинация знаков, отменяющая стандартную обработку компилятором следующих за ней знаков (они как бы «исключаются из рассмотрения»). – *Прим. пер.* [^11]: WYSIWIG – акроним «What You See Is What You Get» (что видишь, то и получишь) – способ представления, при котором данные в процессе редактирования выглядят так же, как и в результате обработки каким-либо инструментом (компилятором, после отображения браузером и т. п.). – *Прим. пер.*