программирование. Руководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт
Скачать 3.32 Mb.
|
Как и методы, конструкторы также могут перегружаться. Это дает возможность конструировать объекты самыми разными способами. В качестве примера рассмотрим следующую программу. // Продемонстрировать перегрузку конструктора. using System; class MyClass { public int x; public MyClass() { Console.WriteLine("В конструкторе MyClass()."); x = 0; } public MyClass(int i) { Console.WriteLine("В конструкторе MyClass(int)."); x = i ; } public MyClass(double d) { Console.WriteLine("В конструкторе MyClass(double)."); x = (int) d; } public MyClass(int i, int j) { Console.WriteLine("В конструкторе MyClass(int, int)."); x = i * j; } } class OverloadConsDemo { static void MainO‑ { MyClass tl = new MyClass (); MyClass t2 = new MyClass(88); MyClass t3 = new MyClass(17.23); MyClass t4 = new MyClass(2, 4); Console.WriteLine("tl.x: " + tl.x); Console.WriteLine("t2.х: " + t2.x); Console.WriteLine("t3.x: " + t3.x); Console.WriteLine("t4.x: " + t4.x); } } При выполнении этой программы получается следующий результат. В конструкторе MyClass(). В конструкторе MyClass (int) . В конструкторе MyClass(double). В конструкторе MyClass (int, int). tl.x: О t2.x: 88 t3.x: 17 t4.x: 8 В данном примере конструктор MyClass () перегружается четыре раза, всякий раз конструируя объект по‑разному. Подходящий конструктор вызывается каждый раз, исходя из аргументов, указываемых при выполнении оператора new. Перегрузка конструктора класса предоставляет пользователю этого класса дополнительные преимущества в конструировании объектов. Одна из самых распространенных причин для перегрузки конструкторов заключается в необходимости предоставить возможность одним объектам инициализировать другие. В качестве примера ниже приведен усовершенствованный вариант разработанного ранее класса Stack, позволяющий конструировать один стек из другого. // Класс для хранения символов в стеке. using System; class Stack { // Эти члены класса являются закрытыми, char[] stck; // массив, содержащий стек int tos; // индекс вершины стека // Сконструировать пустой объект класса Stack по заданному размеру стека, public Stack(int size) { stck = new char[size]; // распределить память для стека tos = 0; } // Сконструировать объект класса Stack из существующего стека, public Stack(Stack ob) { // Распределить память для стека, stck = new char[ob.stck.Length]; // Скопировать элементы в новый стек, for (int i=0; i < ob.tos; i++) stck[i] = ob.stck[i]; // Установить переменную tos для нового стека, tos = ob.tos; // Поместить символы в стек, public void Push(char ch) { if(tos==stck.Length) { Console.WriteLine(" ‑ Стек заполнен."); return; ‑ } stck[tos] = ch; tos++; } // Извлечь символ из стека, public char Pop () { if(tos==0) { Console.WriteLine (" ‑ Стек пуст."); return (char) 0; } tos–; return stck[tos]; } // Возвратить значение true, если стек заполнен, public bool IsFullO { return tos==stck.Length; } // Возвратить значение true, если стек пуст, public bool IsEmptyO { return tos==0; } // Возвратить общую емкость стека, public int Capacity() { return stck.Length; } // Возвратить количество объектов, находящихся в настоящий момент в стеке, public int GetNum() { return tos; } } // Продемонстрировать применение класса Stack. class StackDemo { static void Main() { Stack stkl = new Stack(10); char ch; int i; // Поместить ряд символов в стек stkl. Console.WriteLine("Поместить символы А‑J в стек stkl."); for(i=0; !stkl.IsFull(); i++) stkl.Push((char) ('A' + i)); // Создать копию стека stckl. Stack stk2 = new Stack(stkl); // Вывести содержимое стека stkl. Console.Write("Содержимое стека stkl: "); while ( !stkl.IsEmpty() ) { ch = stkl.Pop (); Console.Write(ch); } Console.WriteLine (); Console.Write("Содержимое стека stk2: "); while ( !stk2.IsEmpty() ) { ch = stk2.Pop (); Console.Write(ch); } Console.WriteLine ("\n"); } } Результат выполнения этой программы приведен ниже. Поместить символы А‑J в стек stkl. Содержимое стека stkl: JIHGFEDCBA Содержимое стека stk2: JIHGFEDCBA В классе StackDemo сначала конструируется первый стек (stkl), заполняемый символами. Затем этот стек используется, для конструирования второго стека (stk2). Это приводит к выполнению следующего конструктора класса Stack. // Сконструировать объект класса Stack из существующего стека, public Stack(Stack ob) { // Распределить память для стека, stck = new char[ob.stck.Length]; // Скопировать элементы в новый стек, for (int i=0; i < ob.tos; i++) stck[i] = ob.stck[i]; // Установить переменйую tos для нового стека, tos = ob.tos; } В этом конструкторе сначала распределяется достаточный объем памяти для массива, чтобы хранить в нем элементы стека, передаваемого в качестве аргумента ob. Затем содержимое массива, образующего стек ob, копируется в новый массив, после чего соответственно устанавливается переменная tos, содержащая индекс вершины стека. По завершении работы конструктора новый и исходный стеки существуют как отдельные, хотя и одинаковые объекты. Вызов перегружаемого конструктора с помощью ключевого слова this Когда приходится работать с перегружаемыми конструкторами, то иногда очень полезно предоставить возможность одному конструктору вызывать другой. В C# это дается с помощью ключевого слова this. Ниже приведена общая форма такого вызова. имя_конструктора{список_параметров1) : this ( список_параметров2) { II ... Тело конструктора, которое может быть пустым. } В исходном конструкторе сначала выполняется перегружаемый конструктор, список параметров которого соответствует критерию список_параметров2, а затем все остальные операторы, если таковые имеются в исходном конструкторе. Ниже приведен соответствующий пример. // Продемонстрировать вызов конструктора с помощью ключевого слова this. using System; class XYCoord { public int x, y; public XYCoord() : this(0, 0) { Console.WriteLine("В конструкторе XYCoord()"); } public XYCoord(XYCoord obj) : this(obj.x, obj.y) { Console.WriteLine("В конструкторе XYCoord(obj)"); } public XYCoord(int i, int j) { Console.WriteLine("В конструкторе XYCoord(int, int)"); x = i; У = j; } } class OverloadConsDemo { static void Main() { XYCoord tl = new XYCoord(); XYCoord t2 = new XYCoord(8, 9); XYCoord t3 = new XYCoord(t2); Console.WriteLine("tl.x, tl.y: " + tl.x + \ " + tl.y); Console.WriteLine("t2.x, t2. у: " + t2 .x + ", " + t2.y); Console.WriteLine("t3.x, t3.y: " + t3. x + ", " + t3.y); } } Выполнение этого кода приводит к следующему результату. В конструкторе XYCoord(int, int) В конструкторе XYCoord() В конструкторе XYCoord(int, int) В конструкторе XYCoord(int, int) В конструкторе XYCoord(obj) tl.x, tl.y: 0, 0 t2 . x, t2 . у: 8, 9 t3.x, t3.у: 8, 9 Код в приведенном выше примере работает следующим образом. Единственным конструктором, фактически инициализирующим поля х и у в классе XYCoord, является конструктор XYCoord(int, int). А два других конструктора просто вызывают этот конструктор с помощью ключевого слова this. Например, когда создается объект 11, то вызывается его конструктор XYCoord (), что приводит к вызову this (0 , 0) , который в данном случае преобразуется в вызов конструктора XYCoord (0 , 0) . То же самое происходит и при создании объекта t2. Вызывать перегружаемый конструктор с помощью ключевого слова this полезно, в частности, потому, что он позволяет исключить ненужное дублирование кода. В приведенном выше примере нет никакой необходимости дублировать во всех трех конструкторах одну и ту же последовательность инициализации, и благодаря применению ключевого слова this такое дублирование исключается. Другое преимущество организации подобного вызова перезагружаемого конструктора заключается в возможности создавать конструкторы с задаваемыми "по умолчанию" аргументами, когда эти аргументы не указаны явно. Ниже приведен пример создания еще одного конструктора XYCoord. public XYCoord(int х) : this(х, х) { } По умолчанию в этом конструкторе для координаты у автоматически устанавливается то же значение, что и для координаты у. Конечно, пользоваться такими конструкциями с задаваемыми "по умолчанию" аргументами следует благоразумно и осторожно, чтобы не ввести в заблуждение пользователей классов. Инициализаторы объектов Инициализаторы объектов предоставляют еще один способ создания объекта и инициализации его полей и свойств. (Подробнее о свойствах речь пойдет в главе 10.) Если используются инициализаторы объектов, то вместо обычного вызова конструктора класса указываются имена полей или свойств, инициализируемых первоначально задаваемым значением. Следовательно, синтаксис инициализатора объекта предоставляет альтернативу явному вызову конструктора класса. Синтаксис инициализатора объекта используется главным образом при создании анонимных типов в LINQ‑выражениях. (Подробнее об анонимных типах и LINQ‑выражениях – в главе 19.) Но поскольку инициализаторы объектов можно, а иногда и должно использовать в именованном классе, то ниже представлены основные положения об инициализации объектов. Обратимся сначала к простому примеру. // Простой пример, демонстрирующий применение инициализаторов объектов. using System; class MyClass { public int Count; public string Str; class ObjlnitDemo { static void Main() { // Сконструировать объект типа MyClass, используя инициализаторы объектов. MyClass obj = new MyClass { Count = 100, Str = "Тестирование" }; Console.WriteLine(obj.Count + " " + obj.Str); } } Выполнение этого кода дает следующий результат. 100 Тестирование Как показывает результат выполнения приведенного выше кода, переменная экземпляра obj .Count инициализирована значением 100, а переменная экземпляра obj . Str – символьной строкой "Тестирование". Но обратите внимание на то, что в классе MyClass отсутствуют явно определяемые конструкторы и не используется обычный синтаксис конструкторов. Вместо этого объект obj класса MyClass создается с помощью следующей строки кода. MyClass obj = new MyClass { Count = 100, Str = "Тестирование" }; В этой строке кода имена полей указываются явно вместе с их первоначальными значениями. Это приводит к тому, что сначала конструируется экземпляр объекта типа MyClass (с помощью неявно вызываемого по умолчанию конструктора), а затем задаются первоначальные значения переменных Count и Str данного экземпляра. Следует особо подчеркнуть, что порядок указания инициализаторов особого значения не имеет. Например, объект obj можно было бы инициализировать и так, как показано ниже. MyClass obj = new MyClass { Str = "Тестирование", Count = 100 }; В этой строке кода инициализация переменной экземпляра Str предшествует инициализации переменной экземпляра Count, а в приведенном выше коде все происходило наоборот. Но в любом случае результат получается одинаковым. Ниже приведена общая форма синтаксиса инициализации объектов: new имя_класса {имя = выражение, имя = выражение, . . . } где имя обозначает имя поля или свойства, т.е. доступного члена класса, на который указывает имя_класса. А выражение обозначает инициализирующее выражение, тип которого, конечно, должен соответствовать типу поля или свойства. Инициализаторы объектов обычно не используются в именованных классах, как, например, в представленном выше классе MyClass, хотя это вполне допустимо. Вообще, при обращении с именованными классами используется синтаксис вызова обычного конструктора. И, как упоминалось выше, инициализаторы объектов применяются в основном в анонимных типах, формируемых в LINQ‑выражениях. Необязательные аргументы В версии C# 4.0 внедрено новое средство, повышающее удобство указания аргументов при вызове метода. Это средство называется необязательными аргументами и позволяет определить используемое по умолчанию значение для параметра метода. Данное значение будет использоваться по умолчанию в том случае, если для параметра не указан соответствующий аргумент при вызове метода. Следовательно, указывать аргумент для такого параметра не обязательно. Необязательные аргументы позволяют упростить вызов методов, где к некоторым параметрам применяются аргументы, выбираемые по умолчанию. Их можно также использовать в качестве "сокращенной7' формы перегрузки методов. Применение необязательного аргумента разрешается при создании необязательного параметра. Для этого достаточно указать используемое по умолчанию значение параметра с помощью синтаксиса, аналогичного инициализации переменной. Используемое по умолчанию значение должно быть константным выражением. В качестве примера рассмотрим следующее определение метода. static void OptArgMeth(int alpha, int beta=10, int gamma = 20) { В этой строке кода объявляются два необязательных параметра: beta и gamma, причем параметру beta по умолчанию присваивается значение 10, а параметру gamma – значение 20. Эти значения используются по умолчанию, если для данных параметров не указываются аргументы при вызове метода. Следует также иметь в виду, что параметр alpha не является необязательным. Напротив, это обычный параметр, для которого всегда нужно указывать аргумент. Принимая во внимание приведенное выше объявление метода OptArgMeth (), последний можно вызвать следующими способами. // Передать все аргументы явным образом. OptArgMeth(1, 2, 3); // Сделать аргумент gamma необязательным. OptArgMeth(1, 2); // Сделать оба аргумента beta и gamma необязательными. OptArgMeth(1); При первом вызове параметру alpha передается значение 1, параметру beta – значение 2, а параметру gamma – значение 3. Таким образом, все три аргумента задаются явным образом, а значения, устанавливаемые по умолчанию, не используются. При втором вызове параметру alpha передается значение 1, а параметру beta – значение 2, но параметру gamma присваивается устанавливаемое по умолчанию значение 20. И наконец, при третьем вызове упомянутого выше метода параметру alpha передается значение 1, а параметрам beta и gamma присваиваются устанавливаемые по умолчанию значения. Следует, однако, иметь в виду, что параметр beta не получит устанавливаемое по умолчанию значение, если то же самое не произойдет с параметром gamma. Если первый аргумент устанавливается по умолчанию, то и все остальные аргументы должны быть установлены по умолчанию. |