2.2.5-2.3

This commit is contained in:
Alexander Zhirov 2023-01-22 18:03:35 +03:00
parent a69bc1f89c
commit b64e6a47cb
1 changed files with 235 additions and 3 deletions

View File

@ -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. Литералы массивов и ассоциативных массивов
рока это частный случай массива со своим синтаксисом литералов. А как представить литерал массива другого типа, например `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 (лат.) на месте. *Прим. пер.*