This commit is contained in:
Alexander Zhirov 2023-02-27 01:38:33 +03:00
parent 151d77ee54
commit ca968c9fa1
1 changed files with 84 additions and 0 deletions

View File

@ -0,0 +1,84 @@
# 7. Другие пользовательские типы
- [7.1. Структуры](#7-1-структуры)
- [7.1.1. Семантика копирования]()
- [7.1.2. Передача объекта-структуры в функцию]()
- [7.1.3. Жизненный цикл объекта-структуры]()
- [7.1.3.1. Конструкторы]()
- [7.1.3.2. Делегирование конструкторов]()
- [7.1.3.3. Алгоритм построения]()
- [7.1.3.4. Конструктор копирования this(this)]()
- [7.1.3.5. Аргументы в пользу this(this)]()
- [7.1.3.6. Уничтожение объекта и освобождение памяти]()
- [7.1.3.7. Алгоритм уничтожения структуры]()
- [7.1.4. Статические конструкторы и деструкторы]()
- [7.1.5. Методы]()
- [7.1.5.1. Оператор присваивания]()
- [7.1.5.2. Сравнение структур на равенство]()
- [7.1.6. Статические внутренние элементы]()
- [7.1.7. Спецификаторы доступа]()
- [7.1.8. Вложенность структур и классов]()
- [7.1.9. Структуры, вложенные в функции]()
- [7.1.10. Порождение подтипов в случае структур. Атрибут @disable]()
- [7.1.11. Взаимное расположение полей. Выравнивание]()
- [7.1.11.1. Атрибут align]()
- [7.2. Объединение]()
- [7.3. Перечисляемые значения]()
Применяя классы, основные типы и функции, можно написать много хороших программ. С параметризированными классами и функциями дело идет еще лучше. Но нередко мы с сожалением отмечаем, что по нескольким причинам классы не представляют собой инструмент с максимальной абстракцией типа.
Во-первых, классы подчиняются ссылочной семантике и из-за этого могут воплощать многие проектные решения не полностью или с ощутимыми накладными расходами. На практике трудно моделировать с помощью класса такую простую сущность, как точка с двумя или тремя координатами, если таких точек больше нескольких миллионов: разработчик оказывается перед непростым выбором хорошая абстракция или приемлемое быстродействие. Кроме того, для линейной алгебры ссылочная семантика большая морока. Попробуйте убедить математика или программиста-теоретика, что присваивание `a = b` должно делать из матрицы a лишь псевдоним матрицы `b`, а не отдельную копию! Даже такой простой тип, как массив, довольно накладно моделировать в виде класса в сравнении с мощной и лаконичной абстракцией массива, имеющейся в языке D (см. главу 4). Можно, конечно, сделать массивы «волшебными», но опыт то и дело показывает, что предоставлять множество «волшебных» типов, не воспроизводимых в пользовательском коде, дурной тон и признак плохо спроектированного языка. Затраты на массив всего два слова, а выделение памяти под экземпляр класса и использование дополнительного косвенного обращения означают большие накладные расходы по памяти и времени для всех примитивов массива. Даже такой простой тип, как `int`, нельзя выразить в виде класса дешево и элегантно (причем речь не об удобстве оператора). У такого класса, как `BigInt`, та же проблема: `a = b` делает нечто совершенно иное,
чем соответствующая операция присваивания для типа `int`.
Во-вторых, классы живут вечно, а значит, с их помощью трудно моделировать ресурсы с выраженным *конечным* временем жизни (такие как дескрипторы файлов, дескрипторы графического контекста, мьютексы, сокеты и т. д.). Работая с такими ресурсами как с классами, нужно постоянно быть начеку, чтобы не забыть своевременно освободить инкапсулированные ресурсы с помощью метода, вроде `close` или `dispose`. В таких случаях обычно помогает инструкция `scope` (см. раздел 3.13), но лучше, когда подобная контекстная семантика инкапсулирована в типе раз и навсегда.
В-третьих, классы это механизм для довольно «тяжелых» и высокоуровневых абстракций, то есть они не позволяют легко выражать «легковесные» абстракции вроде перечисляемых типов или псевдонимов для заданного типа.
D не был бы настоящим языком для системного программирования, если бы предоставлял для выражения абстракций только классы. Кроме классов в запасе у D есть структуры (типы-значения, сравнимые с классами по мощности, но с семантикой значения и без полиморфизма), типы `enum` (легковесные перечисляемые типы и простые константы), объединения (низкоуровневое хранилище с перекрыванием для разных типов) и вспомогательные механизмы определения типов, такие как `alias`. Все эти средства последовательно рассматриваются в этой главе.
[В начало ⮍](#6-классы-объектно-ориентированный-стиль)
## 7.1. Структуры
Структуры позволяют определять простые, инкапсулированные типы-значения. Удобная аналогия тип `int`: значение типа `int` это 4 байта, допускающие определенные операции. В `int` нет никакого скрытого состояния и никаких косвенных обращений, и две переменные типа `int` всегда ссылаются на разные значения[^1]. Соглашение о структурах исключает динамический полиморфизм, переопределение методов, наследование и бесконечное время жизни. Структура это преувеличенный тип `int`.
Как вы помните, класс ведет себя как ссылка (см. раздел 6.2), то есть вы всегда манипулируете объектом посредством ссылки на него, причем копирование ссылок лишь увеличивает количество ссылок на тот же объект без дублирования самого объекта. А структура это тип-значение, то есть, по сути, ведет себя «как `int`»: имя жестко привязано к представленному им значению, а при копировании значения структуры на самом деле копируется целый объект, а не только ссылка.
Определяют структуру так же, как класс, за исключением следующих моментов:
- вместо ключевого слова `class` используется ключевое слово `struct`;
- свойственное классам наследование и реализация интерфейсов запрещены, то есть в определении структуры нельзя указать `:BaseType` или `:Interface`, и очевидно, что внутри структуры не определена ссылка `super`;
- методы структуры нельзя переопределять все методы являются финальными (вы можете указать в определении метода структуры ключевое слово `final`, но это было бы совершенно излишне);
- нельзя применять к структуре инструкцию `synchronized` (см. главу 13);
- структуре запрещается определять конструктор по умолчанию `this()` (см. раздел 7.1.3.1);
- структуре разрешается определять конструктор копирования (postblit constructor) `this(this)` (см. раздел 7.1.3.4);
- запрещен спецификатор доступа `protected` (иначе предполагалось бы наличие структур-потомков).
Определим простую структуру:
```d
struct Widget
{
// Константа
enum fudgeFactor = 0.2;
// Разделяемое неизменяемое значение
static immutable defaultName = "Виджет";
// Некоторое состояние, память под которое выделяется для каждого экземпляра класса Widget
string name = defaultName;
uint width, height;
// Статический метод
static double howFudgy()
{
return fudgeFactor;
}
// Метод
void changeName(string another)
{
name = another;
}
}
```
[В начало ⮍](#7-1-структуры) [Наверх ⮍](#7-другие-пользовательские-типы)
[^1]: Не считая эквивалентных имен, создаваемых с помощью `alias`, о чем мы еще поговорим в этой главе (см. раздел 7.4).