Полное руководство. С# 4.0 - Шилдт Герберт
Шрифт:
Интервал:
Закладка:
Следовательно, при обращенииob.MyMeth(i)
вызывается метод MyMeth(int x), но при обращенииob.MyMeth(ref i)
вызывается метод MyMeth(ref int x).
Несмотря на то что модификаторы параметров ref и out учитываются, когда принимается решение о перегрузке метода, отличие между ними не столь существенно.Например, два следующих варианта метода MyMeth() оказываются недействительными.// Неверно!public void MyMeth(out int x) { // ...public void MyMeth(ref int x) { // ...
В данном случае компилятор не в состоянии различить два варианта одного и тогоже метода MyMeth() только на основании того, что в одном из них используется параметр out, а в другом — параметр ref.
Перегрузка методов поддерживает свойство полиморфизма, поскольку именно таким способом в C# реализуется главный принцип полиморфизма: один интерфейс —множество методов. Для того чтобы стало понятнее, как это делается, обратимся кконкретному примеру. В языках программирования, не поддерживающих перегрузкуметодов, каждому методу должно быть присвоено уникальное имя. Но в программировании зачастую возникает потребность реализовать по сути один и тот же методдля обработки разных типов данных. Допустим, что требуется функция, определяющая абсолютное значение. В языках, не поддерживающих перегрузку методов, обычноприходится создавать три или более вариантов такой функции с несколько отличающимися, но все же разными именами. Например, в С функция abs() возвращает абсолютное значение целого числа, функция labs() — абсолютное значение длинногоцелого числа, а функция fabs() — абсолютное значение числа с плавающей точкойобычной (одинарной) точности.
В С перегрузка не поддерживается, и поэтому у каждой функции должно быть свое,особое имя, несмотря на то, что все упомянутые выше функции, по существу, делаютодно и то же — определяют абсолютное значение. Но это принципиально усложняетположение, поскольку приходится помнить имена всех трех функций, хотя они реализованы по одному и тому же основному принципу. Подобные затруднения в С# не возникают, поскольку каждому методу, определяющему абсолютное значение, может бытьприсвоено одно и то же имя. И действительно, в состав библиотеки классов для среды.NET Framework входит метод Abs(), который перегружается в классе System.Math дляобработки данных разных числовых типов. Компилятор C# сам определяет, какой именно вариант метода Abs() следует вызывать, исходя из типа передаваемого аргумента.Главная ценность перегрузки заключается в том, что она обеспечивает доступ к связанным вместе методам по общему имени. Следовательно, имя Abs обозначает общеевыполняемое действие, а компилятор сам выбирает конкретный вариант метода пообстоятельствам. Благодаря полиморфизму несколько имен сводятся к одному. Несмотря на всю простоту рассматриваемого здесь примера, продемонстрированныйв нем принцип полиморфизма можно расширить, чтобы выяснить, каким образомперегрузка помогает справляться с намного более сложными ситуациями в программировании.
Когда метод перегружается, каждый его вариант может выполнять какое угоднодействие. Для установления взаимосвязи между перегружаемыми методами не существует какого-то одного правила, но с точки зрения правильного стиля программирования перегрузка методов подразумевает подобную взаимосвязь. Следовательно,использовать одно и то же имя для несвязанных друг с другом методов не следует,хотя это и возможно. Например, имя Sqr можно было бы выбрать для методов, возвращающих квадрат и квадратный корень числа с плавающей точкой. Но ведь этопринципиально разные операции. Такое применение перегрузки методов противоречит ее первоначальному назначению. На практике перегружать следует только тесносвязанные операции.
В C# определено понятие сигнатуры, обозначающее имя метода и список его параметров, Применительно к перегрузке это понятие означает, что в одном классе недолжно существовать двух методов с одной и той же сигнатурой. Следует подчеркнуть,что в сигнатуру не входит тип возвращаемого значения, поскольку он не учитывается,когда компилятор C# принимает решение о перегрузке метода. В сигнатуру не входиттакже модификатор params.Перегрузка конструкторов
Как и методы, конструкторы также могут перегружаться. Это дает возможностьконструировать объекты самыми разными способами. В качестве примера рассмотримследующую программу.// Продемонстрировать перегрузку конструктора.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 Main() { MyClass t1 = new MyClass(); MyClass t2 = new MyClass(88); MyClass t3 = new MyClass(17.23); MyClass t4 = new MyClass(2, 4); Console.WriteLine("t1.x: " + t1.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).t1.x: 0t2.x: 88t3.x: 17t4.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 IsFull() { return tos==stck.Length; } // Возвратить значение true, если стек пуст. public bool IsEmpty() { return tos==0; } // Возвратить общую емкость стека. public int Capacity() { return stck.Length; } // Возвратить количество объектов, находящихся в настоящий момент в стеке. public int GetNum() { return tos; }}// Продемонстрировать применение класса Stack.class StackDemo { static void Main() { Stack stk1 = new Stack(10); char ch; int i; // Поместить ряд символов в стек stk1. Console.WriteLine("Поместить символы А-J в стек stk1."); for(i=0; !stk1.IsFull(); i++) stk1.Push((char) ('A' + i)); // Создать копию стека stck1. Stack stk2 = new Stack(stk1); // Вывести содержимое стека stk1. Console.Write("Содержимое стека stk1: "); while( !stk1.IsEmpty() ) { ch = stk1.Pop(); Console.Write(ch); } Console.WriteLine(); Console.Write("Содержимое стека stk2: "); while( !stk2.IsEmpty() ) { ch = stk2.Pop(); Console.Write(ch); } Console.WriteLine("n"); }}