Здравствуйте! Записался на ваш курс, но не понимаю как произвести оплату. Надо ли писать заявление и, если да, то куда отправлять? как я получу диплом о профессиональной переподготовке? |
Структуры
Проект к данной лекции Вы можете скачать здесь.
Развернутые и ссылочные типы
В ООП главная роль, которую играют классы, состоит в задании типа данных. В этой лекции, как и во многих других, говоря о классах, будем иметь в виду именно эту роль. Хотя следует помнить, что некоторые классы могут играть только одну роль - роль модуля, и для них невозможно создавать объекты. Такие сервисные классы подробно рассматривались в предыдущей лекции, а примеры таких классов появлялись многократно.
Итак, будем рассматривать классы, задающие тип данных. Такой класс Т представляет описание множества объектов - экземпляров класса, задавая их свойства и поведение. Если класс представляет собой текст - статическое описание, то объекты класса создаются динамически в процессе работы программы. Как правило, объекты класса Т создаются в других классах, являющихся клиентами класса Т. Рассмотрим в клиентском классе объявление объекта класса T, выполненное с инициализацией:
T x = new T();
Напомню, как выполняется этот оператор, создающий объект класса Т. Объектам нужна память, чтобы с ними можно было работать. Рассмотрим две классические стратегии выделения памяти и связывания объекта, создаваемого в памяти, и сущности, объявленной в тексте. Есть два типа памяти - стек ( stack ) и куча ( heap ). Сущности x в стеке всегда отводится память, но какая память - зависит от того, к развернутому или ссылочному типу относится класс Т.
Определение 1.Если класс T относится к развернутому типу, то в стеке сущности x отводится память, необходимая объекту класса T. Говорят, что объект разворачивается на памяти, жестко связанной с сущностью x. Эту память сущность x ни с кем не разделяет.
Определение 2.Если класс T относится к ссылочному типу, то память объекту отводится в куче, а в стеке сущности x отводится дополнительная память, содержащая ссылку на объект.
Для развернутого типа характерно то, что каждая сущность ни с кем не разделяет свою память; сущность жестко связывается со своим объектом. В этом случае сущность и объект можно и не различать, они становятся неделимым понятием. Для ссылочных типов ситуация иная - несколько сущностей могут ссылаться на один и тот же объект. Такие сущности разделяют память и являются разными именами одного объекта. Полезно понимать разницу между сущностью, заданной ссылкой, и объектом, на который в текущий момент указывает ссылка. Заметим, что тип объекта в куче может не совпадать с базовым типом сущности, заданным классом T. Более того, типы объектов, с которыми динамически связывается сущность, могут быть различными, хотя и требуется определенное согласование типов. Подробнее эти вопросы будут обсуждаться при рассмотрении наследования.
Развернутые и ссылочные типы порождают две различные семантики. Рассмотрим присваивание:
y = x;
Когда сущность y принадлежит развернутому типу, значения полей объекта, связанного с сущностью y, при присваивании изменяются, получая значения полей объекта, связанного с x. Напомним, что поля y хранятся в стеке.
Когда сущность y принадлежит ссылочному типу, происходит присваивание ссылок. Ссылка y получает значение ссылки x, и обе они после присваивания указывают на один и тот же объект. Объект в куче, на который до присваивания указывала ссылка y, теряет одну из своих ссылок и, возможно, становится "висячим" объектом без ссылок, становясь добычей для сборщика мусора.
Рассмотрим операцию проверки объектов на эквивалентность:
if (x == y)
Для развернутых типов объекты x и y эквивалентны, если эквивалентны значения всех их полей. Для ссылочных типов объекты x и y эквивалентны, если эквивалентны значения ссылок.
Язык программирования должен позволять программисту в момент определения класса указать, к развернутому или ссылочному типу относится класс. В языке C# это делается следующим образом. Если объявление класса задается с использованием служебного слова class, то такой класс относится к ссылочным типам.
public class A { ... }
Если объявление класса задается с использованием служебного слова struct, то такой класс относится к развернутым типам, которые также называют значимыми типами или value типами.
public struct B { ... }
Напомню: к значимым типам относятся все встроенные арифметические типы, булевский тип, перечисления, структуры. К ссылочным типам относятся массивы, строки, классы. Так можно ли в C# спроектировать свой собственный класс так, чтобы он относился к значимым типам? Ответ на этот вопрос - положительный, хотя и с рядом оговорок. Для того чтобы класс отнести к значимым типам, его достаточно объявить как структуру.
Классы и структуры
Структура - это частный случай класса. Исторически структуры используются в языках программирования раньше классов. В языках PL/1, C, Pascal они представляли собой только совокупность данных (полей класса), но не включали ни методов, ни событий. В языке С++ возможности структур были существенно расширены, и они стали настоящими классами, хотя и c некоторыми ограничениями. В языке C# сохранен именно такой подход к структурам.
Чем следует руководствоваться, делая выбор между структурой и классом? Полагаю, можно пользоваться следующими правилами:
- если необходимо отнести класс к развернутому типу, делайте его структурой;
- если у класса число полей относительно невелико, а число возможных объектов относительно велико, делайте его структурой. В этом случае память объектам будет отводиться в стеке, не будут создаваться лишние ссылки, что позволит повысить эффективность использования памяти;
- в остальных случаях проектируйте настоящие классы.
Поскольку на структуры накладываются дополнительные ограничения, может возникнуть необходимость в компромиссе - согласиться с ограничениями и использовать структуру либо пожертвовать развернутостью и эффективностью и работать с настоящим классом. Стоит отметить: когда говорится, что встроенные типы - int и другие - представляют собой классы, то, на самом деле, речь идет о классах, реализованных в виде структур.
Структуры
Рассмотрим теперь более подробно вопросы описания структур, их синтаксиса, семантики и тех особенностей, что отличают их от классов.
Синтаксис структур
Синтаксис объявления структуры аналогичен синтаксису объявления класса:
[атрибуты][модификаторы]struct имя_структуры[:список_интерфейсов] {тело_структуры}
Какие изменения произошли в синтаксисе заголовка структуры в сравнении с синтаксисом класса? Их немного. Перечислим их:
- ключевое слово class изменено на слово struct ;
- список родителей, который мог включать имя родительского класса, заменен списком, допускающим только имена интерфейсов. Для структур не может быть задан родитель (класс или структура). Заметьте, структура может наследовать интерфейсы;
- для структур не применимы модификаторы abstract и sealed. Причиной является отсутствие механизма наследования.
Все, что может быть вложено в тело класса, может быть вложено и в тело структуры: поля, методы, конструкторы и прочее, включая классы и интерфейсы.
Аналогично классу, структура может иметь статические и нестатические поля и методы, может иметь несколько конструкторов, в том числе статические и закрытые конструкторы. Для структур можно создавать собственные константы, используя поля с атрибутом readonly и статический конструктор. Структуры похожи на классы по своему описанию и ведут себя сходным образом, хотя и имеют существенные различия в семантике присваивания и проверке эквивалентности.
Рассмотрим ограничения, накладываемые на структуры.
Структуры и наследование
Самое серьезное ограничение связано с ограничением наследования. Для структуры не может быть задан родительский класс или родительская структура. У структуры не может быть наследников. Конечно, всякая структура, как и любой класс в C#, является наследником класса object, наследуя все свойства и методы этого класса. Структура может наследовать один или несколько интерфейсов, реализуя методы этих интерфейсов.
С чем связано такое серьезное ограничение. Одно из объяснений состоит в следующем. Рассмотрим присваивание
y = x;
При присваивании допустимо, чтобы y принадлежал родительскому классу, а x - его потомку. При ссылочном присваивании никаких проблем не возникает. В результате присваивания ссылка y будет указывать на объект потомка, который помимо полей родителя может иметь дополнительные поля. Главное, что все поля родителя будут определены. После такого присваивания возможно и обратное присваивание с приведением типа
x = (T)y;
Для структур, принадлежащих к значимому типу, подобное присваивание между родителем и потомком было бы невозможным. Родителю нельзя присвоить объект потомка, поскольку у родителя меньше полей, чем у потомка, поэтому можно было бы присвоить лишь часть полей. А уж в обратную сторону операция присваивания была бы принципиально невозможна. И это одна из причин, по которой решились отказаться от наследования для значимых типов в языке C#.
Структуры и инициализация полей
Второе серьезное ограничение связано с процессом создания объектов и их инициализацией.
Рассмотрим объявление:
int[] x; int y = x[0];
Это объявление корректно. А теперь рассмотрим похожее объявление:
int u; int v = u;
Это объявление некорректно, возникнет ошибка периода компиляции, сообщающая, что переменная v не инициализирована и не может быть использована в вычислениях. В чем разница? Сущность x - это массив и, следовательно, относится к ссылочным типам. При создании объектов ссылочного типа все поля объектов инициализируются некоторыми значениями. В данном конкретном случае все элементы целочисленного типа получат значение по умолчанию, равное нулю. Сущность u арифметического типа относится к развернутому типу. Все поля объектов развернутого типа должны быть инициализированы явно или стандартным конструктором по умолчанию в момент создания объекта. Разная семантика инициализации приводит к следующим ограничениям на структуры.
- Если в классе поля можно объявлять с инициализацией, то поля структуры не допускают инициализацию при их объявлении. Объявление с инициализацией полей означало бы их неявную инициализацию, а потому запрещено.
- Стандартный конструктор по умолчанию у структур имеется, он в стеке создает объект, инициализируя поля созданного объекта значениями по умолчанию. Этот конструктор нельзя заменить собственным конструктором без аргументов.