Статические поля и методы класса
Ранее говорилось, что когда конструктор класса создает новый объект, то в памяти создается структура данных с полями, определяемыми классом. Уточним теперь это описание. Не все поля отражаются в структуре объекта. У класса могут быть поля, связанные не с объектами, а с самим классом. Эти поля объявляются как статические с модификатором static. Статические поля доступны всем методам класса. Независимо от того, какой объект вызвал метод, используются одни и те же статические поля, позволяя методу использовать информацию, созданную другими объектами класса. Статические поля представляют общий информационный пул для всех объектов классов, позволяя извлекать и создавать общую информацию. Например, у класса Person может быть статическое поле message, в котором каждый объект может оставить сообщение для других объектов класса.
Аналогично полям, у класса могут быть и статические методы, объявленные с модификатором static. Такие методы не используют информацию о свойствах конкретных объектов класса - они обрабатывают общую для класса информацию, хранящуюся в его статических полях. Например, в классе Person может быть статический метод, обрабатывающий данные из статического поля message. Другим частым случаем применения статических методов является ситуация, когда класс предоставляет свои сервисы объектам других классов. Таковым является класс Math из библиотеки FCL, который не имеет собственных полей - все его статические методы работают с объектами арифметических классов.
Как вызываются статические поля и методы? Напомню, что всякий вызов метода в объектных вычислениях имеет вид x.F(...); где x - это цель вызова. Обычно целью вызова является объект, который вызывает методы класса, не являющиеся статическими (динамическими или экземплярными). В этом случае поля целевого объекта доступны методу и служат глобальным источником информации. Если же необходимо вызвать статический метод (поле), то целью должен быть сам класс. Можно полагать, что для каждого класса автоматически создается статический объект с именем класса, содержащий статические поля и обладающий статическими методами. Этот объект и его методы доступны и тогда, когда ни один другой динамический объект класса еще не создан. (Полагаю, что разумно обратиться к лекции 2, а именно, к разделу, описывающему точку большого взрыва, процесс вычислений в ОО-системах, вызовы статических методов.)
Константы
В классе могут быть объявлены константы. Синтаксис объявления приводился в лекции 5. Константы фактически являются статическими полями, доступными только для чтения, значения которых задаются при инициализации. Однако задавать модификатор static для констант не только не нужно, но и запрещено. В нашем классе Person была задана константа Child_Max, характеризующая максимальное число детей у персоны.
Никаких проблем не возникает, когда речь идет о константах встроенных типов, таких, как Child_Max. Однако совсем не просто определить в языке C# константы собственного класса. Этот вопрос будет подробно рассматриваться чуть позже, когда речь пойдет о проектировании класса Rational.
16. Лекция: Классы
16.4
Конструкторы класса
Конструктор - неотъемлемый компонент класса. Нет классов без конструкторов. Конструктор представляет собой специальный метод класса, позволяющий создавать объекты класса. Одна из синтаксических особенностей этого метода в том, что его имя должно совпадать с именем класса. Если программист не определяет конструктор класса, то к классу автоматически добавляется конструктор по умолчанию - конструктор без аргументов. Заметьте, что если программист сам создает один или несколько конструкторов, то автоматического добавления конструктора без аргументов не происходит.
Как и когда происходит создание объектов? Чаще всего, при объявлении сущности в момент ее инициализации. Давайте обратимся к нашему последнему примеру и рассмотрим создание трех объектов класса Person:
Person pers1 = new Person(), pers2 = new Person();Person pers3= new Person("Петрова");Сущности pers1, pers2 и pers3 класса Person объявляются с инициализацией, задаваемой унарной операцией new, которой в качестве аргумента передается конструктор класса Person. У класса может быть несколько конструкторов - это типичная практика, - отличающихся сигнатурой. В данном примере в первой строке вызывается конструктор без аргументов, во второй строке для сущности pers3 вызывается конструктор с одним аргументом типа string. Разберем в деталях процесс создания:
- первым делом для сущности pers создается ссылка, пока висячая, со значением null;
- затем в динамической памяти создается объект - структура данных с полями, определяемыми классом Person. Поля объекта инициализируются значениями по умолчанию: ссылочные поля - значением null, арифметические - нулями, строковые - пустой строкой. Эту работу выполняет конструктор по умолчанию, который, можно считать, всегда вызывается в начале процесса создания. Заметьте, если инициализируется переменная значимого типа, то все происходит аналогичным образом, за исключением того, что объект создается в стеке;
- если поля класса проинициализированы, как в нашем примере, то выполняется инициализация полей заданными значениями;
- если вызван конструктор с аргументами, то начинает выполняться тело этого конструктора. Как правило, при этом происходит инициализация отдельных полей класса значениями, переданными конструктору. Так, поле fam объекта pers3 получает значение "Петрова";
- На заключительном этапе ссылка связывается с созданным объектом.
Процесс создания объектов становится сложнее, когда речь идет об объектах, являющихся потомками некоторого класса. В этом случае, прежде чем создать сам объект, нужно вызвать конструктор, создающий родительский объект. Но об этом мы еще поговорим при изучении наследования. (Ключевое слово new используется в языке для двух разных целей. Во-первых, это имя операции, запускающей только что описанный процесс создания объекта. Во-вторых, это модификатор класса или метода. Роль new как модификатора будет выяснена при рассмотрении наследования.)
Зачем классу нужно несколько конструкторов? Дело в том, что, в зависимости от контекста и создаваемого объекта, может требоваться различная инициализация его полей. Перегрузка конструкторов и обеспечивает решение этой задачи.
Немного экзотики, связанной с конструкторами. Конструктор может быть объявлен с атрибутом private. Понятно, что в этом случае внешний пользователь не может воспользоваться им для создания объектов. Но это могут делать методы класса, создавая объекты для собственных нужд со специальной инициализацией. Пример такого конструктора будет дан позже.
В классе можно объявить статический конструктор с атрибутом static. Он вызывается автоматически - его не нужно вызывать стандартным образом. Точный момент вызова не определен, но гарантируется, что вызов произойдет до создания первого объекта класса. Такой конструктор может выполнять некоторую предварительную работу, которую нужно выполнить один раз, например, связаться с базой данных, заполнить значения статических полей класса, создать константы класса, выполнить другие подобные действия. Статический конструктор, вызываемый автоматически, не должен иметь модификаторов доступа. Вот пример объявления такого конструктора в классе Person:
static Person(){ Console.WriteLine("Выполняется статический конструктор!");}В нашей тестирующей процедуре, работающей с объектами класса Person, этот конструктор вызывается первым, и первым появляется сообщение этого конструктора.
Подводя итоги, можно отметить, что объекты создаются динамически в процессе выполнения программы - для создания объекта всегда вызывается тот или иной конструктор класса.
Деструкторы класса
Если задача создания объектов полностью возлагается на программиста, то задача удаления объектов, после того, как они стали не нужными, в Visual Studio .Net снята с программиста и возложена на соответствующий инструментарий - сборщик мусора. В классическом варианте языка C++ деструктор так же необходим классу, как и конструктор. В языке C# y класса может быть деструктор, но он не занимается удалением объектов и не вызывается нормальным образом в ходе выполнения программы. Так же, как и статический конструктор, деструктор класса, если он есть, вызывается автоматически в процессе сборки мусора. Его роль - в освобождении ресурсов, например, файлов, открытых объектом. Деструктор C# фактически является финализатором (finalizer), с которыми мы еще встретимся при обсуждении исключительных ситуаций. Приведу формальное описание деструктора класса Person:
~Person(){ //Код деструктора}Имя деструктора строится из имени класса с предшествующим ему символом ~ (тильда). Как и у статического конструктора, у деструктора не указывается модификатор доступа.