Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции_C#.doc
Скачиваний:
38
Добавлен:
15.11.2018
Размер:
2.72 Mб
Скачать

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

До сих пор мы рассматривали исключения, которые генерирует среда, но сгенерировать исключение может и сам программист. Для этого необходимо воспользоваться оператором 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);

}

}

7.4 Приемы использования обработчиков исключений

Рассмотрим несколько полезных приемов использования обработчиков исключений.

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

static void Main()

{

Console.WriteLine("a=");

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

Console.WriteLine("b=");

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

int f=1;

try //Внешний блок-try

{

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

{

try //Внутренний блок-try

{

f=checked((int)(f*i));

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

}

catch (DivideByZeroException)

{

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

}

}

}

catch (ArithmeticException)

{

Console.WriteLine("ERROR");

}

}

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

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

static void genException ()

{

Console.WriteLine("a=");

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

Console.WriteLine("b=");

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

int f = 1;

try //Внешний блок-try

{

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

{

try //Внутренний блок-try

{

f = checked((int)(f * i));

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

}

catch (DivideByZeroException)

{

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

}

}

}

catch (ArithmeticException)

{

Console.WriteLine("ERROR");

throw ; //повторная генерация исключения

}

}

static void Main()

{

try

{

genException();

}

catch

{

Console.WriteLine("НЕИСПРАВИМАЯ ОШИБКА!!!");

}

}

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

Задания.

  1. Объясните почему не было сгенерировано исключение DivideByZeroException.

  2. Сгенерируйте собственное исключение DivideByZeroException и его обработку для ситуации f-1<0.000001.

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

Замечание. Иногда возникает потребность в обязательном выполнении каких-то действий, которые должны выполниться по выходу из try/catch-блока. Например, генерируется исключение и происходит преждевременное завершение выполнения программного фрагмента, но при этом остается открытым файл. Для выхода из такой ситуации С# предоставляет блок finally, который добавляется после всех блоков catch.

static void Main()

{

for (int i = 0; i < 5; i++)

{

try

{

Console.WriteLine("Введите два числа");

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

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

Console.WriteLine(a+"/"+b+"="+a/b);

}

catch (FormatException)

{

Console.WriteLine("Нужно ввести число!");

}

catch (DivideByZeroException)

{

Console.WriteLine("Делить на нуль нельзя!");

}

catch

{

Console.WriteLine("Какая-то ошибка");

}

finally

{

Console.WriteLine("после try-блока");

}

}

}

Задание. Протестируйте данную программу, вводя поочередно следующие значения:

a=4, b=2

a=3, b=g

a=d, b=1

a=2, b=0,

a=123456789987654321, b=1

Самостоятельная работа

Вычисление конечных сумм и произведений

Решение многих задач связано с нахождением суммы или произведения элементов заданной последовательности. В данном разделе мы рассмотрим основные приемы вычисления конечных сумм и произведений.

Пусть - произвольная последовательность n функций. Будем рассматривать конечную сумму вида . Такую сумму можно записать более компактно, используя следующее обозначение: . При значение суммы равно 0.

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

  1. Написать программу, которая подсчитывает сумму натуральных чисел от до ().

Указания по решению задачи. Пусть - сумма натуральных чисел от до . Тогда Мы пришли к рекуррентному соотношению , которым мы можем воспользоваться для подсчета суммы. Соотношение говорит о том, что сумма на -ном шаге равна сумме, полученной на предыдущем шаге, плюс очередное слагаемое.

static void Main()

{

Console.Write("Ввведите значение n: ");

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

int s=0;

for (int i=1; i<=n; ++i)

s+=i;

Console.WriteLine("s="+s);

}

  1. Написать программу, которая подсчитывает для вещественного и натурального .

Указание по решению задачи. Из свойства факториала , , . Следовательно, факториал можно вычислять, используя рекуррентное соотношение .

static void Main()

{

Console.Write("Ввведите значение n: ");

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

int f=1;

for (int i=1; i<=n; ++i)

f*=i;

Console.WriteLine("{0}!={1}", n, f);

}

  1. Написать программу для подсчета суммы , где - вещественное число, - натуральное число.

Указания по решению задачи. Если пронумеровать слагаемые, начиная с , то мы увидим, что номер слагаемого совпадает со значением знаменателя. Рассмотрим каждый числитель отдельно: Эту последовательность можно представить рекуррентным соотношением (1). Теперь сумму можно представить следующим образом, , а для нее справедливо рекуррентное соотношение , (2). При составлении программы будем использовать формулы (1-2).

static void Main()

{

Console.Write("Ввведите значение n: ");

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

Console.Write("Ввведите значение x: ");

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

double b=0, s=0;

for (int i=1; i<=n; ++i)

{

b+=Math.Cos(i*x);

s+=b/i;

}

Console.WriteLine("s={0:f2}",s);

}

  1. Написать программу для подсчета суммы , где - вещественное число, - натуральное число.

Указания по решению задачи. Перейдем от сокращенной формы записи к развернутой, получим

Каждое слагаемое формируется по формуле . Если в эту формулу подставить , то получим .

Чтобы не вводить несколько рекуррентных соотношений (отдельно для числителя, отдельно для знаменателя), представим общий член последовательности слагаемых с помощью рекуррентного соотношением вида , где для нас пока не известно. Найти его можно из выражения . Произведя необходимые расчеты, получим, что . Следовательно, для последовательности слагаемых мы получили рекуррентное соотношение , (3). А всю сумму, по аналогии с предыдущими примерами, можно представить рекуррентным соотношением: , (4). Таким образом, при составлении программы будем пользоваться формулами (3-4).

using System;

namespace Hello

{

class Program

{

static void Main()

{

Console.Write("Ввведите значение n: ");

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

Console.Write("Ввведите значение x: ");

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

double a=-1, s=0;

for (int i=1; i<=n; ++i)

{

a*=-x/i; s+=a;

}

Console.WriteLine("s={0:f2}",s);

}

}

}

Вычисление бесконечных сумм

Будем теперь рассматривать бесконечную сумму вида . Это выражение называется функциональным рядом. При различных значениях из функционального ряда получаются различные числовые ряды . Числовой ряд может быть сходящимся или расходящимся. Совокупность значений , при которой функциональный ряд сходится, называется его областью сходимости.

Числовой ряд называется сходящимся, если сумма n первых его членов при имеет предел, в противном случае, ряд называется расходящимся. Ряд может сходиться лишь при условии, что общий член ряда при неограниченном увеличении его номера стремится к нулю: . Это необходимый признак сходимости для всякого ряда.

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

Пример. Написать программу для подсчета суммы с заданной точностью ().

Указание по решению задачи. Рассмотрим, что представляет из себя заданный ряд: . Как видим, общий член ряда с увеличением значения i стремится к нулю. Поэтому данную сумму можно вычислить, но только с определенной точностью . Заметим также, что последовательность слагаемых можно выразить с помощью рекуррентного соотношения , , а всю сумму - с помощью рекуррентного соотношения . (Данные рекуррентные соотношения выведите самостоятельно.)

using System;

namespace Hello

{

class Program

{

static void Main()

{

Console.Write("Задайте точность вычислений е: ");

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

double a=-1, s=0;

for (int i=2; Math.Abs(a)>=e; ++i)

{

s+=a;

a/=-i;

}

Console.WriteLine("s={0:f2}",s);

}

}

}

Лекция 8. Массивы

Массив - набор элементов одного и того же типа, объединенных общим именем. Массивы в С# можно использовать по аналогии с тем, как они используются в других языках программирования. Однако С#-массивы имеют существенные отличия: они относятся к ссылочным типам данных, более того - реализованы как объекты. Фактически имя массива является ссылкой на область кучи (динамической памяти), в которой последовательно размещается набор элементов определенного типа. Выделение памяти под элементы происходит на этапе инициализации массива. А за освобождением памяти следит система сборки мусора - неиспользуемые массивы автоматически утилизируются данной системой.

Рассмотрим различные типы массивов.