diff --git a/book/05-данные-и-функции-функциональный-стиль/README.md b/book/05-данные-и-функции-функциональный-стиль/README.md index 1e53848..26a9777 100644 --- a/book/05-данные-и-функции-функциональный-стиль/README.md +++ b/book/05-данные-и-функции-функциональный-стиль/README.md @@ -871,7 +871,7 @@ T[] find(T)(T[] haystack, T needle) 2. `haystack[0]` осуществляет доступ к первому элементу `haystack`. 3. `haystack = haystack[1 .. $]` исключает из рассмотрения первый элемент `haystack`. -Конкретный способ, каким массивы реализуют эти операции, непросто распространить на другие контейнеры. Например, проверять с помощью выражения `haystack.length > 0`, есть ли в односвязном списке элементы, – подход, достойный премии Дарвина[^9]. Если не обеспечено постоянное кэширование длины списка (что по многим причинам весьма проблематично), то для вычисления длины списка таким способом потребуется время, пропорциональное самой длине списка, а быстрое обращение к началу списка занимает всего лишь несколько машинных инструкций. Применить к спискам индексацию – столь же проигрышная идея. Так что выделим сущность рассмотренных операций, представим полученный результат в виде трех именованных функций и оставим их реализацию типу `haystack`. Примерный синтаксис базовых операций, необходимых для реализации алгоритма линейного поиска: +Конкретный способ, каким массивы реализуют эти операции, непросто распространить на другие контейнеры. Например, проверять с помощью выражения `haystack.length > 0`, есть ли в односвязном списке элементы – подход, достойный премии Дарвина[^9]. Если не обеспечено постоянное кэширование длины списка (что по многим причинам весьма проблематично), то для вычисления длины списка таким способом потребуется время, пропорциональное самой длине списка, а быстрое обращение к началу списка занимает всего лишь несколько машинных инструкций. Применить к спискам индексацию – столь же проигрышная идея. Так что выделим сущность рассмотренных операций, представим полученный результат в виде трех именованных функций и оставим их реализацию типу `haystack`. Примерный синтаксис базовых операций, необходимых для реализации алгоритма линейного поиска: 1. `haystack.empty` – для проверки `haystack` на пустоту. 2. `haystack.front` – для получения первого элемента `haystack`. @@ -1010,7 +1010,7 @@ unittest } ``` -(Обратите внимание на очередное удачное использование `reduce`.) Интересная деталь функции `average`: многоточие ... после параметра `values`, который является срезом. (Если бы это было не так или если бы параметр `values` не был последним в списке аргументов функции `average`, компилятор диагностировал бы это многоточие как ошибку.) +(Обратите внимание на очередное удачное использование `reduce`.) Интересная деталь функции `average`: многоточие `...` после параметра `values`, который является срезом. (Если бы это было не так или если бы параметр `values` не был последним в списке аргументов функции `average`, компилятор диагностировал бы это многоточие как ошибку.) Вызов функции `average` со срезом массива элементов типа `double` (как показано в последней строке теста модуля) ничем не примечателен. Однако благодаря многоточию эту функцию можно вызывать с любым числом аргументов, при условии что каждый из них можно привести к типу `double`. Компилятор автоматически сформирует из этих аргументов срез и передаст его в `average`. diff --git a/book/06-классы-объектно-ориентированный-стиль/README.md b/book/06-классы-объектно-ориентированный-стиль/README.md index 780dde5..cd3baa6 100644 --- a/book/06-классы-объектно-ориентированный-стиль/README.md +++ b/book/06-классы-объектно-ориентированный-стиль/README.md @@ -161,13 +161,11 @@ unittest Вместо трех последних строк можно было бы использовать универсальную вспомогательную функцию `swap` из модуля `std.algorithm`: `swap(a1, a2)`, но явная запись процесса обмена нагляднее. На рис. 6.2 продемонстрированы привязки до и после обмена. -Сами объекты остаются на том же месте, то есть после создания они никогда не перемещаются в памяти. Просто замечательно, объект никогда не исчезнет: можно рассчитывать, что объект навсегда останется там, куда он был помещен при создании. (Сборщик мусора перерабатывает в фоновом режиме те объекты, которые больше не используются.) Ссылки на объекты (в данном случае `a1` и `a2`) можно заставить «смотреть в другую сторону», переназначив их привязку. Когда библиотека времени исполнения обнаруживает, что для какого-то объекта больше нет привязанных к нему ссылок, она может заново использовать выделенную под него память (этот процесс называется сбором мусора).[^1] Такое поведение - ![image-6-2-2](images/image-6-2-2.png) ***Рис. 6.2.*** *Привязки до и после обмена. В процессе обмена меняются привязки к ссылкам; сами объекты остаются на том же месте* -в корне отличается от семантики *значения* (например, `int`), в случае которого нет никаких косвенных изменений или привязок: каждое имя прочно закреплено за значением, которым манипулируют с помощью этого идентификатора. +Сами объекты остаются на том же месте, то есть после создания они никогда не перемещаются в памяти. Просто замечательно, объект никогда не исчезнет: можно рассчитывать, что объект навсегда останется там, куда он был помещен при создании. (Сборщик мусора перерабатывает в фоновом режиме те объекты, которые больше не используются.) Ссылки на объекты (в данном случае `a1` и `a2`) можно заставить «смотреть в другую сторону», переназначив их привязку. Когда библиотека времени исполнения обнаруживает, что для какого-то объекта больше нет привязанных к нему ссылок, она может заново использовать выделенную под него память (этот процесс называется сбором мусора).[^1] Такое поведение в корне отличается от семантики *значения* (например, `int`), в случае которого нет никаких косвенных изменений или привязок: каждое имя прочно закреплено за значением, которым манипулируют с помощью этого идентификатора. Ссылка, не привязанная к какому-либо объекту, – это «пустая» ссылка (`null`). При инициализации по умолчанию с помощью свойства `.init` ссылки на классы получают значение `null`. Можно сравнивать ссылку с константой `null` и присваивать ссылке значение `null`. Следующие проверки пройдут успешно: @@ -401,10 +399,11 @@ this(uint h) 1. *Выделение памяти*. Библиотека времени исполнения выделяет участок «сырой» памяти в куче, достаточный для размещения нестатических полей объекта. Память подо все объекты, основанные на классах, выделяется динамически – в отличие от C++, в D нет способа выделить для объекта память в стеке[^2]. Если выделить память не удалось, построение объекта прерывается: порождается исключительная ситуация. -*Инициализация полей*. Каждое поле инициализируется своим значением по умолчанию. Как уже говорилось, в качестве значения поля по умолчанию выступает значение, указанное при объявлении поля в виде `= значение`, или при отсутствии такой записи значение свойства `.init` типа поля. +2. *Инициализация полей*. Каждое поле инициализируется своим значением по умолчанию. Как уже говорилось, в качестве значения поля по умолчанию выступает значение, указанное при объявлении поля в виде `= значение`, или при отсутствии такой записи значение свойства `.init` типа поля. -2. *Брендирование*. После завершения инициализации полей значениями по умолчанию объекту присваивается статус полноправного экземпляра класса `T` (объект брендируется) еще *до* того, как будет вызван настоящий конструктор. -3. *Вызов конструктора*. Наконец, компилятор инициирует вызов подходящего конструктора. Если класс не определяет собственный конструктор, этот шаг пропускается. +3. *Брендирование*. После завершения инициализации полей значениями по умолчанию объекту присваивается статус полноправного экземпляра класса `T` (объект брендируется) еще *до* того, как будет вызван настоящий конструктор. + +4. *Вызов конструктора*. Наконец, компилятор инициирует вызов подходящего конструктора. Если класс не определяет собственный конструктор, этот шаг пропускается. Поскольку объект считается «живым» и правильно построенным сразу после инициализации по умолчанию, настоятельно рекомендуется использовать инициализирующие значения, которые всегда приводят объект в осмысленное состояние. Настоящий конструктор внесет затем свои поправки, приведя объект в другое интересное состояние (разумеется, также осмысленное). @@ -1375,7 +1374,9 @@ void main() void widgetize() { Widget w = new Widget; - .../* Использование w */... + ... + /* Использование w */ + ... } ``` @@ -1385,7 +1386,9 @@ void widgetize() void widgetize() { Widget w = new TextWidget; - .../* Использование w */... + ... + /* Использование w */ + ... } ``` @@ -1395,7 +1398,9 @@ void widgetize() void widgetize(string widgetClass) { Widget w = cast(Widget) Object.factory(widgetClass); - ... /* Использование w */... + ... + /* Использование w */ + ... } ``` @@ -2649,7 +2654,7 @@ D полностью поддерживает технику невиртуал [^7]: Ф. Брукс «Мифический человеко-месяц». – Символ-Плюс, 2000. [^8]: `rhs` (от right hand side – справа от) – значение, в выражении расположенное справа от оператора. Аналогично `lhs` (от left hand side – слева от) – значение, в выражении расположенное слева от оператора. – *Прим. ред.* [^9]: Интересно, что семантика использования `opCmp` та же, что и в функциях сравнения памяти и строк в языке C. – *Прим. науч. ред.* -[^10]: Виртуа льный метод – метод, который переопределяет другой метод или сам может быть переопределен. – *Прим. науч. ред.* +[^10]: Виртуальный метод – метод, который переопределяет другой метод или сам может быть переопределен. – *Прим. науч. ред.* [^11]: Это напоминает виртуа льное наследование в С++. – *Прим. науч. ред.* [^12]: Сходный механизм используется при возвращении функцией делегата и образовании замыканий, однако следует учитывать, что компилятор выполняет описанные действия строго для предопределенных языком случаев (таких как внутренние классы и делегаты). Попытка функции вернуть указатель на локальную переменную ни к чему хорошему не приведет. – *Прим. науч. ред.* [^13]: Аргументы конструктора при создании анонимного класса передаются сразу после ключевого слова `class`, а если создается анонимный класс, реализующий список интерфейсов, то эти интерфейсы указываются через запятую после имени суперкласса. Пример: `new class(arg1, arg2) BaseClass, Interface1, Interface2 {};`. – *Прим. науч. ред.* diff --git a/book/07-другие-пользовательские-типы/README.md b/book/07-другие-пользовательские-типы/README.md index 444a9cd..f19c04a 100644 --- a/book/07-другие-пользовательские-типы/README.md +++ b/book/07-другие-пользовательские-типы/README.md @@ -38,7 +38,7 @@ Применяя классы, основные типы и функции, можно написать много хороших программ. С параметризированными классами и функциями дело идет еще лучше. Но нередко мы с сожалением отмечаем, что по нескольким причинам классы не представляют собой инструмент с максимальной абстракцией типа. -Во-первых, классы подчиняются ссылочной семантике и из-за этого могут воплощать многие проектные решения не полностью или с ощутимыми накладными расходами. На практике трудно моделировать с помощью класса такую простую сущность, как точка с двумя или тремя координатами, если таких точек больше нескольких миллионов: разработчик оказывается перед непростым выбором – хорошая абстракция или приемлемое быстродействие. Кроме того, для линейной алгебры ссылочная семантика – большая морока. Попробуйте убедить математика или программиста-теоретика, что присваивание `a = b` должно делать из матрицы a лишь псевдоним матрицы `b`, а не отдельную копию! Даже такой простой тип, как массив, довольно накладно моделировать в виде класса в сравнении с мощной и лаконичной абстракцией массива, имеющейся в языке D (см. главу 4). Можно, конечно, сделать массивы «волшебными», но опыт то и дело показывает, что предоставлять множество «волшебных» типов, не воспроизводимых в пользовательском коде, – дурной тон и признак плохо спроектированного языка. Затраты на массив – всего два слова, а выделение памяти под экземпляр класса и использование дополнительного косвенного обращения означают большие накладные расходы по памяти и времени для всех примитивов массива. Даже такой простой тип, как `int`, нельзя выразить в виде класса дешево и элегантно (причем речь не об удобстве оператора). У такого класса, как `BigInt`, та же проблема: `a = b` делает нечто совершенно иное, +Во-первых, классы подчиняются ссылочной семантике и из-за этого могут воплощать многие проектные решения не полностью или с ощутимыми накладными расходами. На практике трудно моделировать с помощью класса такую простую сущность, как точка с двумя или тремя координатами, если таких точек больше нескольких миллионов: разработчик оказывается перед непростым выбором – хорошая абстракция или приемлемое быстродействие. Кроме того, для линейной алгебры ссылочная семантика – большая морока. Попробуйте убедить математика или программиста-теоретика, что присваивание `a = b` должно делать из матрицы `a` лишь псевдоним матрицы `b`, а не отдельную копию! Даже такой простой тип, как массив, довольно накладно моделировать в виде класса в сравнении с мощной и лаконичной абстракцией массива, имеющейся в языке D (см. главу 4). Можно, конечно, сделать массивы «волшебными», но опыт то и дело показывает, что предоставлять множество «волшебных» типов, не воспроизводимых в пользовательском коде, – дурной тон и признак плохо спроектированного языка. Затраты на массив – всего два слова, а выделение памяти под экземпляр класса и использование дополнительного косвенного обращения означают большие накладные расходы по памяти и времени для всех примитивов массива. Даже такой простой тип, как `int`, нельзя выразить в виде класса дешево и элегантно (причем речь не об удобстве оператора). У такого класса, как `BigInt`, та же проблема: `a = b` делает нечто совершенно иное, чем соответствующая операция присваивания для типа `int`. Во-вторых, классы живут вечно, а значит, с их помощью трудно моделировать ресурсы с выраженным *конечным* временем жизни (такие как дескрипторы файлов, дескрипторы графического контекста, мьютексы, сокеты и т. д.). Работая с такими ресурсами как с классами, нужно постоянно быть начеку, чтобы не забыть своевременно освободить инкапсулированные ресурсы с помощью метода, вроде `close` или `dispose`. В таких случаях обычно помогает инструкция `scope` (см. раздел 3.13), но лучше, когда подобная контекстная семантика инкапсулирована в типе – раз и навсегда. diff --git a/book/11-расширение-масштаба/README.md b/book/11-расширение-масштаба/README.md index 9b898a4..fdb113d 100644 --- a/book/11-расширение-масштаба/README.md +++ b/book/11-расширение-масштаба/README.md @@ -53,17 +53,17 @@ *Таблица 11.1. Для различения файлов с исходным кодом на D используют ся метки порядка байтов. Шаблоны проверяются сверху вниз, первое же совпадение при сопоставлении устанавливает кодировку файла. `xx` – любое ненулевое значение байта* -|Если первые байты...|...то кодировка файла – ...|Игнорировать эти байты?| -|-|-|:-:| -|`00 00 FE FF`|UTF-32 с прямым порядком байтов[^1]|✓| -|`FF FE 00 00`|UTF-32 с обратным порядком байтов[^2]|✓| -|`FE FF`|UTF-16 с прямым порядком байтов|✓| -|`FF FE`|UTF-16 с обратным порядком байтов|✓| -|`00 00 00 xx`|UTF-32 с прямым порядком байтов|| -|`xx 00 00 00`|UTF-32 с обратным порядком байтов|| -|`00 xx`|UTF-16 с прямым порядком байтов|| -|`xx 00`|UTF-16 с обратным порядком байтов|| -|Что-то другое|UTF-8|| +| Если первые байты... | ...то кодировка файла – ... | Игнорировать эти байты? | +| --- | --- | :-: | +| `00 00 FE FF` | UTF-32 с прямым порядком байтов[^1] | ✓ | +| `FF FE 00 00` | UTF-32 с обратным порядком байтов[^2] | ✓ | +| `FE FF` | UTF-16 с прямым порядком байтов | ✓ | +| `FF FE` | UTF-16 с обратным порядком байтов | ✓ | +| `00 00 00 xx` | UTF-32 с прямым порядком байтов | | +| `xx 00 00 00` | UTF-32 с обратным порядком байтов | | +| `00 xx` | UTF-16 с прямым порядком байтов | | +| `xx 00` | UTF-16 с обратным порядком байтов | | +| Что-то другое | UTF-8 | | В некоторых файлах метка порядка байтов отсутствует, но у D есть средство, позволяющее автоматически недвусмысленно определить кодировку. Процедура автоопределения тонко использует тот факт, что любой правильно построенный модуль на D должен начинаться хотя бы с нескольких знаков, встречающихся в кодировке ASCII, то есть с кодовых точек Юникода со значением меньше 128. Ведь в соответствии с грамматикой D правильно построенный модуль должен начинаться или с ключевого слова языка D (состоящего из знаков Юникода с ASCII-кодами), или с ASCII-пробела, или с комментария, который начинается с ASCII-знака `/`, или с пары директив, начинающихся с `#`, которые также должны состоять из ASCII-знаков. Если выполнить проверку на соответствие шаблонам из табл. 11.1, перебирая эти шаблоны сверху вниз, первое же совпадение недвусмысленно укажет кодировку. Если кодировка определена ошибочно, вреда от этого все равно не будет – файл, несомненно, и так ошибочен, поскольку начинается со знаков, которые не может содержать корректный код на D. @@ -642,8 +642,7 @@ module my_widget; В этом месте определяются атрибуты `@safe`, `@trusted` и `@system`, которые позволяют модулю объявить о своем уровне безопасности. (Такой подход не нов; в языке Модула-3 применяется тот же подход, чтобы отличить небезопасные и безопасные модули.) -Код, размещенный после атрибута `@safe`, обязуется использовать ин -струкции лишь из безопасного подмножества D, что означает: +Код, размещенный после атрибута `@safe`, обязуется использовать инструкции лишь из безопасного подмножества D, что означает: - никаких преобразований указателей в неуказатели (например, `int`), и наоборот; - никаких преобразований между указателями, типы которых не имеют отношения друг к другу; @@ -959,32 +958,32 @@ void fun() *Таблица 11.2. Обзор стандартных модулей* -|Модуль|Описание| -|-|-| -|`std.algorithm`|Этот модуль можно считать основой мощнейшей способности к обобщению, присущей языку. Вдохновлен стандартной библиотекой шаблонов C++ (Standard Template Library, STL). Содержит больше 70 важных алгоритмов, реализованных очень обобщенно. Большинство алгоритмов применяются к структурированным последовательностям идентичных элементов. В STL базовой абстракцией последовательности служит итератор, соответствующий примитив D – *диапазон*, для которого краткого обзора явно недостаточно; полное введение в диапазоны D доступно в Интернете| -|`std.array`|Функции для удобства работы с массивами| -|`std.bigint`|Целое число переменной длины с сильно оптимизированной реализацией| -|`std.bitmanip`|Типы и часто используемые функции для низкоуровневых битовых операций| -|`std.concurrency`|Средства параллельных вычислений (см. главу 13)| -|`std.container`|Реализации разнообразных контейнеров| -|`std.conv`|Универсальный магазин, удовлетворяющий любые нужды по преобразованиям. Здесь определены многие полезные функции, такие как `to` и `text`| -|`std.datetime`|Полезные вещи, связанные с датой и временем| -|`std.file`|Файловые утилиты. Зачастую этот модуль манип улирует файлами целиком; например, в нем есть функция `read`, которая считывает весь файл, при этом `std.file.read` и понятия не имеет о том, что можно открывать файл и читать его маленькими порциями (об этом заботится модуль `std.stdio`, см. далее)| -|`std.functional`|Примитивы для определения и композиции функций| -|`std.getopt`|Синтаксический анализ командной строки| -|`std.json`|Обработка данных в формате JSON| -|`std.math`|В высшей степени оптимизированные, часто используемые математические функции| -|`std.numeric`|Общие числовые алгоритмы| -|`std.path`|Утилиты для манипуляций с путями к файлам| -|`std.random`|Разнообразные генераторы случайных чисел| -|`std.range`|Определения и примитивы классификации, имеющие отношение к диапазонам| -|`std.regex`|Обработчик регулярных выражений| -|`std.stdio`|Стандартные библиотечные средства ввода/вывода, построенные на основе библиотеки `stdio` языка C. Входные и выходные файлы предоставляют интерфейсы в стиле диапазонов, благодаря чему многие алгоритмы, определенные в модуле `std.algorithm`, могут работать непосредственно с файлами| -|`std.string`|Функции, специфичные для строк. Строки тесно связаны с `std.algorithm`, так что модуль `std.string`, относительно небольшой по размеру, в основном лишь ссылается (определяя псевдонимы) на части `std.algorithm`, применимые к строкам| -|`std.traits`|Качества типов и интроспекция| -|`std.typecons`|Средства для определения новых типов, таких как `Tuple`| -|`std.utf`|Функции для манипулирования кодировками UTF| -|`std.variant`|Объявление типа `Variant`, который является контейнером для хранения значения любого типа. `Variant` – это высокоуровневый `union`| +| Модуль | Описание | +| --- | --- | +| `std.algorithm` | Этот модуль можно считать основой мощнейшей способности к обобщению, присущей языку. Вдохновлен стандартной библиотекой шаблонов C++ (Standard Template Library, STL). Содержит больше 70 важных алгоритмов, реализованных очень обобщенно. Большинство алгоритмов применяются к структурированным последовательностям идентичных элементов. В STL базовой абстракцией последовательности служит итератор, соответствующий примитив D – *диапазон*, для которого краткого обзора явно недостаточно; полное введение в диапазоны D доступно в Интернете | +| `std.array` | Функции для удобства работы с массивами | +| `std.bigint` | Целое число переменной длины с сильно оптимизированной реализацией | +| `std.bitmanip` | Типы и часто используемые функции для низкоуровневых битовых операций | +| `std.concurrency` | Средства параллельных вычислений (см. главу 13) | +| `std.container` | Реализации разнообразных контейнеров | +| `std.conv` | Универсальный магазин, удовлетворяющий любые нужды по преобразованиям. Здесь определены многие полезные функции, такие как `to` и `text` | +| `std.datetime` | Полезные вещи, связанные с датой и временем | +| `std.file` | Файловые утилиты. Зачастую этот модуль манип улирует файлами целиком; например, в нем есть функция `read`, которая считывает весь файл, при этом `std.file.read` и понятия не имеет о том, что можно открывать файл и читать его маленькими порциями (об этом заботится модуль `std.stdio`, см. далее) | +| `std.functional` | Примитивы для определения и композиции функций | +| `std.getopt` | Синтаксический анализ командной строки | +| `std.json` | Обработка данных в формате JSON | +| `std.math` | В высшей степени оптимизированные, часто используемые математические функции | +| `std.numeric` | Общие числовые алгоритмы | +| `std.path` | Утилиты для манипуляций с путями к файлам | +| `std.random` | Разнообразные генераторы случайных чисел | +| `std.range` | Определения и примитивы классификации, имеющие отношение к диапазонам | +| `std.regex` | Обработчик регулярных выражений | +| `std.stdio` | Стандартные библиотечные средства ввода/вывода, построенные на основе библиотеки `stdio` языка C. Входные и выходные файлы предоставляют интерфейсы в стиле диапазонов, благодаря чему многие алгоритмы, определенные в модуле `std.algorithm`, могут работать непосредственно с файлами | +| `std.string` | Функции, специфичные для строк. Строки тесно связаны с `std.algorithm`, так что модуль `std.string`, относительно небольшой по размеру, в основном лишь ссылается (определяя псевдонимы) на части `std.algorithm`, применимые к строкам | +| `std.traits` | Качества типов и интроспекция | +| `std.typecons` | Средства для определения новых типов, таких как `Tuple` | +| `std.utf` | Функции для манипулирования кодировками UTF | +| `std.variant` | Объявление типа `Variant`, который является контейнером для хранения значения любого типа. `Variant` – это высокоуровневый `union` | [В начало ⮍](#11-9-стандартная-библиотека-d) [Наверх ⮍](#11-расширение-масштаба)