static void Main(string[] args) static int Main(string[] args) В первой форме метод Main() возвращает значение типа void, а во второй — це лое значение, как пояснялось выше. Но в обеих формах аргументы командной строки сохраняются в виде символьных строк в массиве типа string, который передается ме тоду Main(). Длина этого массива (args) должна быть равна числу аргументов ко мандной строки, которое может быть и нулевым. В качестве примера ниже приведена программа, выводящая все аргументы команд ной строки, вместе с которыми она вызывается.
// Вывести все аргументы командной строки. using System; class CLDemo { static void Main(string[] args) { Console.WriteLine("Командная строка содержит " + args.Length + " аргумента."); Console.WriteLine("Вот они: "); for(int i=0; i < args.Length; i++) Console.WriteLine(args[i]); } } Если программа CLDemo запускается из командной строки следующим образом:
CLDemo один два три то ее выполнение дает такой результат. Командная строка содержит 3 аргумента. Вот они: один два три Для того чтобы стало понятнее, каким образом используются аргументы командной строки, рассмотрим еще один пример программы, в которой применяется простой подстановочный шифр для шифровки или расшифровки сообщений. Шифруемое или расшифровываемое сообщение указывается в командной строке. Применяемый шифр действует довольно просто. Для шифровки слова значение каждой его буквы инкрементируется на 1. Следовательно, Буква "А" становится буквой "Б" и т.д. А для расшифровки слова значение каждой его буквы декрементируется на 1. Разумеется, такой шифр не имеет никакой практической ценности, поскольку его нетрудно раз гадать. Тем не менее он может стать приятным развлечением для детей.
// Зашифровать и расшифровать сообщение, используя // простой подстановочный шифр. using System;
class Cipher { static int Main(string[] args) { // Проверить наличие аргументов. if(args.Length < 2) { Console.WriteLine("ПРИМЕНЕНИЕ: " + "слово1: <зашифровать>/<расшифровать> " + "[слово2... словоN]"); return 1; // возвратить код неудачного завершения программы } // Если аргументы присутствуют, то первым аргументом должно быть // слово <зашифровать> или же слово <расшифровать>. if(args[0] != "зашифровать" & args[0] != "расшифровать") { Console.WriteLine("Первым аргументом должно быть слово " + "<зашифровать> или <расшифровать>."); return 1; // возвратить код неудачного завершения программы } // Зашифровать или расшифровать сообщение. for(int n=1; n < args.Length; n++) { for(int i=0; i < args[n].Length; i++) { if(args[0] == "зашифровать") Console.Write((char) (args[n][i] + 1) ); else Console.Write((char) (args[n][i] - 1) ); } Console.Write(" "); } Console.WriteLine(); return 0; } } Для того чтобы воспользоваться этой программой, укажите в командной строке имя программы, затем командное слово "зашифровать" или "расшифровать" и далее сообщение, которое требуется зашифровать или расшифровать. Ниже приведены два примера выполнения данной программы, при условии, что она называется Cipher.
С:\Cipher зашифровать один два пейо егб С:\Cipher расшифровать пейо егб один два Данная программа отличается двумя интересными свойствами. Во-первых, обра тите внимание на то, как в ней проверяется наличие аргументов командной строки перед тем, как продолжить выполнение. Это очень важное свойство, которое можно обобщить. Если в программе принимается во внимание наличие одного или более ар гументов командной строки, то в ней должна быть непременно организована проверка факта передачи ей предполагаемых аргументов, иначе программа будет работать не правильно. Кроме того, в программе должна быть организована проверка самих ар гументов перед тем, как продолжить выполнение. Так, в рассматриваемой здесь про грамме проверяется наличие командного слова "зашифровать" или "расшифровать" в качестве первого аргумента командной строки. И во-вторых, обратите внимание на то, как программа возвращает код своего завер шения. Если предполагаемые аргументы командной строки отсутствуют или указаны неправильно, программа возвращает код 1, указывающий на ее аварийное завершение. В противном случае возвращается код 0, когда программа завершается нормально. ## Рекурсия В C# допускается, чтобы метод вызывал самого себя. Этот процесс называется рекур сией, а метод, вызывающий самого себя, — рекурсивным. Вообще, рекурсия представля ет собой процесс, в ходе которого нечто определяет самое себя. В этом отношении она чем-то напоминает циклическое определение. Рекурсивный метод отличается главным образом тем, что он содержит оператор, в котором этот метод вызывает самого себя. Рекурсия является эффективным механизмом управления программой. Классическим примером рекурсии служит вычисление факториала числа. Факто риал числа N представляет собой произведение всех целых чисел от 1 до N. Напри мер, факториал числа 3 равен 1×2×3, или 6. В приведенном ниже примере программы демонстрируется рекурсивный способ вычисления факториала числа. Для сравнения в эту программу включен также нерекурсивный вариант вычисления факториала числа.
// Простой пример рекурсии. using System;
class Factorial { // Это рекурсивный метод. public int FactR(int n) { int result; if(n==1) return 1; result = FactR(n-1) n; return result; } // Это итерационный метод. public int FactI(int n) { int t, result; result = 1; for(t=1; t <= n; t++) result = t; return result; } }
class Recursion { static void Main() { Factorial f = new Factorial(); Console.WriteLine("Факториалы, рассчитанные рекурсивным методом."); Console.WriteLine("Факториал числа 3 равен " + f.FactR(3)); Console.WriteLine("Факториал числа 4 равен " + f.FactR(4)); Console.WriteLine("Факториал числа 5 равен " + f.FactR(5)); Console.WriteLine(); Console.WriteLine("Факториалы, рассчитанные итерационным методом."); Console.WriteLine("Факториал числа 3 равен " + f.FactR(3)); Console.WriteLine("Факториал числа 4 равен " + f.FactR(4)); Console.WriteLine("Факториал числа 5 равен " + f.FactR(5)); }
} При выполнении этой программы получается следующий результат.
Факториалы, рассчитанные рекурсивным методом. Факториал числа 3 равен 6 Факториал числа 4 равен 24 Факториал числа 5 равен 120 Факториалы, рассчитанные итерационным методом. Факториал числа 3 равен 6 Факториал числа 4 равен 24 Факториал числа 5 равен 120 Принцип действия нерекурсивного метода FactI() вполне очевиден. В нем ис пользуется цикл, в котором числа, начиная с 1, последовательно умножаются друг на друга, постепенно образуя произведение, дающее факториал. А рекурсивный метод FactR() действует по более сложному принципу. Если ме тод FactR() вызывается с аргументом 1, то он возвращает значение 1. В противном случае он возвращает произведение FactR(n-1)*n. Для вычисления этого произве дения метод FactR() вызывается с аргументом n-1. Этот процесс повторяется до тех пор, пока значение аргумента n не станет равным 1, после чего из предыдущих вызовов данного метода начнут возвращаться полученные значения. Например, когда вычисля ется факториал числа 2, то при первом вызове метода FactR() происходит второй его вызов с аргументом 1. Из этого вызова возвращается значение 1, которое затем умно жается на 2 (первоначальное значение аргумента n). В итоге возвращается результат 2, равный факториалу числа 2(1×2). Было бы любопытно ввести в метод FactR() опе раторы, содержащие вызовы метода WriteLine(), чтобы наглядно показать уровень рекурсии при каждом вызове метода FactR(), а также вывести промежуточные ре зультаты вычисления факториала заданного числа. Когда метод вызывает самого себя, в системном стеке распределяется память для новых локальных переменных и параметров, и код метода выполняется с этими новы ми переменными и параметрами с самого начала. При рекурсивном вызове метода не создается его новая копия, а лишь используются его новые аргументы. А при возврате из каждого рекурсивного вызова старые локальные переменные и параметры извле каются из стека, и выполнение возобновляется с точки вызова в методе. Рекурсивные методы можно сравнить по принципу действия с постепенно сжимающейся и затем распрямляющейся пружиной. Ниже приведен еще один пример рекурсии для вывода символьной строки в об ратном порядке. Эта строка задается в качестве аргумента рекурсивного метода DisplayRev().
// Вывести символьную строку в обратном порядке, используя рекурсию. using System;
class RevStr { // Вывести символьную строку в обратном порядке. public void DisplayRev(string str) { if(str.Length > 0) DisplayRev(str.Substring(1, str.Length-1)); else return; Console.Write(str[0]); } }
class RevStrDemo { static void Main() { string s = "Это тест"; RevStr rsOb = new RevStr(); Console.WriteLine("Исходная строка: " + s); Console.Write("Перевернутая строка: "); rsOb.DisplayRev(s); Console.WriteLine(); } } Вот к какому результату приводит выполнение этого кода.
Исходная строка: Это тест Перевернутая строка: тсет отЭ Всякий раз, когда вызывается метод DisplayRev(), в нем происходит проверка длины символьной строки, представленной аргументом str. Если длина строки не равна нулю, то метод DisplayRev() вызывается рекурсивно с новой строкой, кото рая меньше исходной строки на один символ. Этот процесс повторяется до тех пор, пока данному методу не будет передана строка нулевой длины. После этого начнет ся раскручиваться в обратном порядке механизм всех рекурсивных вызовов метода DisplayRev(). При возврате из каждого такого вызова выводится первый символ строки, представленной аргументом str, а в итоге вся строка выводится в обратном порядке. Рекурсивные варианты многих процедур могут выполняться немного медленнее, чем их итерационные эквиваленты из-за дополнительных затрат системных ресурсов на неоднократные вызовы метода. Если же таких вызовов окажется слишком много, то в конечном итоге может быть переполнен системный стек. А поскольку параметры и локальные переменные рекурсивного метода хранятся в системном стеке и при каж дом новом вызове этого метода создается их новая копия, то в какой-то момент стек может оказаться исчерпанным. В этом случае возникает исключительная ситуация, и общеязыковая исполняющая среда (CLR) генерирует соответствующее исключение. Но беспокоиться об этом придется лишь в том случае, если рекурсивная процедура выполняется неправильно. Главное преимущество рекурсии заключается в том, что она позволяет реализовать некоторые алгоритмы яснее и проще, чем итерационным способом. Например, ал горитм быстрой сортировки довольно трудно реализовать итерационным способом. А некоторые задачи, например искусственного интеллекта, очевидно, требуют именно рекурсивного решения. При написании рекурсивных методов следует непременно указать в соответству ющем месте условный оператор, например if, чтобы организовать возврат из мето да без рекурсии. В противном случае возврата из вызванного однажды рекурсивного метода может вообще не произойти. Подобного рода ошибка весьма характерна для реализации рекурсии в практике программирования. В этом случае рекомендуется пользоваться операторами, содержащими вызовы метода WriteLine(), чтобы сле дить за происходящим в рекурсивном методе и прервать его выполнение, если в нем обнаружится ошибка. ## Применение ключевого слова static Иногда требуется определить такой член класса, который будет использоваться не зависимо от всех остальных объектов этого класса. Как правило, доступ к члену клас са организуется посредством объекта этого класса, но в то же время можно создать член класса для самостоятельного применения без ссылки на конкретный экземпляр объекта. Для того чтобы создать такой член класса, достаточно указать в самом начале его объявления ключевое слово static. Если член класса объявляется как static, то он становится доступным до создания любых объектов своего класса и без ссылки на какой-нибудь объект. С помощью ключевого слова static можно объявлять как пере менные, так и методы. Наиболее характерным примером члена типа static служит метод Main(), который объявляется таковым потому, что он должен вызываться опе рационной системой в самом начале выполняемой программы. Для того чтобы воспользоваться членом типа static за пределами класса, доста точно указать имя этого класса с оператором-точкой. Но создавать объект для этого не нужно. В действительности член типа static оказывается доступным не по ссылке на объект, а по имени своего класса. Так, если требуется присвоить значение 10 перемен ной count типа static, являющейся членом класса Timer, то для этой цели можно воспользоваться следующей строкой кода.