2.2.5-2.3
This commit is contained in:
parent
a69bc1f89c
commit
b64e6a47cb
|
@ -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 (лат.) – на месте. – *Прим. пер.*
|
||||
|
|
Loading…
Reference in New Issue