41031

Методы: основные понятия

Лекция

Информатика, кибернетика и программирование

Метод – это функциональный элемент класса, который реализует вычисления или другие действия, выполняемые классом или его экземпляром (объектом). Метод представляет собой законченный фрагмент кода, к которому можно обратиться по имени. Он описывается один раз, а вызываться может многократно. Совокупность методов класса определяет, что конкретно может делать класс.

Русский

2013-10-22

182.5 KB

0 чел.

Методы: основные понятия

Метод – это функциональный элемент класса, который реализует вычисления или другие действия, выполняемые классом или его экземпляром (объектом). Метод представляет собой законченный фрагмент кода, к которому можно обратиться по имени. Он описывается один раз, а вызываться может многократно. Совокупность методов класса определяет, что конкретно может делать класс. Например, стандартный класс Math содержит методы, которые позволяют вычислять значения математический функций.

Синтаксис метода:

[атрибуты] [спецификторы] тип_возвращаемого_результаты  имя_метода ([список_параметров])

{

   тело_метода;

    return значение

}

где:

  1.  Атрибуты и спецификторы являются необязательными элементами синтаксиса описания метода. На данном этапе атрибуты нами использоваться не будут, а из всех спецификаторов мы в обязательном порядке будем использовать спецификатор static, который позволит обращатся к методу класса без создания его экземпляра.
  2.  Тип_возвращаемого_результата определяет тип значения, возвращаемого методом. Это может быть любой тип, включая типы классов, создаваемые программистом. Если метод не возвращает никакого значения, необходимо указать тип void (в этом случае в теле метода отсутсвует оператор return).
  3.  Имя_метода – идентификатор, заданный программистом с учетом требований накладываемыми на идентификаторы в С#, отличный от тех, которые уже использованы для других элементов программы в пределах текущей области видимости.
  4.  Список_параметров представляет собой последовательность пар, состоящих из типа данных и идентификатора, разделенных запятыми. Параметры — это переменные или константы, которые получают значения, передаваемые методу при вызове. Если метод не имеет параметров, то список_параметров остается пустым.
  5.  Значение определяет значение, возвращаемое методом. Тип значения должен соответствовать типу_возвращаемого_результата или приводится к нему.

Рассмотрим простейший пример метода:

class Program

   {

       static void Func() //дополнительный метод

       {

           Console.Write("x= ");

           double x=double.Parse(Console.ReadLine());

           double y = 1 / x;

           Console.WriteLine("y({0})={1}", x,y );

       }

       static void Main() //точка входа в программу

       {

          Func(); //первый вызов метода Func

          Func(); //второй вызов метода Func

       }

   }

В даном примере в метод Func не передаются никакие значения, поэтому список параметров пуст. Кроме того метод ничего не возвращает, поэтому тип возвращаемого значения  void. В основном методе Main мы вызвали метод Func два раза. Если будет необходимо, то данный метод можно будет вызвать еще столько раз, сколько потребуется для решения задачи.

Изменим исходный пример так, чтобы в него передавалось значение х, а сам метод возвращал значение y.

class Program

   {

       static double Func( double x) //дополнительный метод

       {

           return 1 / x; //Возвращаемое значение

       }

       static void Main() //точка входа в программу

       {

         Console.Write("a=");

         double a=double.Parse(Console.ReadLine());

         Сonsole.Write("b=");

         double b=double.Parse(Console.ReadLine());

         for (double x = a; x <= b; x += 0.5)

         {

           double y = Func(x); //вызов метода Func

         Console.WriteLine("y({0:f1})={1:f2}", x, y);

         }

    }

В данном примере метод Func содержит параметр х, тип которого double. Для того, чтобы метод Func возвращал в вызывающий его метод Main значение выражения 1/x (тип которого double), перед именем метода указывается тип возвращаемого значения – double, а в теле метода используется оператор передачи управления – return. Оператор return завершает выполнение метода и передает управление в точку его вызова.

Рассмотрим другой пример:

class Program

   {

       static int Func( int x, int y) //сторка 1

       {

           return (x>y)? x:y;

       }

       static void Main()

       {

           Console.Write("a=");

           int a = int.Parse(Console.ReadLine());

           Console.Write("b=");

           int b = int.Parse(Console.ReadLine());

           Console.Write("c=");

           int c = int.Parse(Console.ReadLine());

           int max = Func(Func(a, b), c); //строка 2 - вызовы метода Func

           Console.WriteLine("max({0}, {1}, {2})={3}", a, b, c, max);     

       }

     }

В данном примере метод Func  имеет два целочисленных параметра – x, y, а в качестве результата метод возвращает наибольшее из них.  На этапе описания метода (строка 1) указываются формальных параметры, на этапе вызова (строка 2) в метод передаются фактические параметры, которые по количеству и по типу совпадают с формальными параметрами. Если количество фактических и формальных параметров будет различным, то компилятор выдаст соответствующее сообщение об ошибке. Если параметры будут отличаться типами, то компилятор попытается выполнить неявное преобразование типов. Если неявное преобразование невозможно, то также будет сгенерирована ошибка.

Обратите внимание на то, что при вызове метода Func использовалось вложение одного вызова в другой.

В общем случае параметры используются для обмена информацией между вызывающим и   вызываемым методами. В С# для обмена предусмотрено четыре типа параметров:  параметры-значения, параметры-ссылки, выходные параметры, параметры-массивы.

При передаче параметра по значению метод получает копии параметров, и операторы метода работают с этими копиями. Доступа к исходным значениям параметров у метода нет, а, следовательно, нет и возможности их изменить.

Замечание. Все примеры, рассмотренные ранее, использовали передачу данных по значению.

Рассмотрим небольшой пример:

class Program

   {

       static void Func(int x)

       {

           x += 10; // изменили значение параметра

           Console.WriteLine("In Func: " + x);

        }

       static void Main()

       {

           int a=10;

           Console.WriteLine("In Main: "+ a);

           Func(a);

           Console.WriteLine("In Main: " + a);

       }

   }

Результат работы программы:

In Main: 10

In Func: 20

In Main: 10

В данном примере значение формального параметра х было изменено в методе Func, но эти изменения не отразились на фактическом параметре а метода Main.

При передаче параметров по ссылке метод получает копии адресов параметров, что позволяет осуществлять доступ к ячейкам памяти по этим адресам и изменять исходные значения параметров. Для того чтобы параметр передавался по ссылке, необходимо при описании метода перед формальным параметром и при вызове метода перед соответствующим фактическим параметром поставить служебное слово ref.

class Program

   {

       static void Func(int x, ref int y)

       {

           x += 10; y += 10;  //изменение параметров

           Console.WriteLine("In Func: {0}, {1}", x, y);

        }

       static void Main()

       {

           int a=10, b=10;  // строка 1

           Console.WriteLine("In Main: {0}, {1}", a, b);

           Func(a, ref b);

           Console.WriteLine("In Main: {0}, {1}", a, b);

       }

   }

Результат работы программы:

In Main: 10  10

In Func: 20  20

In Main: 10  20

В данном примере в методе Func были изменены значения формальных  параметров х и y. Эти изменения не отразились на фактическом параметре а, т.к. он передавался по значению, но значение b было изменено, т.к. он передавался  по ссылке.  

Передача параметра по ссылке требует, чтобы аргумент был инициализирован до вызова метода (см. строку 1). Если в этой строке не проводить инициализацию переменных, то компилятор выдаст сообщение об ошибке.

Однако в некоторых случаях бывает невозможно инициализировать параметр до вызова метода. Тогда параметр следует передавать как выходной, используя  спецификатор out.

class Program

   {

       static void Func(int x, out int y)

       {

           x += 10; y = 10;   // определение значения выходного параметра y

           Console.WriteLine("In Func: {0}, {1}", x, y);

        }

       static void Main()

       {

           int a=10, b;

           Console.WriteLine("In Main: {0}", a);

           Func(a, out b);

           Console.WriteLine("In Main: {0}, {1}", a, b);

       }

   }

Результат работы программы:

In Main: 10  

In Func: 20  10

In Main: 10  10

В данном примере в методе Func формальный параметр y и соответствующий ему фактический параметр b метода Main были  помечены спецификатором out. Поэтому значение b до вызова метода Func можно было не определять, но изменение параметра y отразились на изменении значения параметра b.

Перегрузка методов

Иногда бывает удобно, чтобы методы, реализующие один и тот же алгоритм для различных типов данных, имели одно и то же имя. Использование нескольких методов с одним и тем же именем, но различными типами и количеством параметров называется перегрузкой методов. Компилятор определяет, какой именно метод требуется вызвать, по типу и количеству фактических параметров.

Рассмотрим следующий пример:

class Program

   {

       static int max(int a) //первая версия метода max

       {

           int b = 0;

           while (a > 0)

           {

               if (a % 10 > b) b = a % 10;

               a /= 10;

           }

           return b;

       }

      static int max(int a, int b) //вторая версия метода max

       {

           if (a > b) return a;

           else return b;

       }

       

       static int max(int a, int b, int c) //третья версия метода max

       {

           if (a > b && a > c) return a;

           else if (b > c) return b;

           else return c;

       }

       static void Main()

       {

           int a = 1283, b = 45, c = 35740;

           Console.WriteLine(max(a));

           Console.WriteLine(max(a, b));

           Console.WriteLine(max(a, b, c));

       }

   }

При вызове метода max компилятор выбирает вариант, соответствующий типу и количеству передаваемых в метод аргументов. Если точного соответствия не найдено, выполняются неявные преобразования типов в соответствии с общими правилами. Если преобразование невозможно, выдается сообщение об ошибке. Если выбор перегруженного метода возможен более чем одним способом, то выбирается «лучший» из вариантов (вариант, содержащий меньшие количество и длину преобразований в соответствии с правилами преобразования типов). Если существует несколько вариантов, из которых невозможно выбрать лучший, выдается сообщение об ошибке.

Перегрузка методов является проявлением полиморфизма, одного из основных свойств ООП. Программисту гораздо удобнее помнить одно имя метода и использовать его для работы с различными типами данных, а решение о том, какой вариант метода вызвать, возложить на компилятор. Этот принцип широко используется в классах библиотеки .NET. Например, в стандартном классе Console метод WriteLine перегружен 19 раз для вывода величин разных типов.

Обработка исключений

Язык С#, как и многие другие объектно-ориентированные языки, реагирует на ошибки и ненормальные ситуации с помощью механизма обработки исключений. Исключение - это объект, генерирующий информацию о «необычном программном происшествии». При этом важно проводить различие между ошибкой в программе, ошибочной ситуацией и исключительной ситуаций.

Ошибка в программе допускается программистом при ее разработке. Например, вместо операции сравнения (==) используется операция присваивания (=). Программист должен исправить подобные ошибки до передачи кода программы заказчику. Использование механизма обработки исключений не  является защитой от ошибок в программе.

Ошибочная ситуация вызвана действиями пользователя. Например, пользователь вместо числа ввел строку. Такая ошибка способна вызывать исключение. Программист должен предвидеть ошибочные ситуации и  предотвращать их с помощью операторов, проверяющих допустимость поступающих данных.

Даже если программист исправил все свои ошибки в программе, предвидел все ошибочные ситуации, он все равно может столкнуться с непредсказуемыми и неотвратимыми проблемами  - исключительными ситуациями. Например, нехваткой доступной памяти или попыткой открыть несуществующий файл. Исключительные ситуации  программист предвидеть не может, но он может отреагировать на них так, что они не приведут к краху программы.

Для обработки ошибочных и исключительных ситуаций в С# используется специальная подсистема обработки исключений. Преимущество данной подсистемы состоит в автоматизации создания большей части кода по обработке исключений. Раньше этот код приходилось вводить в программу "вручную". Кроме этого обработчик исключений способен распознавать и выдавать информацию о таких стандартных исключениях, как деление на нуль или попадание вне диапазона определения индекса.

Оператор try

В С# исключения представляются классами. Все классы исключений порождены от встроенного класса исключений Exception, который определен в пространстве имен System.

Управление обработкой исключений основывается на использовании оператора try.  Синтаксис оператора:

try // контролируемый блок

{

}

catch //один или несколько блоков обработки исключений

{

}

finally //блок завершения

{

}

Программные инструкции, которые нужно проконтролировать на предмет исключений, помещаются в блок try. Если исключение возникает в этом блоке, оно дает знать о себе выбросом определенного рода информации. Выброшенная информация может быть перехвачена и обработана соответствующим образом с помощью блока catch. Любой код, который должен быть обязательно выполнен при выходе из блока try, помещается в блок finally. Рассмотрим пример, демонстрирующий, как отследить и перехватить исключение.

static void Main()

       {

           int  x = int.Parse(Console.ReadLine());

           int y =1 / x;

           Console.WriteLine(y);

       }

Перечислим, какие исключительные ситуации могут возникнуть:

  1.  пользователь может ввести нечисловое значение
  2.  если ввести значение 0, то произойдет деление на 0.

Теперь попробуем обработать эти ситуации. Для этого изменим код следующим образом.

static void Main()

       {

           try

           {

               int x = int.Parse(Console.ReadLine());    

               int y =1 / x;

   Console.WriteLine("y={0}", y);

               Console.WriteLine("блок try выполнилсь успешно");

           }

           catch  // *

           {

               Console.WriteLine("возникла какая-то ошибка");

           }

          Console.WriteLine("конец программы");

      }

Рассмотрим, как обрабатываются исключения в данном примере. Когда возникает исключение, выполнение программы останавливается и управление передается блоку catch. Этот блок никогда не возвращает управление в то место программы, где возникло исключение. Поэтому команды из блока try,  расположенные ниже строки, в которой  возникло исключение,  никогда не будут выполнены. Блок catch обрабатывает исключение, и выполнение программы продолжается с оператора, следующего за этим блоком.

В нашем случае при вводе нечислового значения или 0 будет выведено сообщение «возникла ошибка», а затем сообщение «конец программы».

Обработчик исключений позволяет не только отловить ошибку, но и вывести полную информацию о ней. Для демонстрации сказанного заменим блок catch следующим фрагментом.

       catch (Exception error)

        {

         Console.WriteLine("Возникла ошибка {0}", error);

        }

Теперь, если возникнет исключительная ситуация, «выброшенная» информация будет записана в идентификатор error. Данную информацию можно просмотреть с помощью метода WriteLine. Такое сообщение очень полное и будет полезно только разработчику на этапе отладки проекта.

Для пользователя на этапе эксплуатации приложения достаточно более краткой информации о типе ошибке. С этой целью в С# выделены стандартные классы исключений, такие как DivideByZeroException, FormatException. Внесем изменения в программу.

 

static void Main()

       {

           try

           {

               int x = int.Parse(Console.ReadLine());    // 1 ситуация              

               int y =1 / x;   // 2 ситуация

               Console.WriteLine("y={0}", y);

               Console.WriteLine("блок try выполнилсь успешно");

            }

           catch(FormatException)  // обработка 1 ситуации

           {

               Console.WriteLine("Ошибка: введено нечисловое значение!");                

           }

           catch (DivideByZeroException) // обработка 2 ситуации

           {

               Console.WriteLine("Ошибка: деление на 0!");

           }

           Console.WriteLine("конец программы");

      }

В данном примере обрабатывается каждая ситуация в отдельности, при этом пользователю сообщается лишь минимальная информация об ошибке. В следующей таблице содержится описание наиболее часто используемых обработчиков стандартных исключений.

Имя

Описание

ArithmeticException

Ошибка в арифметических операциях или преобразованиях

ArrayTypeMismatchException

Попытка сохранения в массиве элемента несовместимого типа

DivideByZeroException

Попытка деления на ноль

FormatException

Попытка передать в метод аргумент неверного формата

IndexOutOfRangeException

Индекс массива выходит за границу диапазона

InvalidCastException

Ошибка преобразования типа

OutOfMemoryException

Недостаточно памяти для нового объекта

OverflowException

Перевыполнение при выполнении арифметических операций

StackOverflowException

Переполнение стека

Одно из основных достоинств обработки исключений состоит в том, что она позволяет программе отреагировать на ошибку и продолжить выполнение. Рассмотрим программу, которая строит таблицу значений для функции вида .

static void Main()

       {

           Console.WriteLine("a=");

           int a = int.Parse( Console.ReadLine());

           Console.WriteLine("b=");

           int b = int.Parse(Console.ReadLine());

           for (int i = a; i <= b; ++i)

           {

               try

               {

                   Console.WriteLine("y({0})={1}", i, 100 / (i * i - 1));

               }

               catch (DivideByZeroException)

               {

                   Console.WriteLine("y({0})=Деление на 0", i);

               }

           }

      }

Если встречается деление на нуль, генерируется исключение типа DivideByZeroException. В программе это исключение обрабатывается выдачей сообщения об ошибке, после чего выполнение программы продолжается. При этом попытка разделить на нуль не вызывает внезапную динамическую ошибку (т.к. блок обработки прерываний помещен внутрь цикла for). Вместо этого исключение позволяет красиво выйти из ошибочной ситуации и продолжить выполнение программы.

Операторы checked и unchecked

В С# предусмотрено специальное средство, которое связано с генерированием исключений, вызванных переполнением результата в арифметических вычислениях. Например, когда значение арифметического выражения выходит за пределы диапазона, определенного для типа данных выражения. Рассмотрим небольшой фрагмент программы:

static void Main()

       {

           byte x = 200; byte y = 200;

           byte result = (byte) (x + y);

           Console.WriteLine(result);

       }

Здесь произведение значений а и b превышает диапазон представления значений типа byte. Следовательно, результат данного выражения не может быть записан в переменную result, тип которой byte.

Для управления подобными исключениями в С#  используются операторы checked и unchecked. Чтобы указать, что некоторое выражение должно быть проконтролировано на предмет переполнения, используйте ключевое слово checked. А чтобы проигнорировать переполнение, используйте ключевое слово unchecked. В последнем случае результат будет усечен так, чтобы его тип соответствовал типу-результату выражения.

Замечание. По умолчанию проверка переполнения отключена (галочка не стоит). В результате код выполняется быстро, но тогда программист должен быть уверен, что переполнения не случится или предусмотреть его возникновение. Как мы уже упоминали, можно включить проверку переполнения для всего проекта, однако она не всегда нужна. С помощью использования операторов checked и unchecked в С# реализуется механизм гибкого управления проверкой

Можно задать (или отключить) проверку переполнения сразу для всего проекта. Для этого необходимо выполнить следующие действия

  1.  Щелкнуть правой кнопкой мыши на имени проекта
  2.  В выпадающем меню выбрать Properties
  3.  В появившемся окне (см. рис.) выбрать слева страницу Build
  4.  Щелкнуть на кнопке Advanced
  5.  В появившемся окошке поставить или убрать галочку напротив Check for arithmetic overflow/underflow property.

Оператор checked имеет две формы:

  1.  проверяет конкретное выражение и называется операторной checked-формой

checked ((тип-выражения) expr)

где expr — выражение, значение которого необходимо контролировать. Если значение контролируемого выражения переполнилось, генерируется исключение типа OverflowException.

  1.  проверяет блок инструкций

checked

{

// Инструкции, подлежащие проверке.

}

Оператор unchecked также имеет две формы:

  1.  операторная форма, которая позволяет игнорировать переполнение для заданного выражения

unchecked ((тип-выражения) expr)

где ехрr — выражение, которое не проверяется на предмет переполнения. В случае переполнения это выражение усекается.

  1.  игнорирует переполнение, которое возможно в блоке инструкций

unchecked

{

// Инструкции, для которых переполнение игнорируется.

}

Рассмотрим пример программы, которая демонстрирует использование checked и unchecked.

static void Main()

       {

           byte x = 200; byte y = 200;

           try

           {

               byte result = unchecked((byte)(x + y));

               Console.WriteLine("1: {0}", result);

               result = checked((byte)(x + y));

               Console.WriteLine("2: ", result);

           }

           catch (OverflowException)

           {

               Console.WriteLine("возникло переполнение");

           }

       }

Результат выполнения программы:

1: 144

возникло переполнение

В данном примере мы посмотрели, как использовать checked и uncheсked для проверки выражения. А теперь посмотрим, как использовать их для контроля за блоком инструкций.

static void Main()

       {

           byte n = 1; byte i;

           try

           {

               unchecked //блок без проверки

               {

                   for (i = 1; i < 10; i++) n *= i;

                   Console.WriteLine("1: {0}", n);

               }

               checked  //блок с проверкой

               {

                n=1;

                   for (i = 1; i < 10; i++) n *= i;

                   Console.WriteLine("2: ", n);

               }

            }

           catch (OverflowException)

           {

               Console.WriteLine("возникло переполнение");

           }

}

Результат выполнения программы:

1: 128

возникло переполнение

Генерация собственных исключений

До сих пор мы рассматривали исключения, которые генерирует среда, но сгенерировать исключение может и сам программист. Для этого необходимо воспользоваться оператором throw, указав параметры, определяющие вид исключения. Параметром должен быть объект, порожденный от стандартного класса System.Exception. Этот объект используется для передачи информации об исключении обработчику.

     static void Main()

       {

           try

           {

               int x = int.Parse(Console.ReadLine());

               if (x < 0) throw new Exception(); //1

               Console.WriteLine("ok");

            }

           catch

           {

               Console.WriteLine("введено недопустимое значение");

           }

    }

В строчке 1  c помощью команды new был создан объект исключения типа Exception. При необходимости можно генерировать исключение любого типа.

При генерации исключения можно определить сообщение, которое будет «выбрасываться» обработчиком исключений. Например:

static void Main()

   {

     try

       {

         int x = int.Parse(Console.ReadLine());

         if (x < 0) throw new Exception("введено недопустимое значение"); //1

         Console.WriteLine("ok");

        }

      catch (Exception error)

        {

          Console.WriteLine(error.Message);

        }

   }

12


 

А также другие работы, которые могут Вас заинтересовать

60270. Літературно-музична композиція «Что делать нам с бессмертными стихами?» 92.5 KB
  Ведуча Ми запрошуємо Вас до літературномистецького кабаре Бродячая Собака якому в цьому році відзначають 100 років від дня заснування. Це буде і не кабаре і не клуб ні карт ні програми Ведуча Так Микола Могилевський став хрещеним батьком...
60271. ВІД ЗІРОК - ДО ЗІРОК 61 KB
  Мета: вчити сприймати і відчувати все серцем; розвивати творчі здібності; формувати і виховувати творчу особистість, здатну цінувати себе і поважати інших. вечір наші мами Добрий вечір татусі Добрий вечір бабусі і дідусі І звичайно гості всі...
60272. АЛЬТЕРНАТИВНІ ДЖЕРЕЛА ЕНЕРГІЇ 1.8 MB
  Мета і завдання проекту Виявити переваги і недоліки традиційних джерел енергії Провести дослідження наявних світових енергетичних комплексів з метою виявлення найбільш безпечних з точки зору екології...
60273. Хіміко-екологічний калейдоскоп 242 KB
  Мета: екологічне виховання учнів; пропаганда розумного ставлення до природи; привернення уваги учнів до проблем навколишнього середовища з метою захистити землю від нас самих, зберегти її для нас і наших нащадків.
60274. Конференція «Про аскорбінку знаємо все!?.» 95.5 KB
  Обладнання: плакати Добова потреба населення України у вітаміні С Вміст вітаміну С у продуктах харчування бюлетень Корисно знати що Реактиви: соки цитрусових розчин крохмалю розчин йоду колби.
60275. Чотирнадцяте сузір’я: вплив Космосу на свідомість людини 102.5 KB
  А там далекодалеко розгорнулось недосяжне тло помережане сузір’ями. Небесним світилам почали поклонятися а найбільш яскраві й помітні групи зір обєднали в сузіря кожному з яких дали своє найменування: Персей Кассіопея Андромеда Південна Гідра Телець Центавр Пегас Овен тощо.
60276. Люди на світ народжуються різними: несхожими, своєрідними 84 KB
  Слайд 3: Учень 1: Толерантність означає пошану прийняття і правильне розуміння багатого різноманіття культур нашого світу різних форм самовираження і способів проявів людської індивідуальності.
60277. День Победы 49.5 KB
  Ход урока За то что солнце светит нам спасибо доблестным солдатам Какой праздник наша страна отмечает 9 Мая Кто и когда начал войну Сколько лет длилась Великая Отечественная Война слайд №1 1. Тогда появились плакаты Родина–Мать зовет и была написана песня Священная война.
60278. ПРАВО НА ЖИТТЯ 73.5 KB
  Слайд 1 Все на землі все треба берегти І птаха й звіра і оту рослину Не чванься тим що цар природи ти – Бо врешті ти його частинка. Лепкий слайд 27 Учитель: наше довкілля рідна природа чарує нас в усі пори року.