Скачиваний:
64
Добавлен:
24.03.2015
Размер:
229.89 Кб
Скачать

16.5. Исключения в арифметических выражениях

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

int x = 111111, y = 111111, z = 0;

double a = x / 0.0; //результат: "бесконечность"

//double b = x / 0; //ошибка компиляции

double c = x / z; //исключение DivideByZeroException

double d = x * y; //результат: -539247567

Как отмечено в комментариях, все приведённые выражения приводят к возникновению особых ситуаций. В первых трёх случаях программист по сообщениям компилятора или по результатам выполнения программы может явно распознать ситуацию. Значением переменной а=х/0.0 является бесконечно большая величина. В случае b=х/0 компилятор выдаёт сообщение о попытке целочисленного деления на нулевую константу. Выражение c=x/z не смущает компилятор, но приводит к генерации исключения System.DivideByZeroException на этапе выполнения программы. В случае вычисления х*у происходит целочисленное переполнение, но никаких признаков особой ситуации нет. Правда, переменной d присваивается некоторое отрицательное значение после умножения двух положительных величин. В нашем простом примере этот результат может служить сигналом об особой ситуации. Однако в сложных арифметических выражениях целочисленное переполнение может остаться незамеченным, но приведёт к неверному результату.

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

checked (выражение)

checked {операторы}

В первом случае отслеживаются возникновения переполнений в заключённом в скобки выражении. Во втором случае контролируются переполнения во всех операторах блока. В обоих случаях при возникновении переполнения генерируется исключение System.OverflowException.

Наш пример можно дополнить оператором

double e= checked(x*y);

В этом случае переполнение при вычислении выражения х*у не будет игнорироваться, а приведёт к генерации названного исключения. Обработка такого исключения может быть организована обычным образом с помощью блока try/catch.

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

16.6. Генерация исключений

Об обработке исключений, посылаемых методами библиотечных классов, мы уже говорили и неоднократно пользовались. Чтобы глубже разобраться в механизме генерации исключений и научиться создавать и посылать исключения в любой заранее предусмотренной ситуации, возникшей при выполнении программы, нужно ясно понять и усвоить, что исключения – это объекты некоторых специальных классов. Все системные классы исключений являются производными от класса System.Exception. Этот класс имеет несколько замечательных свойств, два из которых (Message и Sourse) мы уже применяли. При создании объектов классов исключений наиболее важными являются Message и InnerException. Оба эти свойства в обязательном порядке используются всеми исключениями. Оба свойства предназначены только для чтения.

Message – это свойство типа String, значение которого представляет собой текст сообщения о причине возникновения исключения.

InnerException – имеет тип Exception и представляет собой ненулевую ссылку на то исключение, которое является причиной возникновения данного исключения.

Значения этих свойств можно задать с помощью параметров конструктора при создании объекта класса System.Exception. Нам могут быть полезны следующие конструкторы:

Exception() – инициализирует умалчиваемыми данными новый экземпляр класса Exception.

Exception(String) – инициализирует новое исключение (новый объект). Параметр задаёт значение свойства Message.

Exception(String, Exception) – параметр типа String определяет значение свойства Message, а второй параметр представляет собой ссылку на внутреннее исключение, которое явилось причиной данного исключения.

Зная конструкторы, создать объект-исключение, применяя операцию new мы уже сможем. Осталось выяснить каким образом послать исключение и как определить условие, при выполнении которого это нужно делать. Определение условия не относится к механизму обработки исключений, а зависит от той конкретной задачи, для решения которой создаётся программа.

Для генерации (для посылки) исключения из выбранной точки кода программы используется оператор, имеющий одну из следующих двух форм:

throw выражение;

throw;

Оператор throw без выражения; можно использовать только в catch-блоке для ретрансляции исключения в catch-блок следующего уровня.

Значением выражения, использованного в операторе throw, должна быть ссылка на объект класса исключений. Именно этот объект посылается в качестве исключения. Если значение выражения равно null, то вместо него посылается исключение System.NullReference.Exception.

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

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

Ошибки ввода в данном случае могут быть двух видов – лексические и семантические. Лексическая ошибка - это неверная запись вещественного числа при его наборе на клавиатуре. Семантическая ошибка – отличие введённого числа от трёх допустимых условием задачи значений (110, 127, 220). Для преобразования введённой с клавиатуры последовательности символов в числовое значение применим метод TryParse(). Тогда послать исключение при лексической ошибке можно с помощью следующих операторов:

string input = Console.ReadLine();

if (!double.TryParse(input, out U))

throw new Exception("Ошибка в формате данных!");

В данном фрагменте U – имя переменной типа double, которая будет представлять в программе введённое пользователем значение напряжения.

Для генерации исключения при семантической ошибки используем такой код:

if (U != 110 & U != 127 & U != 220)

throw new Exception(input+" - недопустимое значение!");

Все приведённые операторы разместим в try-блоке, вслед за которым поместим обработчик исключений (catch-блок), где будем подсчитывать их количество. Всю конструкцию try/catch поместим в цикл. Тем, кто забыл раздел физики, посвященный электричеству, напомним, что мощность, потребляемая сопротивлением R при его включении в сеть с напряжением U, вычисляется по формуле U2/R- Программа в целом может быть такой:

// 16_05.cs - исключения для проверки вводимых данных

public static void Main05()

{

double U, R=100;

int counter = 0; // Счетчик исключений

while (true)

{ Console.Write("Введите напряжение (110, 127, 220): ");

try

{

string input = Console.ReadLine();

if (!double.TryParse(input, out U))

throw new Exception("Ошибка в формате данных!");

if (U != 110 & U != 127 & U != 220)

throw new Exception(input+" - недопустимое значение!");

}

catch (Exception ex)

{

Console.WriteLine(ex. Message);

if (counter++ >= 2)

{ Console.WriteLine("Трижды ошиблись!");

return;

}

continue;

}

break;

} // end of while

Console.WriteLine("Потребляемая мощность: {0,5:f2}", U * U / R);

}

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

* Первый сеанс:

Введите напряжение (110, 127, 220): 120<ENTER>

120 - недопустимое значение!

Введите напряжение (110, 127, 220): 110f<ENTER>

Ошибка в формате данных!

Введите напряжение (110, 127, 220): asd<ENTER>

Ошибка в формате данных!

Трижды ошиблись!

* Второй сеанс:

Введите напряжение (110, 127, 220): 127<ENTER> Потребляемая мощность: 161,29

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

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

static double scalar(double[] x, double[] y)

{

if (x.Length != y.Length)

throw new Exception("Неверные размеры векторов!");

}

Обработку исключений, формируемых методом при ошибке в его аргументах, обычно выполняют в коде, из которого выполнено обращение к методу. (Там, где заданы конкретные аргументы.) В следующей программе обращения к методу scalar() размещены в try-блоке. Первое обращение корректно, во втором допущена ошибка - размеры массивов-аргументов не совпадают. Текст программы:

// 16_06.cs - исключения в теле метода

static double scalar(double[] x, double[] y)

{

if (x.Length != y.Length)

throw new Exception("Неверные размеры векторов!");

double result = 0;

for (int i = 0; i < x.Length; i++)

result += x[i] * y[i];

return Math.Sqrt(result);

}

public static void Main06()

{

try

{

Console.WriteLine("scalar1="+ scalar(new double[] {1,2,3,4}, new double[] {1,2,3,4}));

Console.WriteLine("scalar2="+scalar(new double[] {1,2,3,4}, new double[] {1,2,3}));

}

catch (Exception ex)

{

Console.WriteLine(ex.Message);

}

}

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

scalar1 = 5,47722557505166 Неверные размеры векторов!

Соседние файлы в папке Lekc_C#