From b64e6a47cb900a8d50a2ab8f99405985d7b6cb80 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 22 Jan 2023 18:03:35 +0300 Subject: [PATCH] 2.2.5-2.3 --- 02-основные-типы-данных-выражения/README.md | 238 +++++++++++++++++++- 1 file changed, 235 insertions(+), 3 deletions(-) diff --git a/02-основные-типы-данных-выражения/README.md b/02-основные-типы-данных-выражения/README.md index 5a65da1..27b4812 100644 --- a/02-основные-типы-данных-выражения/README.md +++ b/02-основные-типы-данных-выражения/README.md @@ -8,9 +8,9 @@ - [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.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-значения]() @@ -255,6 +255,233 @@ auto a = "В этой строке есть \"двойные кавычки\", [В начало ⮍](#2-2-5-строковые-литералы) [Наверх ⮍](#2-основные-типы-данных-выражения) +#### 2.2.5.1. Строковые литералы: WYSIWYG, с разделителями, строки токенов, шестнадцатеричные и импортированные + +WYSIWYG-строка либо начинается с `r"` и заканчивается на `"` (`r"как здесь"`), либо начинается и заканчивается грависом[^12] (`как здесь`). В WYSIWYG-строке может встретиться любой знак (кроме соответствующих знаков начала и конца литерала), который хранится так же, как и выглядит. Это означает, что вы не можете представить, например, знак двойной кавычки внутри заключенной в такие же двойные кавычки WYSIWYG-строки. Это не проблема, потому что всегда можно сделать конкатенацию строк, представленных с помощью разных синтаксических правил. Например: + +```d +auto a = r"Строка с \ и " `"` " внутри."; +``` + +Из практических соображений можно считать, что двойная кавычка внутри `r"такой строки"` обозначается последовательностью ``〈"`"`"〉``, а гравис внутри `такой строки` – последовательностью ``〈`"`"`〉``. Счастливого подсчета кавычек. + +Иногда бывает удобно описать строку, ограниченную с двух сторон какими-то символами. Для этих целей D предоставляет особый вид строковых литералов – литерал с разделителями. + +```d +auto a = q"[Какая-то строка с "кавычками", `обратными апострофами` и [квадратными скобками]]"; +``` + +Теперь в `a` находится строка, содержащая кавычки, обратные апострофы и квадратные скобки, то есть все, что мы видим между `q"[` и `]"`. И никаких обратных слэшей, нагромождения ненужных кавычек и прочего мусора. Общий формат этого литерала: сначала идет префикс `q` и двойная кавычка, за которой сразу без пробелов следует знак-разделитель. Оканчивается строка знаком-разделителем и двойной кавычкой, за которой может следовать суффикс, указывающий на тип литерала: `c`, `w` или `d`. Допустимы следующие парные разделители: `[` и `]`, `(` и `)`, `<` и `>`, `{` и `}`. Допускаются вложения парных разделителей, то есть внутри пары скобок может быть другая пара скобок, которая становится частью строки. В качестве разделителя можно также использовать любой знак, например: + +```d +auto a = q"/Просто строка/"; // Эквивалентно строке "Просто строка" +``` + +При этом строка распознается до первого вхождения ограничивающего знака: + +```d +auto a = q"/Просто/строка/"; // Ошибка. +auto b = q"[]]"; // Опять ошибка. +auto с = q"[[]]"; // А теперь все нормально, т. к. разделители [] допускают вложение. +``` + +Если в качестве разделителя нужно использовать какую-то последовательность знаков, открывающую и закрывающую последовательности следует писать на отдельной строке: + +```d +auto a = q"EndOfString +Несколько +строк +текста +EndOfString"; +``` + +Кроме того, D предлагает такой вид строкового литерала, как строка токенов. Такой литерал начинается с последовательности `q{` и заканчивается на `}`. При этом текст, расположенный между `{` и `}`, должен представлять из себя последовательность токенов языка D и интерпретируется как есть. + +```d +auto a = q{ foo(q{hello}); }; // Эквивалентно " foo(q{hello}); " +auto b = q{ № }; // Ошибка! "№" - не токен языка +auto a = q{ __EOF__ }; // Ошибка! __EOF__ - не токен, а конец файла +``` + +Также D определяет еще один вид строковых литералов – шестнадцатеричную строку, то есть строку, состоящую из шестнадцатеричных цифр и пробелов (пробелы игнорируются) между `x"` и `"`. Шестнадцатеричные строки могут быть полезны для определения сырых данных; компилятор не пытается интерпретировать содержимое литералов никак знаки Юникода, ни как-то еще – только как шестнадцатеричные цифры. Пробелы внутри строк игнорируются. + +```d +auto +a = x"0A", // То же самое, что "\x0A" +b = x"00 F BCD 32"; // То же самое, что "\x00\xFB\xCD\x32" +``` + +Если ваш хакерский мозг уже начал прикидывать, как внедрить в программы на D двоичные данные, вы будете счастливы услышать об очень мощном способе определения строки: из файла! + +```d +auto x = import("resource.bin"); +``` + +Во время компиляции переменная x будет инициализирована непосредственно содержимым файла `resource.bin`. (Это отличается от действия директивы C `#include`, поскольку в рассмотренном примере файл включается в качестве данных, а не кода.) По соображениям безопасности допустимы только относительные пути и пути поиска контролируются флагами компилятора. Эталонная реализация `dmd` использует флаг `-J` для управления путями поиска. + +Строка, возвращаемая функцией `import`, не проверяется на соответствие кодировке UTF-8. Это сделано намеренно – для реализации возможности включать двоичные данные. + +[В начало ⮍](#2-2-5-1-строковые-литералы-wysiwyg-с-разделителями-строки-токенов-шестнадцатеричные-и-импортированные) [Наверх ⮍](#2-основные-типы-данных-выражения) + +#### 2.2.5.2. Тип строкового литерала + +Каков тип строкового литерала? Проведем простой эксперимент: + +```d +import std.stdio; + +void main() +{ + writeln(typeid(typeof("Hello, world!"))); +} +``` + +Встроенный оператор `typeof` возвращает тип выражения, а `typeid` конвертирует его в печатаемую строку. Наша маленькая программа печатает: + +```d +immutable(char)[] +``` + +открывая нам то, что строковые литералы – это *массивы неизменяемых знаков*. На самом деле, тип string, который мы использовали в примерах, – это краткая форма записи (или псевдоним), означающая `immutable(char)[]`. Рассмотрим подробно все три составляющие типа строковых литералов: неизменяемость, длина и базовый тип данных – знаковый. + +**Неизменяемость** + +Строковые литералы живут в неизменяемой области памяти. Это вовсе не говорит о том, что они хранятся на действительно не стираемых кристаллах ЗУ или в защищенной области памяти операционной системы. Это означает, что язык обязуется не перезаписывать память, выделенную под строку. Ключевое слово `immutable` воплощает это обязательство, запрещая во время компиляции любые операции, которые могли бы модифицировать содержимое неизменяемых данных, помеченных этим ключевым словом: + +```d +auto a = "Изменить этот текст нельзя"; +a[0] = 'X'; // Ошибка! Нельзя модифицировать неизменяемую строку! +``` + +Ключевое слово `immutable` – это *квалификатор типа* (квалификаторы обсуждаются в главе 8); его действие распространяется на любой тип, указанный в круглых скобках после него. Если вы напишете `immutable(char)[] str`, то знаки в строке `str` нельзя будет изменять по отдельности, однако `str` можно заставить ссылаться на другую строку: + +```d +immutable(char)[] str = "One"; +str[0] = 'X'; // Ошибка! Нельзя присваивать значения переменным типа immutable(char)! +str = "Two"; // Отлично, присвоим str другую строку +``` + +С другой стороны, если круглые скобки отсутствуют, квалификатор `immutable` будет относиться ко всему массиву: + +```d +immutable char[] a = "One"; +a[0] = 'X'; // Ошибка! +a = "Two"; // Ошибка! +``` + +У неизменяемости масса достоинств, а именно: квалификатор `immutable` предоставляет достаточно гарантий, чтобы разрешить неразборчивое совместное использование данных модулями и потоками (см. главу 13). Поскольку знаки строки неизменяемы, никогда не возникает споров, и совместный доступ безопасен и эффективен. + +**Длина** + +Очевидно, что длина строкового литерала (13 для "Hello, world!") известна во время компиляции. Поэтому может казаться естественным давать наиболее точное определение каждой строке; например, строка `"Hello, world!"` может быть типизирована как `char[13]`, что означает «массив ровно из 13 знаков». Однако опыт языка Паскаль показал, что статические размеры крайне неудобны. Так что в D тип литерала не включает информацию о его длине. Тем не менее, если вы действительно хотите работать со строкой фиксированного размера, то можете создать такую, явно указав ее длину: + +```d +immutable(char)[13] a = "Hello, world!"; +char[13] b = "Hello, world!"; +``` + +Типы массивов фиксированного размера `T[N]` могут неявно конвертироваться в типы динамических массивов `T[]` для всех типов `T`. В процессе преобразования информация не теряется, так как динамические массивы «помнят» свою длину: + +```d +import std.stdio; + +void main() +{ + immutable(char)[3] a = "Hi!"; + immutable(char)[] b = a; + writeln(a.length, " ", b.length); // Печатает "3 3" +} +``` + +**Базовый тип данных – знаковый** + +Последнее, но немаловажное, что нужно сказать о строковых литералах, – в качестве их базового типа может выступать `char`, `wchar` или `dchar`[^13]. Использовать многословные имена типов необязательно: `string`, `wstring` и `dstring` – удобные псевдонимы для `immutable(char)[]`, `immutable(wchar)[]` и `immutable(dchar)[]` соответственно. Если строковый литерал содержит хотя бы один 4-байтный знак типа `dchar`, то строка принимает тип `dstring`; иначе, если строка содержит хотя бы один 2-байтный знак типа `wchar`, то строка принимает тип `wstring`, иначе строка принимает знакомый тип `string`. Если ожидается тип, отличный от определяемого по контексту, литерал молча уступит, как в этом примере: + +```d +wstring x = "Здравствуй, широкий мир!"; // UTF-16 +dstring y = "Здравствуй, еще более широкий мир!"; // UTF-32 +``` + +Если вы хотите явно указать тип строки, то можете снабдить строковый литерал суффиксом: `c`, `w` или `d`, которые заставляют тип строкового литерала принять значение `string`, `wstring` или `dstring` соответственно. + +[В начало ⮍](#2-2-5-2-тип-строкового-литерала) [Наверх ⮍](#2-основные-типы-данных-выражения) + +### 2.2.6. Литералы массивов и ассоциативных массивов + +Cтрока – это частный случай массива со своим синтаксисом литералов. А как представить литерал массива другого типа, например `int` или `double`? Литерал массива задается заключенным в квадратные скобки списком значений, разделенных запятыми[^14]: + +```d +auto somePrimes = [ 2u, 3, 5, 7, 11, 13 ]; +auto someDoubles = [ 1.5, 3, 4.5 ]; +``` + +Размер массива вычисляется по количеству разделенных запятыми элементов списка. В отличие от строковых литералов, литералы массивов изменяемы, так что вы можете изменить их после инициализации: + +```d +auto constants = [ 2.71, 3.14, 6.023e22 ]; +constants[0] = 2.21953167; // "Константа дивана" +auto salutations = [ "привет", "здравствуйте", "здорово" ]; +salutations[2] = "Да здравствует Цезарь"; +``` + +Обратите внимание: можно присвоить новую строку элементу массива `salutations`, но нельзя изменить содержимое старой строки, хранящееся в памяти. Этого и следовало ожидать, потому что членство в массиве не отменяет правил работы с типом `string`. + +Тип элементов массива определяется «соглашением» между всеми элементами массива, которое вычисляется с помощью оператора сравнения `?:` (см. раздел 2.3.16). Для литерала `lit`, содержащего больше одного элемента, компилятор вычисляет выражение `true ? lit[0] : lit[1]` и сохраняет тип этого выражения как тип `L`. Затем для каждого i-го элемента `lit[i]` до последнего элемента в `lit` компилятор вычисляет тип `true ? L.init : lit[i]` и снова сохраняет это значение в `L`. Конечное значение `L` и есть тип элементов массива. + +На самом деле, все гораздо проще, чем кажется, – тип элементов массива устанавливается аналогично Польскому демократическому соглашению[^15]: ищется тип, в который можно неявно конвертировать все элементы массива. Например, тип массива `[1, 2, 2.2]` – `double`, а тип массива `[1, 2, 3u]` – `uint`, так как результатом операции `?:` с аргументами `int` и `uint` будет `uint`. + +Литерал ассоциативного массива задается так: + +```d +auto famousNamedConstants = [ "пи" : 3.14, "e" : 2.71, "константа дивана" : 2.22 ]; +``` + +Каждая ячейка литерала ассоциативного массива имеет вид `ключ: значение`. Тип ключей литерала ассоциативного массива вычисляется по массиву, в который неявно записываются все эти ключи, с помощью описанного выше способа. Тип значений вычисляется аналогично. После вычисления типа ключей `K` и типа значений `V` литерал типизируется как `V[K]`. Например, константа `famousNamedConstants` принимает тип `double[string]`. + +[В начало ⮍](#2-2-6-литералы-массивов-и-ассоциативных-массивов) [Наверх ⮍](#2-основные-типы-данных-выражения) + +### 2.2.7. Функциональные литералы + +В некоторых языках имя функции задается в ее определении; впоследствии такие функции вызываются по именам. Другие языки предоставляют возможность определить анонимную функцию (так называемую *лямбда-функцию*) прямо там, где она должна использоваться. Такое средство помогает строить мощные конструкции, задействующие функции более высокого порядка, то есть функции, принимающие в качестве аргументов и/или возвращающие другие функции. Функциональные литералы D позволяют определять анонимные функции *in situ*[^16] – когда бы ни ожидалось имя функции. + +Задача этого раздела – всего лишь показать на нескольких интересных примерах, как определяются функциональные литералы. Примеры более действенного применения этого мощного средства отложим до главы 5. Вот базовый синтаксис функционального литерала: + +```d +auto f = function double(int x) { return x / 10.; }; +auto a = f(5); +assert(a == 0.5); +``` + +Функциональный литерал определяется по тем же синтаксическим правилам, что и функция, с той лишь разницей, что определению предшествует ключевое слово `function`, а имя функции отсутствует. Рассмотренный пример в общем-то даже не использует анонимность, так как анонимная функция немедленно связывается с идентификатором `f`. Тип `f` – «указатель на функцию, принимающую `int` и возвращающую `double`». Этот тип записывается как `double function(int)` (обратите внимание: ключевое слово `function` и возвращаемый тип поменялись местами), так что эквивалентное определение `f` выглядит так: + +```d +double function(int) f = function double(int x) { return x / 10.; }; +``` + +Кажущаяся странной перестановка `function` и `double` на самом деле сильно облегчает всем жизнь, позволяя отличить функциональный литерал по типу. Формулировка для легкого запоминания: слово `function` стоит в начале определения литерала, а в типе функции замещает имя функции. + +Для простоты в определении функционального литерала можно опустить возвращаемый тип – компилятор определит его для вас по контексту, ведь ему тут же доступно тело функции. + +```d +auto f = function(int x) { return x / 10.; }; +``` + +Наш функциональный литерал использует только собственный параметр `x`, так что его значение можно выяснить, взглянув лишь на тело функции и не принимая во внимание окружение, в котором она используется. Но что если функциональному литералу потребуется использовать данные, которые присутствуют в точке вызова, но не передаются как аргумент? В этом случае нужно заменить слово `function` словом `delegate`: + +```d +int c = 2; +auto f = delegate double(int x) { return c * x / 10.; }; +auto a = f(5); +assert(a == 1); +c = 3; +auto b = f(5); +assert(b == 1.5); +``` + +Теперь тип `f` – `delegate double(int x)`. Все правила распознавания типа для `function` применимы без изменений к `delegate`. Отсюда справедливый вопрос: если конструкции `delegate` могут делать все, на что способны `function`-конструкции (в конце концов конструкции `delegate` *могут*, но *не обязаны* использовать переменные своего окружения), зачем же сначала возиться с функциями? Нельзя ли всегда использовать конструкции `delegate`? Ответ прост: все дело в эффективности. Очевидно, что конструкции `delegate` обладают доступом к большему количеству информации, а по непреложному закону природы за такой доступ приходится расплачиваться. На самом деле, размер `function` равен размеру указателя, а `delegate` – в два раза больше (один указатель на функцию, один – на окружение). + +[В начало ⮍](#2-2-7-функциональные-литералы) [Наверх ⮍](#2-основные-типы-данных-выражения) + [^1]: Впрочем, использование нелатинских букв является дурным тоном. – *Прим. науч. ред.* [^2]: С99 – обновленная спецификация C, в том числе добавляющая поддержку знаков Юникода. – *Прим. пер.* [^3]: Сам язык не поддерживает восьмеричные литералы, но поскольку они присутствуют в некоторых C-подобных языках, в стандартную библиотеку был добавлен соответствующий шаблон. Теперь запись `std.conv.octal!777` аналогична записи `0777` в C. – *Прим. науч. ред.* @@ -266,3 +493,8 @@ auto a = "В этой строке есть \"двойные кавычки\", [^9]: Да, синтаксис странноватый, но D скопировал его из стандарта C99, чтобы не изобретать свою нотацию с собственными выкрутасами, которых все равно не избежать. [^10]: Escape-последовательность (от англ. escape – избежать), экранирующая/управляющая последовательность – специальная комбинация знаков, отменяющая стандартную обработку компилятором следующих за ней знаков (они как бы «исключаются из рассмотрения»). – *Прим. пер.* [^11]: WYSIWIG – акроним «What You See Is What You Get» (что видишь, то и получишь) – способ представления, при котором данные в процессе редактирования выглядят так же, как и в результате обработки каким-либо инструментом (компилятором, после отображения браузером и т. п.). – *Прим. пер.* +[^12]: Он же обратный апостроф. – *Прим. науч. ред.* +[^13]: Префиксы `w` и `d` – от англ. wide (широкий) и double (двойной) – *Прим. науч. ред.* +[^14]: В литерале массива допустима запятая, после которой нет элемента, например [1, 2,] – длина этого массива равна 2, а последняя запятая попросту игнорируется. Это сделано для удобства автоматических генераторов кода: при генерации текста литерала массива они конкатенируют строки вида `"очередной_элемент"`, не обрабатывая отдельно последний элемент, запятая после которого была бы не нужна. – *Прим. науч. ред.* +[^15]: Заключенное в 1989 году соглашение между коммунистами и демократами, ознаменовавшее собой достижение компромисса между двумя партиями. В данном случае также ищется «компромиссный» тип. – *Прим. пер.* +[^16]: In situ (лат.) – на месте. – *Прим. пер.*