- •Лабораторная работа № 1
- •5. Ввод и вывод в консольном приложении
- •6 Классы: основные понятия
- •6.1 Присваивание и сравнение объектов
- •6.2 Данные: поля и константы
- •6.3 Методы
- •5.3.1 Параметры методов
- •6.4 Ключевое слово this
- •6.5 Конструкторы
- •6.6 Свойства
- •7 Классы: подробности
- •7.1 Перегрузка методов
- •7.2 Рекурсивные методы
- •7.3 Методы с переменным количеством аргументов
- •7.4 Метод Main
- •7.5 Индексаторы
- •7.6 Операции класса
- •7.7 Деструкторы
- •7.8 Вложенные типы
- •8 Задания
7 Классы: подробности
7.1 Перегрузка методов
Часто бывает удобно, чтобы методы, реализующие один и тот же алгоритм для различных типов данных, имели одно и то же имя. Если имя метода является осмысленным и несет нужную информацию, это делает программу более понятной, поскольку для каждого действия требуется помнить только одно имя. Использование нескольких методов с одним и тем же именем, но различными типами параметров называется перегрузкой методов.
Компилятор определяет, какой именно метод требуется вызвать, по типу фактических параметров. Этот процесс называется разрешением (resolution) перегрузки. Тип возвращаемого методом значения в разрешении не участвует. Механизм разрешения основан на достаточно сложном наборе правил, смысл которых сводится к тому, чтобы использовать метод с наиболее подходящими аргументами и выдать сообщение, если такой не найдется. Допустим, имеется четыре варианта метода, определяющего наибольшее значение:
// Возвращает наибольшее из двух целых:
int max( int a, int b )
// Возвращает наибольшее из трех целых:
int max( int a, int b, int с )
// Возвращает наибольшее из первого параметра и длины второго:
int max ( int a, string b )
// Возвращает наибольшее из второго параметра и длины первого:
int max ( string b. int a )
. . .
Console.WriteLine( max( 1, 2 ) );
Console.WriteLine( max( 1, 2, 3 ) );
Console.WriteLine( max( 1, "2" ) );
Console.WriteLineC max( "1", 2 ) ):
При вызове метода max компилятор выбирает вариант метода, соответствующий типу передаваемых в метод аргументов (в приведенном примере будут последовательно вызваны все четыре варианта метода).
Если точного соответствия не найдено, выполняются неявные преобразования типов в соответствии с общими правилами, например, bool иcharвint,floatвdoubleи т. п. Если преобразование невозможно, выдается сообщение об ошибке. Если соответствие на одном и том же этапе может быть получено более чем одним способом, выбирается «лучший» из вариантов, то есть вариант, содержащий меньшие количество и длину преобразований. Если существует несколько вариантов, из которых невозможно выбрать лучший, выдается сообщение об ошибке.
Вам уже известно, что все методы класса должны различаться сигнатурами. Это понятие было введено в разделе. Перегруженные методы имеют одно имя, но должны различаться параметрами, точнее, их типами и способами передачи (outилиref). Например, методы, заголовки которых приведены ниже, имеют различные сигнатуры и считаются перегруженными:
int max( int a, int b )
int max( int a, ref int b )
Перегрузка методов является проявлением полиморфизма, одного из основных свойств ООП. Программисту гораздо удобнее помнить одно имя метода и использовать его для работы с различными типами данных, а решение о том, какой вариант метода вызвать, возложить на компилятор. Этот принцип широко используется в классах библиотеки .NET. Например, в стандартном классеConsolеметодWriteLineперегружен 19 раз для вывода величин разных типов.
7.2 Рекурсивные методы
Рекурсивным называется метод, который вызывает сам себя. Такая рекурсия называетсяпрямой. Существует ещекосвенная рекурсия, когда два или более метода вызывают друг друга. Если метод вызывает себя, в стеке создается копия значений его параметров, как и при вызове обычного метода, после чего управление передается первому исполняемому оператору метода. При повторном вызове этот процесс повторяется.
Ясно, что для завершения вычислений каждый рекурсивный метод должен содержать хотя бы одну нерекурсивную ветвь алгоритма, заканчивающуюся оператором возврата. При завершении метода соответствующая часть стека освобождается и управление передается вызывающему методу, выполнение которого продолжается с точки, следующей за рекурсивным вызовом.
Классическим примером рекурсивной функции является функция вычисления факториала (это не означает, что факториал следует вычислять именно так). Для того чтобы получить значение факториала числа n, требуется умножить наn факториал числа (n- 1). Известно также, что 0! = 1 и 1!=1:
long fact( long n ) {
if ( n == 0 || n == 1 ) return 1; // нерекурсивная ветвь
return ( n * fact( n - 1 ) ); // рекурсивная ветвь
}
To же самое можно записать короче:
long fact( long n ) {
return ( n > 1 ) ? n * fact( n - 1 ) : 1;
}
Рекурсивные методы чаще всего применяют для компактной реализации рекурсивных алгоритмов, а также для работы со структурами данных, описанными рекурсивно, например, с двоичными деревьями. Любой рекурсивный метод можно реализовать без применения рекурсии, для этого программист должен обеспечить хранение всех необходимых данных самостоятельно.
К достоинствам рекурсии можно отнести компактность записи, кнедостаткам расход времени и памяти на повторные вызовы метода и передачу ему копий параметров, а главное, опасность переполнения стека.