Рассмотрим подробнее, как создается объект класса Person:
newMe = new Person();
Сначала в правой части присваивания выполняется операция new, которая резервирует в памяти участок, способный хранить все переменные класса. Однако назвать это действие полноценным созданием объекта нельзя. Здесь не хватает того, что происходит и в реальной жизни – при рождении объект не только занимает место в пространстве, но и получает полный набор значений своих характеристик (начальное состояние объекта). Это необходимо выполнить и в момент создания объекту. Вот почему после операции new указывается не просто тип Person, а вызывается специальный метод, имя которого совпадает с именем класса. Такой метод называется конструктором. Исходя из такой роли конструктора, он должен быть определен в каждом классе.
Конструктор класса имеет несколько синтаксических особенностей:
1. Обычно (но не всегда!) конструктор описывается как public.
2. При определении конструктора в заголовке не указывается тип возвращаемого значения. Конструктор в принципе ничего не может возвращать, поэтому даже ключевое слов void здесь будет неуместно.
3. Имя конструктора всегда совпадает с именем класса.
Обсудим теперь список параметров конструктора. Логично, что через фактические параметры при вызове конструктора должны быть указаны данные, позволяющие определить состояние объекта. Поэтому, например, для создания полноценного объекта класса Person можно указать параметры для имени, веса и роста:
public Person(string n, double h, double w)
{ name=n; Height=h; Weight=w; }
При желании мы можем использовать имена параметров, совпадающие с именами переменных классов. Однако в этом случае придется использовать ключевое слово this для решения проблемы коллизии имен (совпадения имен двух переменных в одной области видимости):
public Person(string Name, double Height, double Weight)
Слово this указателем на текущий объект. Это слово обозначает объект данного класса, который вызвал метод.
Список параметров не обязательно должен соответствовать набору переменных класса. Допустим, рост и вес определяются на основании возраста по таблице стандартных соотношений роста и веса:
Заметим, что этот конструктор не обеспечивает назначение объекту имени
Person p = new Person(10);
p.PersonAnalyze();
Такой эксперимент покажет, что объект, на который ссылается переменная p, имеет имя “”, то есть пустую строку. Это стандартное «нулевое» значение, которое автоматически присваивается строковым переменным, если это инициализация не была выполнена явно. Для переменных числовых типов таким стандартным значением является 0, а для логических переменных - false.
В классе может быть одновременно несколько конструкторов, которые должны отличаться друг от друга сигнатурой (последовательностью типов параметров). Такое явление называется перегрузкой. В зависимости от контекста может требоваться различная инициализация переменных объекта. Перегрузка конструкторов и обеспечивает решение этой задачи. Перегрузка допустима и для других методов класса.
В классе можно объявить статический конструктор с атрибутом static. Он вызывается автоматически - его не нужно вызывать стандартным образом. Точный момент вызова не определен, но гарантируется, что вызов произойдет до создания первого объекта класса. Такой конструктор может выполнять некоторую предварительную работу, которую нужно выполнить один раз, например, связаться с базой данных, заполнить значения статических полей класса, создать константы класса, выполнить другие подобные действия. Статический конструктор, вызываемый автоматически, не должен иметь модификаторов доступа. Вот пример объявления такого конструктора в классе Person:
Особую роль играет конструктор без параметров. Его называют конструктором по умолчанию, поскольку он может использовать для инициализации переменных объекта некоторые стандартные значения. Мы можем определить, например, такой конструктор:
public Person( ){ name=”noname”; Height=50; Weight=4; }
Если в классе явно не определен ни один конструктор, то конструктор по умолчанию генерируется компилятором. Однако ничего интересного такой конструктор не выполняет – он инициализирует переменные объекта стандартными «нулевыми» значениями. Если в составе класса имеется переменная, являющаяся объектом некоторого класса, то в этом случае она будет иметь значение null – специальное слово, обозначающее отсутствие ссылки на объект в памяти.
Заметьте, что если программист сам создает один или несколько конструкторов, то автоматического добавления конструктора по умолчанию не происходит.
Вызов перегруженного конструктора
В ходе изучения данного пособия Вы должны усвоить объектно-ориентированный стиль программирования. Кратко это можно описать таким образом.
1. Сначала создайте классы – полезные правила для создания объектов.
2. Далее создавайте объекты – экземпляры этих классов и заставляйте их выполнять нужную Вам работу.
Таким образом в процессе выполнения программы непосредственно действуют объекты. Однако в некоторых случаях естественнее считать, что действия выполняются самим классом. Хорошим примером является класс Math – все его методы вызываются от имени класса:
Console.WriteLine(Math.Sin(0.5)+Math.Cos(0.5));
Было бы довольно странно для вычисления математических функций сначала создавать объект-«математику»:
Math m1=new Math();
Console.WriteLine(m1.Sin(0.5)+m2.Cos(0.5));
Это означало бы, что можно создавать несколько «математик». Но ведь они все должны действовать абсолютно одинаково.
Аналогично дело обстоит и с переменными – бывают случаи, когда некоторые данные естественнее представлять не как порцию информации, принадлежащую объекту, а классу в целом. В классе Math эта ситуация встречается при доступе к тригонометрической константе p - Math.PI.
Элементы класса (переменные и методы), которые соотносятся не с объектами, а с классом, называются статическими. В описании статических элементов используется ключевое слово static. В отличие от статических членов обычные элементы класса называются элементами экземпляров класса – методы экземпляров и переменные экземпляров.
Переменные экземпляра описываются в классе и хранятся в объектах класса. Статические переменные описываются и хранятся (в единственном числе) в классе, как показано на следующем рисунке:
Рассмотрим следующий пример. Класс Person моделирует ситуацию, когда множество людей располагают некоторым общим запасом пищи и делят его поровну. Количество пищи FoodQuantity и количество людей PeopleCount – характеристики всей совокупности людей. Поэтому они описаны как статические переменные. Метод Description описывает состояние всего множества людей и также является статическим методом.
class Person
{ public static double FoodQuantity = 100.0;
public static int PeopleCount = 0;
public double Weight;
public Person()
{ Weight = 10.0; PeopleCount++; }
public void ToEat()
{ double t = FoodQuantity / 2.0 / PeopleCount;
Weight += t; FoodQuantity -= t;
}
public static string Description()
{ return String.Format("Людей - {0} Еды - {1}",
PeopleCount,FoodQuantity);
}
}
class Program
{ static void Main(string[] args)
{ Person p1 = new Person(); p1.ToEat();
Console.WriteLine("Вес p1 - {0}", p1.Weight);
Console.WriteLine(Person.Description());
Person p2 = new Person(); Person p3 = new Person();
Статические методы могут использовать только статические переменные и другие статические методы класса. Попытка использовать в методе Description переменную Weight привела бы к ошибке компилятора.
Для инициализации статических переменных можно использовать статический конструктор:
static Person()
{ FoodQuantity = 100.0; PeopleCount = 0; }
Если в начале изучения языка C# не сразу используются его объектно-ориентированные возможности, структура программы выглядит примерно следующим образом:
Таким образом, можно не обращать внимания на наличие в программе класса Program. Программа сстоит из нескольких статических методов, которые вызываются в «главном» методе Main.