Compare commits

...

10 commits

Author SHA1 Message Date
Тарас
f0f0c72675 11 - fix table 11.2 2025-01-13 09:40:57 +01:00
Тарас
8f7b9d5087 11 - fix text 11.2.2 2025-01-13 00:46:21 +01:00
Тарас
4299579a93 11 - fix table 11.1 2025-01-12 10:48:03 +02:00
Тарас
45190add84 07 - fix md 2025-01-08 12:17:04 +01:00
Тарас
31a3cdc754 06 - fix notes text 2025-01-02 04:42:33 +01:00
Тарас
22708324a5 06 - fix code formatting 2025-01-02 04:34:15 +01:00
Тарас
09fcedd370 06 - fix 6.3.3 list 2025-01-02 04:13:24 +01:00
Тарас
85400768e2 06 - fix image 6.2 2025-01-02 04:02:42 +01:00
Тарас
723bb7dc56 05 - fix 5.10.1 2024-12-31 07:32:10 +01:00
Тарас
902c92d179 05 - fix 5.9 , 2024-12-31 07:22:21 +01:00
4 changed files with 56 additions and 52 deletions

View file

@ -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`.

View file

@ -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 */
...
}
```

View file

@ -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), но лучше, когда подобная контекстная семантика инкапсулирована в типе раз и навсегда.

View file

@ -54,7 +54,7 @@
*Таблица 11.1. Для различения файлов с исходным кодом на D используют ся метки порядка байтов. Шаблоны проверяются сверху вниз, первое же совпадение при сопоставлении устанавливает кодировку файла. `xx` любое ненулевое значение байта*
| Если первые байты... | ...то кодировка файла ... | Игнорировать эти байты? |
|-|-|:-:|
| --- | --- | :-: |
| `00 00 FE FF` | UTF-32 с прямым порядком байтов[^1] | ✓ |
| `FF FE 00 00` | UTF-32 с обратным порядком байтов[^2] | ✓ |
| `FE FF` | UTF-16 с прямым порядком байтов | ✓ |
@ -642,8 +642,7 @@ module my_widget;
В этом месте определяются атрибуты `@safe`, `@trusted` и `@system`, которые позволяют модулю объявить о своем уровне безопасности. (Такой подход не нов; в языке Модула-3 применяется тот же подход, чтобы отличить небезопасные и безопасные модули.)
Код, размещенный после атрибута `@safe`, обязуется использовать ин
струкции лишь из безопасного подмножества D, что означает:
Код, размещенный после атрибута `@safe`, обязуется использовать инструкции лишь из безопасного подмножества D, что означает:
- никаких преобразований указателей в неуказатели (например, `int`), и наоборот;
- никаких преобразований между указателями, типы которых не имеют отношения друг к другу;
@ -960,7 +959,7 @@ void fun()
*Таблица 11.2. Обзор стандартных модулей*
| Модуль | Описание |
|-|-|
| --- | --- |
| `std.algorithm` | Этот модуль можно считать основой мощнейшей способности к обобщению, присущей языку. Вдохновлен стандартной библиотекой шаблонов C++ (Standard Template Library, STL). Содержит больше 70 важных алгоритмов, реализованных очень обобщенно. Большинство алгоритмов применяются к структурированным последовательностям идентичных элементов. В STL базовой абстракцией последовательности служит итератор, соответствующий примитив D *диапазон*, для которого краткого обзора явно недостаточно; полное введение в диапазоны D доступно в Интернете |
| `std.array` | Функции для удобства работы с массивами |
| `std.bigint` | Целое число переменной длины с сильно оптимизированной реализацией |