русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Теоретический материал


Дата добавления: 2013-12-23; просмотров: 1615; Нарушение авторских прав


Практикум

    1. Разработать метод для нахождения минимального из двух чисел. Вычислить с помощью него значение выражения .

Пример.

using System;namespace Hello{ class Program { static double min(double a, double b) { return (a < b) ? a : b; } static void Main(string[] args) { Console.Write("x="); double x = double.Parse(Console.ReadLine()); Console.Write("y="); double y = double.Parse(Console.ReadLine()); double z = min(3 * x, 2 * y) + min(x - y, x + y); Console.WriteLine("z=" + z); } }}
  1. Постройте таблицу значений функции для с шагом .

Замечание. Для решения задачи использовать вспомогательный метод.

Пример:

using System;namespace Hello{ class Program { static double f (double x) { double y; if (x >= 0.9) y = 1 / Math.Pow(1 + x, 2); else if (x >= 0) y = 0.2 * x + 0.1; else y = x * x + 0.2; return y; } static void Main(string[] args){ Console.Write("a="); double a = double.Parse(Console.ReadLine()); Console.Write("b="); double b = double.Parse(Console.ReadLine()); Console.Write("h="); double h = double.Parse(Console.ReadLine()); for (double i = a; i <= b; i += h) Console.WriteLine("f({0:f2})={1:f4}", i, f(i)); } }}
  1. Перегрузите метод из предыдущего раздела так, чтобы его сигнатура (заголовок) соответствовала виду static void f (double x, out double y). Продемонстрируйте работу перегруженных методов.

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

Пусть a1, a2, …, an - произвольная числовая последовательность. Рекуррентным соотношением называется такое соотношение между членами последовательности, в котором каждый следующий член выражается через несколько предыдущих, т.е ak = f(ak-1, ak-2, …, ak-l, l < k (1).

Последовательность задана рекуррентно, если для нее определено рекуррентное соотношение вида (1) и заданы первые l ее членов.



Самым простым примером рекуррентной последовательности является арифметическая прогрессия. Рекуррентное соотношение для нее записывается в виде: ak = ak-1 + d, где d - разность прогрессии. Зная первый элемент и разность прогрессии, и, используя данное рекуррентное соотношение, можно последовательно вычислить все остальные члены прогрессии.

Рассмотрим пример программы, в которой вычисляются первые n членов арифметической прогрессии при условии, что a1=1/2 и d=1/4.

static void Main() { Console.Write("a="); double a = double.Parse(Console.ReadLine()); Console.Write("h="); double d = double.Parse(Console.ReadLine()); Console.Write("n="); int d = int.Parse(Console.ReadLine()); Console.WriteLine("a1="+ a); //вывели первый член последовательности //организуем вычисление 2, 3, … ,n члена последовательности for (int i = 2; i <= n; ++i) { a += d; //для этого прибавляем к предыдущему члену значение d Console.WriteLine("a{0}={1}", i, a); //и выводим новое значение а на экран }}

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

n состояние экрана5a1: 0.5a2: 0.75 a3: 1.a4: 1.25a5: 1.5

Более сложная зависимость представлена в последовательности Фибоначчи: a1 = a2= 1, an = an-1 + an-2. В этом случае каждый член последовательности зависит от значений двух предыдущих членов. Рассмотрим пример программы, в которой вычисляются первые n членов последовательности Фибоначчи.

static void Main(){//задали известные члены последовательности int a1=1, a2=1, a3; Console.Write("n="); int n = int.Parse(Console.ReadLine()); //вывели известные члены последовательности Console.WriteLine("a1={0}\na2={1}",a1,a2); /*Организуем цикл для вычисления членов последовательности с номерами 3 4…n. При этом в переменной a1 будет храниться значение члена последовательности с номером i-2,в переменной a2 - члена с номером i-1, переменная а будет использоваться для вычисления члена с номером i. */ for (int i = 3; i <= n; ++i){ //по рекуррентному соотношению вычисляем член последовательности с номером i //и выводим его значение на экранa3=a1+a2; Console.WriteLine("a{0}={1}", i, a3); //выполняем рекуррентный пересчет для следующего шага цикла a1 = a2; //в элемент с номером i-2 записываем значение элемента с номером i-1 a2 = a3; //в элемент с номером i-1 записываем значение элемента с номером i }}

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

n состояние экрана5 a1: 1 a2: 1 a3: 2 a4: 3 a5: 5

Лекция 6. Рекурсивные методы

6.1 Прямая рекурсия

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

Из курса математики известно, что 0!=1!=1, n!=1*2*3…*n. С другой стороны n!=(n-1)!*n. Таким образом, известны два частных случая параметра n, а именно n=0 и n=1, при которых мы без каких-либо дополнительных вычислений можем определить значение факториала. Во всех остальных случаях, то есть для n>1, значение факториала может быть вычислено через значение факториала для параметра n-1. Таким образом, рекурсивный метод будет иметь вид:

{ static long F(int n) //рекурсивный метод { if (n==0 || n==1) return 1; //нерекурсивная ветвь else return n*F(n-1); //шаг рекурсии - повторный вызов метода с другим параметром } static void Main() { Console.Write("n="); int n =int.Parse( Console.ReadLine()); long f=F(n); //нерекурсивный вызов метода F Console.WriteLine("{0}!={1}",n, f); }}

Рассмотрим работу описанного выше рекурсивного метода для n=3.

 

Первый вызов метода осуществляется из метода Main, в нашем случае командой f=F(3). Этап вхождения в рекурсию обозначим жирными стрелками. Он продолжается до тех пор, пока значение переменной n не становится равной 1. После этого начинается выход из рекурсии (тонкие стрелки). В результате вычислений получается, что F(3)=3*2*1.

Рассмотренный вид рекурсии называют прямой. Метод с прямой рекурсией обычно содержит следующую структуру:

if (<условие>) <оператор>; else <вызов данного метода с другими параметрами>;

В качестве <условия> обычно записываются некоторые граничные случаи параметров, передаваемых рекурсивному методу, при которых результат его работы заранее известен, поэтому далее следует простой оператор или блок, а в ветви else происходит рекурсивный вызов данного метода с другими параметрами.

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

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

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

Пример 1:Найти сумму цифр числа А.

Известно, что любое натуральное число A=an an-1… a1 a0, где an an-1… a1 a0 - цифры числа, можно представить следующим образом:

A=an an-1… a1 a0 = A=an*10n + an-1*10n-1 + … a1*101 + a0*100 = ((…((an*10 + an-1)*10+ an-2)*10…)*10 + a1)*10 + a0

Например, число 1234 можно представить как:

1234 = 1*103 + 2*102 + 3*101 + 4*100 = ((1*10 + 2)*10 + 3)*10 + 4

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

  1. Находим остаток при делении числа А на 10, т.е. получаем крайнюю правую цифру числа.
  2. Находим целую часть числа при делении A на 10, т.е. отбрасываем от числа A крайнюю правую цифру.
  3. Если преобразованное A > 0, то переходим на пункт 1. Иначе число равно нулю и отделять от него больше нечего.

Данный алгоритм будет использоваться при разработке нерекурсивного метода.

С другой стороны, сумму цифр числа 1234 можно представить следующим образом sum(1234)=sum(123)+4=(sum(12)+3)+4=(((sum(1)+2)+3)+4)=(((sum(0)+1)+2)+3)+4. Таким образом, если А=0, то сумма цифр числа также равна нулю, т.е. sum=0. В противном случае сумму цифр числа A можно представить рекуррентным соотношением sum(A)=sum(A/10)+A%10. Полученное рекуррентное соотношение будем использовать при разработке рекурсивного метода.

class Program { static long Sum(long a) //нерекусивный метод { long sum=0; while (a>0) //пока a больше нуля { sum+=a%10; //добавляем к сумме последнюю цифру числа а a/=10; //отбрасываем от числа а последнюю цифру } return sum; //возвращаем в качестве результата сумму цифр числа a } static long SumR(long a) //рекурсивный метод { if (a==0) //если a =0, то return 0; // возвращаем 0 else return SumR(a/10)+ a%10; //иначе обращаемся к рекуррентному соотношению } static void Main() { Console.Write("n="); long n=long.Parse(Console.ReadLine()); Console.WriteLine("Нерекурсивный метод: "+Sum(n)); Console.WriteLine("Рекурсивный метод: "+SumR(n)); } }}

Задание. Изменить методы так, чтобы на экран выводилось количество цифр в числе n.

Пример 2:вычислить n-ный член последовательности Фиббоначи.

Первые два члена последовательности Фиббоначи равны 1, остальные получаются по рекуррентной формуле an=an-1+an-2.

class Program { static int Fb(int n) //нерекурсивный алгоритм { int a, a1=1, a2=1; if (n==1||n==2) return 1; else { for (int i=2; i<=n; ++i) { a=a1+a2; a1=a2; a2=a; } return a1; } } static int FbR(int n) //рекурсивный алгоритм { if (n==1 || n==2 )return 1; else return FbR(n-1)+FbR(n-2); } static void Main() { Console.Write("n="); int n=int.Parse(Console.ReadLine()); Console.WriteLine("Нерекурсивный метод: "+Fb(n)); Console.WriteLine("Рекурсивный метод: "+FbR(n)); } }

Задание. Изменить методы так, чтобы на экран выводилась сумма n элементов последовательности Фиббоначи.

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

Пример 3. Для заданного значения n вывести на экран n строк, в каждой из которых содержится n звездочек. Например, для n=5 на экран нужно вывести следующую таблицу:

*************** class Program { static void Stroka(int n) //выводит на экран строку из n звездочек { for (int i=1; i<=n; ++i) { Console.Write('*'); } Console.WriteLine(); } static void Star(int n) //нерекурсивный метод { for (int i=1; i<=n;++i) //выводит n строк по i звездочек в каждой Stroka(i); } //рекурсивный метод, где i – номер текущей строки, n – номер последней строк static void StarR(int i,int n) { if (i<=n ) //если номер текущей строки не больше номера последней строки, то { Stroka(i); //выводим i звездочек в текущей строке и StarR(i+1,n); //переходим к формированию следующей строки } } static void Main() { Console.Write("n="); int n=int.Parse(Console.ReadLine()); Console.WriteLine("Нерекурсивный метод: "); Star(n); Console.WriteLine("Рекурсивный метод: "); StarR(1,n); // параметр 1 – это номер первой строки, n – номер последней строки } }

Задание. Изменить методы так, чтобы для заданного значения n (в нашем случае для n=5) на экран выводилась следующая таблица:

*****

****

***

**

*

Пример 4. Для заданного значения n (например для n=7) вывести на экран следующую таблицу:

* * * * * * * * * * * * * * * * * * * * * * * * ** * * * * * *

Данную таблицу условно можно разделить на две части. Рассмотрим отдельно верхнюю часть:

Номер строки Содержимое экрана i - количество пробелов в строке Количество звездочек в строке
*******
*****
***
*

Таким образом, если нумеровать строки с нуля, то номер строки совпадает с количеством пробелов, которых нужно напечатать в начале этой строки. При этом количество звездочек в строке, можно определить по формуле n-2i, где n – это количество звездочек в нулевой строке. Так как количество звездочек в каждой строке уменьшается на 2, то всего нужно напечатать n/2+1 строк.

Аналогичную зависимость можно выявить и для нижней части таблицы.

class Program { static void Stroka(int n, char a) //выводит на экран n раз символ а { for (int i=1; i<=n; ++i) { Console.Write(a); } } static void Star(int n) //нерекурсивный метод { for (int i=0; i<=n/2;++i) //выводим верхнюю часть таблицы, в которой в каждой строке вначале { Stroka(i,' '); //печатаем пробелы Stroka(n-2*i,'*'); //затем звездочки Console.WriteLine(); //затем переводим курсор на новую строку } for (int i=n/2; i>=0;--i) // аналогично выводим нижнюю часть таблицы { Stroka(i,' '); Stroka(n-2*i,'*'); Console.WriteLine(); } } //рекурсивный метод, где i определяет номер текущей строки, n – количество звездочек в строке static void StarR(int i, int n) { if (n>0 ) { //действия до рекурсивного вызова – позволят вывести верхнюю часть таблицы Stroka(i, ' '); Stroka(n, '*'); Console.WriteLine(); //вызываем этот же метод, увеличивая номер строки, и уменьшая количество звездочек в ней StarR(i+1,n-2); //действия после рекурсивного вызова – позволят вывести нижнюю часть таблицы Stroka(i, ' '); Stroka(n, '*'); Console.WriteLine(); } } static void Main() { Console.Write("n="); int n=int.Parse(Console.ReadLine()); Console.WriteLine("Нерекурсивный метод: "); Star(n); Console.WriteLine("Рекурсивный метод: "); StarR(0,n); } }}

Задание. Изменить методы так, чтобы для заданного значения n (в нашем случае для n=7) на экран выводилась следующая таблица:

* * * * * * * * ** * * * * * ** * * * * * * * * * * * * * * *

 

6.2 Косвенная рекурсия

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

Данная программа содержит метод Prim, который возвращает true, если его параметр является простым числом, false – в противном случае. Чтобы установить, является ли число j простым, нужно проверить делимость числа j на все простые числа, не превышающие квадратный корень из j. Перебор таких простых чисел можно организовать так: рассмотреть первое простое число – 2, а затем, используя метод NextPrim, возвращающий следующее за значением ее параметра простое число, получить все простые числа, не превышающие квадрата числа j. В свою очередь метод NextPrim обращается к методу Prim для того, чтобы определить является ли заданное число простым.

Таким образом методы Prim и NextPrim перекрестно вызывают друг друга. В этом и проявляется косвенная рекурсия.

class Program { static bool Prim (int j) { int k=2; //первое простое число //значение k "пробегает" последовательность простых чисел, начиная с 2 до корня из j, при //этом проверяется делится ли j на одно из таких простых чисел while (k*k<=j && j%k!=0) k=NextPrim(k); //вызов метода NextPrim return (j%k==0)?false:true; } static int NextPrim(int i) { int p=i+1; while (!Prim(p)) //вызов метода Prim ++p; return p; } static void Main() { Console.Write("n="); int n=int.Parse(Console.ReadLine()); Console.WriteLine("Следующее за {0} простое число равно {1}.", n, NextPrim(n)); } }

Задание. Изменить программу так, чтобы на экран выводились все простые числа меньшие N.

Рекурсия является удобным средством решения многих задач: сортировки числовых массивов, обхода таких структур данных как деревья и графы.

С другой стороны, применение рекурсивных методов в ряде случаев оказывается нерациональным. Вспомним рекурсивный метод подсчета n-ного члена последовательности Фиббоначи. Данный метод будет работать весьма неэффективно. FbR(17) вычисляется в ней как FbR(16)+ FbR(15). В свою очередь FbR(16) вычисляется в ней как FbR(15)+ FbR(14). Таким образом, FbR(15) будет вычисляться 2 раза, FbR(14) – 3 раза, FbR(13) – 5 раз и т.д. Всего для вычисления FbR(17) потребуется выполнить более тысячи операций сложения. Для сравнения при вычислении Fb(17), т.е. используя не рекурсивный метод, потребуется всего лишь 15 операций сложения.

Таким образом, при разработке рекурсивного метода следует задуматься об его эффективности.

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

Задача 1. Разработать рекурсивный метод для вывода на экран всех возможных разложений натурального числа n на множители (без повторений). Например, для n=12 на экран должно быть выведено:

2*2*3=122*6=123*4=12

Задача 2. Разработать рекурсивный метод для вывода на экран всех возможных разложений натурального числа n на слагаемые (без повторений). Например, для n=5 на экран должно быть выведено:

1+1+1+1+1=51+1+1+2=51+1+3=51+4=52+1+2=52+3=5

 


Лекция 7. Обработка исключений

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

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

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

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

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

7.1 Оператор 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.

Создайте указанные исключительные ситуации и посмотрите, как отреагирует на них система.

Задание. Переменные x и y объявлены целочисленными. Объясните, что будет выведено на экран, если замените их тип на double и ввести с клавиатуры значение х равное 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 Переполнение стека

Одно из основных достоинств обработки исключений состоит в том, что она позволяет программе отреагировать на ошибку и продолжить выполнение. Рассмотрим программу, которая строит таблицу значений для функции вида y(x)=100/(x2-1).

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

7.2 Операторы 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-формой
2. checked ((тип-выражения) expr)

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

  1. проверяет блок инструкций
4. checked 5. {6. // Инструкции, подлежащие проверке.7. }

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

  1. операторная форма, которая позволяет игнорировать переполнение для заданного выражения
2. unchecked ((тип-выражения) expr)

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

  1. игнорирует переполнение, которое возможно в блоке инструкций
4. unchecked 5. {6. // Инструкции, для которых переполнение игнорируется.7. }

Рассмотрим пример программы, которая демонстрирует использование 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возникло переполнение

Задание. Уберите блок uncheсked. Посмотрите, что изменится в выполнении программы и дайте этому объяснение

В данном примере мы посмотрели, как использовать 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возникло переполнение

Задание. Поменяйте местами блоки unchecked и checked. Посмотрите, что изменится в выполнении программы и дайте этому объяснение



<== предыдущая лекция | следующая лекция ==>
Практикум | Генерация собственных исключений


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.008 сек.