Г. Шилдт - С# 3.0 Полное руководство. 2010 (1160798), страница 49
Текст из файла (страница 49)
В итоге возвращается результат 2, равный факториалу числа 2 (1к2). Было бы любопытно ввести в метод Гассп() операторы, содержащие вызовы метода ильге ьзпе ( ), чтобы наглядно показать уровень рекурсии при каждом вызове метода расеи (), а также вывести промежуточные результаты вычисления факториала заданного числа.
Когда метод вызывает самого себя, в системном стеке распределяется память для новых локальных переменных и параметров, и код метода выполняется с этими новыми переменными и параметрами с самого начала. При рекурсивном вызове метода не создается его новая копия, но лишь используются его новые аргументы.
А при возврате из каждого рекурсивного вызова старые локальные переменные и параметры извлекаются из стека и выполнение возобновляет с точки вызова в методе. Рекурсивные методы можно сравнить по принципу действия с постепенно сжимающейся и затем распрямляющейся пружиной. Ниже приведен еще один пример рекурсии для вывода символьной строки в обратном порядке. Эта строка задается в качестве аргумента рекурсивного метода Оавр1аупеч () . // Вывести символьную строку в обратном порядке, // используя рекурсию. Глава 8. Подробнее о методах н классах 245 чя1по Яуягелн с1аяя Печзсг ( // Вывести символьную строку в обратном порядке.
рипаьс чогб Ргяр1ауаеч(ясгьпд ясг) ( 1Г(ягг.Ьепчгп > 0) Огяр1ауаеч(ясг.Янпясг1пд(1, ясг.ьепЧГЬ-1))т е1яе гесигп; Сопяо1е.нгаге(яхт[0]); ) ) с1аяя Вечзггпеюо ( ягаггс чогб Ма1п() ( ягггпо я "Это тест"т Вечзсг гяОЬ = пен ВечЯСг() т Сопяо1е.Игьгеьгпе("Исходная строка: " + я) Сопяо1е.нггге("Перевернутая строка: ")т гяОЬ.пьяр1аукеч(я)т Сопяо1е .Игдгеььпе () ) ) Вот к какому результату приводит выполнение этого кода: Исходная строка: Это тест Перевернутая строка: тсет отЭ Всякий раз, когда вызывается метод Р1яр1аупеч(), в нем производится проверка длины символьной строки, представленной аргументом ясг.
Если длина строки не равна нулю, то метод 01яр1ауиеч () вызывается рекурсивно с новой строкой, которая меньше исходной строки на один символ. Этот процесс повторяется до тех пор, пока данному методу не будет передана строка нулевой длины. После этого начнется раскручиваться в обратном порядке механизм всех рекурсивных вызовов метода 01яр1аупеч(). При возврате из каждого такого вызова выводится первый символ строки, представленной аргументом ясг, а в итоге вся строка выводится в обратном порядке. Рекурсивные варианты многих процедур могут выполняться немного медленнее, чем их итерационные эквиваленты из-за дополнительных затрат системных ресурсов на неоднократные вызовы метода.
Если же таких вызовов окажется слишком много, то в конечном итоге может быть переполнен системный стек. А поскольку параметры и локальные переменные рекурсивного метода хранятся в системном стеке и при каждом новом вызове этого метода создается их новая копия, то в какой-то момент стек может оказаться исчерпанным. В этом случае возникает исключительная ситуация и общеязыковая исполняющая среда (С].К) генерирует соответствующее исключение. Но беспокоиться об этом придется лишь в том случае, если рекурсивная процедура выполняется неправильно. 246 Часть (. Язык С№ Главное преимушество рекурсии заключается в том, что она позволяет реализовать некоторые алгоритмы яснее и проще, чем итерационным способом.
Например, алгоритм быстрой сортировки довольно трудно реализовать итерационным способом. А некоторые задачи, например искусственного интеллекта, очевидно, требуют именно рекурсивного решения. При написании рекурсивных методов следует непременно указать в соответствующем месте условный оператор, например 11, чтобы организовать возврат из метода без рекурсии. В противном случае возврата из вызванного однажды рекурсивного метода может вообще не произойти.
Подобного рода ошибка весьма характерна для реализации рекурсии в практике программирования. В этом случае рекомендуется пользоваться операторами, содержащими вызовы метода нг1геь1пе (), чтобы следить за происходящим в рекурсивном методе и прервать его выполнение, если в нем обнаружится ошибка. Применение ключевого слова в~:а~:хс Иногда требуется определить такой член класса, который будет использоваться независимо от всех остальных объектов этого класса.
Как правило, доступ к члену класса организуется посредством объекта этого класса, но в то же время можно создать член класса для самостоятельного применения без ссылки на конкретный экземпляр объекта. Для того чтобы создать такой член класса, достаточно указать в самом начале его объявления ключевое слово зсас1с. Если член класса объявляется как эсас1с, то он становится доступным до создания любых объектов своего класса и без ссылки на какой-нибудь объект. С помощью ключевого слова зсас10 можно объявлять как переменные, так и методы. Наиболее характерным примером члена типа эсас1с служит метод Иа№п (), который объявляется таковым потому, что он должен вызываться операционной системой в самом начале выполняемой программы.
Для того чтобы воспользоваться членом типа эсас10 за пределами класса, достаточно указать имя этого класса с оператором-точкой. Но создавать объект для этого не нужно. В действительности член типа зсасъс оказывается доступным не по ссылке на объект, а по имени своего класса. Так, если требуется присвоить значение 10 переменной соппг типа згаг1с, являющейся членом класса Т1атег, то для этой цели можно воспользоваться следующей строкой кода: т№тег.совок = 10; Эта форма записи подобна той, что используется для доступа к обычным переменным экземпляра посредством объекта, но в ней указывается имя класса, а не объекта. Аналогичным образом можно вызвать метод типа эгаггс, используя имя класса и оператор-точку Переменные, объявляемые как зсас1с, по существу являются глобальными. Когда же объекты объявляются в своем классе, то копия переменной типа згаг№с не создается.
Вместо этого все экземпляры класса совместно пользуются одной и той же переменной типа зсас№с, Такая переменная инициализируется перед ее применением в классе. Когда же ее инициализатор не указан явно, то она инициализируется нулевым значением, если относится к числовому типу данных, пустым значением, если относится к ссылочному типу, или же логическим значением га1зе, если относится к типу )зоо1. Таким образом, переменные типа зсас№с всегда имеют какое-то значение. Метод типа згаг1с отличается от обычного метода тем, что его можно вызывать по имени его класса, не создавая экземпляр объекта этого класса.
Пример такого вызова уже приводился ранее. Это был метод Бк(гс () типа эсас1с, относящийся к классу эузсека. маг)т из стандартной библиотеки классов С№. Глава 8. Подробнее о методах и классах 247 Ниже приведен пример программы, в которой объявляются переменная и метод типа зтас1с. // Использовать модификатор зтаттс.
пвтпо Яузтехи с1азз Ятаттспещо ( // Переменная типа зтатхс. роо11с втатхс 1пт ча1 100; // Метод типа зтаттс. роъттс зтаттс ъпт Ча10тч2() ( тетотп Ча1/2; ) с1авв Боево ( зтат1с ьотд Махп() ( Сопзо1е.нттте11пе("Исходное значение переменной " + "Бтатхспещо.ча1 равно " + Ятат1спещо.ча1)) Ятаттспещо Ча1 8; Сопзо1е.ит1теь1пе("Текущее значение переменной" + "Ятатхспещо.ча1 равно " + Ятатгспещо.Ча1)) Сопзо1е.нтттеььпе("Ятат1соещо.Ча1пдч2(): Ятаттспещо.Ча101ч2()); Выполнение этой программы приводит к следующему результату: Исходное значение переменной Ятатьсвево.Ча1 равно 100 Текущее значение переменной Ятат1соещо.Ча1 равно 8 Ятаттспещо.Ча1пдч2(): 4 Как следует из приведенного выше результата, переменная типа зтат1с инициализируется до создания любого объекта ее класса.
На применение методов типа зтасдс накладывается ряд следующих ограничений. ° В методе типа зтатдс должна отсутствовать ссылка Сада, поскольку такой метод не выполняется относительно какого-либо объекта. ° В методе типа зтас1с допускается непосредственный вызов только других методов типа зсас1с, но не метода экземпляра из того самого же класса. Дело в том, что методы экземпляра оперируют конкретными объектами, а метод типа зтат1с не вызывается для объекта. Следовательно, у такого метода отсутствуют объекты, которыми он мог бы оперировать.
° Аналогичные ограничения накладываются на данные типа зсатдс. Для метода типа зтас1с непосредственно доступными оказываются только другие данные типа зсасъс, определенные в его классе. Он, в частности, не может оперировать переменной экземпляра своего класса, поскольку у него отсутствуют объекты, которыми он мог бы оперировать. 248 Часть (. Язык С№ НИЖЕ ПрИВЕдЕН ПрИМЕр КЛаССа, В КОтОрОМ НсдОПуетИМ МЕтОд уа1О1чОЕПОШ() тИПа ее аг1 с. с1авв зсакъсЕггог ( рантье Епс Оепош Э; // обычная переменная экземпляра рцьтьс згаг1с 1пг на1 = 1024т // статическая переменная /* Ошибка! Непосредственный доступ к нестатической переменной из статического метода недопустим. */ згаг1с ьпг на1пачпепошй ( гесогп Ча1/Оепош; // не подлежит компиляции! В данном примере кода Оепош является обычной переменной, которая недоступна из метода типа згагъс, Но в то же время в этом методе можно воспользоваться переменной уа1, поскольку она обьявлена как зсас№с.
Аналогичная ошибка возникает при попытке вызвать нестатический метод из статического метода того жс самого класса, как в приведенном ниже примсрс. цз1пд зузсеш; с1азз Апосьегзсас1свггог ( // Нестатический метод. чотб НопзкасусМеГЬ() ( Сопзо1е.иг1геъ1пе("В методе Нопзсас1сиеГЬ()."); ) /* Ошибка! Непосредственный вызов нестатического метода из статического метода недопустим.