Генерация собственных исключений
До сих пор мы рассматривали исключения, которые генерирует среда, но сгенерировать исключение может и сам программист. Для этого необходимо воспользоваться оператором 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);
}
}
Полезные совет
Рассмотрим несколько полезных приемов использования обработчиков исключений.
Пример 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-инструкции.
Задания.
· Объясните почему не было сгенерировано исключение DivideByZeroException.
· Сгенерируйте собственное исключение 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
Массивы
Массив — набор элементов одного и того же типа, объединенных общим именем. Массивы в С# можно использовать по аналогии с тем, как они используются в других языках программирования. Однако С#-массивы имеют существенные отличия: они относятся к ссылочным типам данных, более того - реализованы как объекты. Фактически имя массива является ссылкой на область кучи (динамической памяти), в которой последовательно размещается набор элементов определенного типа. Выделение памяти под элементы происходит на этапе инициализации массива. А за освобождением памяти следит система сборки мусора - неиспользуемые массивы автоматически утилизируются данной системой.
Рассмотрим различные типы массивов.
Одномерные массивы
Одномерный массив – это фиксированное количество элементов одного и того же типа, объединенных общим именем, где каждый элемент имеет свой номер. Нумерация элементов массива в С# начинается с нуля, то есть, если массив состоит из 10 элементов, то его элементы будут иметь следующие номера: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.
Одномерный массив в С# реализуется как объект, по этому его создание представляет собой двухступенчатый процесс. Сначала объявляется ссылочная переменная на массив, затем выделяется память под требуемое количество элементов базового типа, и ссылочной переменной присваивается адрес нулевого элемента в массиве. Базовый тип определяет тип данных каждого элемента массива. Количество элементов, которые будут храниться в массиве, определяется размер массива.
В общем случае процесс объявления переменной типа массив, и выделение необходимого объема памяти может быть разделено. Кроме того на этапе объявления массива можно произвести его инициализацию. Поэтому для объявления одномерного массива может использоваться одна из следующих форм записи:
Форма записи | Пояснения |
базовый_тип [] имя__массива; Например: int [] a; | Описана ссылка на одномерный массив, которая в дальнейшем может быть использована: 1) для адресации на уже существующий массив; 2) передачи массива в метод в качестве параметра 3) отсроченного выделения памяти под элементы массива. |
базовый_тип [] имя__массива = new базовый_тип [размер]; Например: int []a=new int [10]; | Объявлен одномерный массив заданного типа и выделена память под одномерный массив указанной размерности. Адрес данной области памяти записан в ссылочную переменную. Элементы массива равны нулю. Замечание. Надо отметить, что в C# элементам массива присваиваются начальные значения по умолчанию в зависимости от базового типа. Для арифметических типов – нули, для ссылочных типов – null, для символов - пробел. |
базовый_тип [] имя__массива={список инициализации}; Например: int []a={0, 1, 2, 3}; | Выделена память под одномерный массив, размерность которого соответствует количеству элементов в списке инициализации. Адрес этой области памяти записан в ссылочную переменную. Значение элементов массива соответствует списку инициализации. |
Обращения к элементам массива происходи с помощью индекса, для этого нужно указать имя массива и в квадратных скобках его номер. Например, a[0], b[10], c[i].
Так как массив представляет собой набор элементов, объединенных общим именем, то обработка массива обычно производится в цикле. Рассмотрим несколько простых примеров работы с одномерными массивами.
Пример 1.
static void Main()
{
int[] myArray = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int i;
for (i = 0; i < 10; ++i)
Console.WriteLine(myArray[i]);
}
Задание. Измените программу так, чтобы числа выводились в строчку.
Пример 2.
static void Main()
{
int[] myArray = new int[10];
int i;
for (i = 0; i < 10; i++)
myArray[i] = i * i;
for (i = 0; i < 10; i++)
Console.WriteLine(myArray[i]);
}
Задание. Измените программу так, чтобы обрабатывался массив из n чисел.
Хотя при инициализации массива нет необходимости использовать операцию new, все же массив можно инициализировать следующим образом:
int [ ] myArray = new int [ ] { 99, 10, 100, 18, 78, 23, 163, 9, 87, 49 };
Несмотря на избыточность, данная форма инициализации массива может оказаться полезной в том случае, когда уже существующей ссылке на одномерный массив присваивается ссылка на новый массив. Например:
static void Main()
{
int[] myArray = { 0, 1, 2, 3, 4, 5};
int i;
for (i = 0; i < 10; i++)
Console.Write(" "+myArray[i]);
Console.WriteLine("\nНовый массив: ");
myArray = new int[] { 99, 10, 100, 18, 78, 23, 163, 9, 87, 49 }; // 1
for (i = 0; i < 10; i++)
Console.Write(" " + myArray[i]);
}
Следует отметить, что первоначально переменная myArray ссылалась на 6-ти элементный массив. В строке 1 переменной myArray была присвоена ссылка на новый 10-элементный массив, в результате чего исходный массив оказался неиспользуемым, т.к. на него теперь не ссылается ни один объект. Поэтому он автоматически будет удален сборщиком мусора.
Массивы и исключения
Выход за границы массива в C# расценивается как ошибка, в ответ на которую генерируется исключение - IndexOutOfRangeException.
Рассмотрим следующий пример:
static void Main()
{
int[] myArray = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int i;
try
{
for (i = 0; i <= 10; i++) Console.WriteLine(myArray[i]);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Exception: Выход за границу диапазона");
}
}
Задание. Добавьте в программу обработчики исключений FormatException и OutOfMemoryException. Вспомните, что они контролируют.
Класс Random и его функции
Умение генерировать случайные числа требуется во многих приложениях. Класс Random содержит все необходимые для этого средства. Класс Random имеет конструктор класса: для того, чтобы вызы-вать методы класса, нужно вначале создавать экземпляр (объект) класса. Созданный объект обеспечи-вает получение псевдослучайных (почти случайных…J) чисел. Этим Random отличается от класса Math, у которого все поля и методы – статические, что позволяет обойтись без создания экземпляров класса Math.
Как всякий "настоящий" класс, класс Random является наследником класса Object, а, следовательно, имеет в своём составе и методы родителя. Рассмотрим часть оригинальных методов класса Random, используемых для генерирования последовательностей случайных чисел.
Начнём рассмотрение с конструктора класса. Конструктор обеспечивает создание объектов класса. Он имеет две реализации. Одна из них позволяет генерировать неповторяющиеся при каждом запуске серии случайных чисел. Начальный элемент такой серии строится на основе текущей даты и времени, что гарантирует уникальность серии. Этот конструктор вызывается без параметров. Он описан как Random(). Другой конструктор с параметром целого типа – Random(int) обеспечивает важную возмож-ность генерирования повторяющейся серии случайных чисел. Параметр конструктора используется для построения начального элемента серии, поэтому при задании одного и того же значения параметра се-рия будет повторяться.
Перегруженный метод Next() при каждом вызове возвращает положительное целое, равномерно рас-пределённое в некотором диапазоне. Диапазон задаётся параметрами метода. Три реализации метода отличаются набором параметров:
1. Next () – метод без параметров выдаёт целые положительные числа во всём положительном диапазоне типа int;
2. Next (max) – выдаёт целые положительные числа в диапазоне [0, max];
3. Next (min, max) – выдаёт целые положительные числа в диапазоне [min, max].
Метод NextDouble() имеет одну реализацию. При каждом вызове этого метода выдаётся новое слу-чайное число, равномерно распределённое в интервале [0, 1].
Ещё один полезный метод класса Random позволяет при одном обращении получать целую серию случайных чисел. Метод имеет параметр – массив, который и будет заполнен случайными числами.
Метод описан как NextBytes(buffer). Так как параметр buffer представляет массив байтов, то, естест-венно, генерированные случайные числа находятся в диапазоне [0, 255].
Рассмотрим пример работы со случайными числами:
/// <summary>
/// Эксперименты с классом Random
/// </summary>
public void Rand()
{
const int initRnd = 77;
Random realRnd = new Random();
Random repeatRnd = new Random(initRnd);
// случайные числа в диапазоне [0,1)
Console.WriteLine("случайные числа в диапазоне[0,1)");
for(int i =1; i <= 5; i++)
{
Console.WriteLine("Число " + i + "= "
+ realRnd.NextDouble() );
}
// случайные числа в диапазоне[min,max]
int min = -100, max=-10;
Console.WriteLine(«случайные числа в диапазоне [" +
min +"," + max + "]«);
for(int i =1; i <= 5; i++)
{
Console.WriteLine(«Число » + i + «= «
+ realRnd.Next(min,max) );
}
// случайный массив байтов
byte[] bar = new byte[10];
repeatRnd.NextBytes(bar);
Console.WriteLine(«Массив случайных чисел в диапазоне
[0, 255]«);
for(int i =0; i < 10; i++)
{
Console.WriteLine(«Число » + i + «= » +bar[i]);
}
}//Rand
Приведу краткий комментарий к тексту программы. Вначале создаются два объекта класса Random. У этих объектов разные конструкторы. Объект с именем realRnd позволяет генерировать неповторяющиеся серии случайных чисел. Объект repeatRnd дает возможность повторить при необходимости серию. Метод NextDouble создает серию случайных чисел в диапазоне [0, 1). Вызываемый в цикле метод Next с двумя параметрами создает серию случайных положительных целых, равномерно распределенных в диапазоне [-100, -10]. Метод NextBytes объекта repeatRnd позволяет получить при одном вызове массив случайных чисел из диапазона [0, 255]. Результаты вывода можно увидеть на рис. 7.2.
Рис. 7.2. Генерирование последовательностей случайных чисел в процедуре Rand
На этом заканчивается рассмотрение темы выражений языка C#.
е double в тип bool приводит к ошибке, потому и закомментировано.
Массив как параметр
Так как имя массива фактически является ссылкой, то он передается в метод по ссылке и, следовательно, все изменения элементов массива, являющегося формальным параметром, отразятся на элементах соответствующего массива, являющимся фактическим параметром.
Рассмотрим пример передачи массива как параметра:
class Program
{
static void Print(int n, int[] a) //n – размерность массива, а – ссылка на массив
{
for (int i = 0; i < n; i++) Console.Write("{0} ", a[i]);
Console.WriteLine();
}
static void Change(int n, int[] a)
{
for (int i = 0; i < n; i++)
if (a[i] > 0) a[i] = 0; // изменяются элементы массива
}
static void Main()
{
int[] myArray = { 0, -1, -2, 3, 4, 5, -6, -7, 8, -9 };
Print(10, myArray);
Change(10, myArray);
Print(10, myArray);
}
}
Задание. Измените программу так, чтобы метод Change удваивал значения положительных элементов массива.
Массив как объект
Мы уже говорили о том, что массивы в С# реализованы как объекты. Если говорить более точно, то они реализованы на основе базового класса Array, определенного в пространстве имен System. Данный класс содержит различные свойства и методы. Например, свойство Length позволяет определять количество элементов в массиве. Преобразуем предыдущий пример:
class Program
{
static void Print(int[] a) // передаем только ссылку на массив
{
for (int i = 0; i < a.Length; i++) Console.Write("{0} ", a[i]);
Console.WriteLine();
}
static void Change(int[] a)
{
for (int i = 0; i < a.Length; i++)
if (a[i] > 0) a[i] = 0;
}
static void Main()
{
int[] myArray = { 0, -1, -2, 3, 4, 5, -6, -7, 8, -9 };
Print(myArray);
Change(myArray);
Print(myArray);
}
}
Другие свойства и методы класса Array приведены в следующей таблице:
Элемент | Вид | Описание |
Length | свойство | Количество элементов массива (по всем размерностям) |
BinarySearch | статический метод | Двоичный поиск в отсортированном массиве |
Clear | статический метод | Присваивание элементам массива значений по умолчанию |
Copy | статический метод | Копирование заданного диапазона элементов одного массива в другой |
CopyTo | экземплярный метод | Копирование всех элементов текущего одномерного массива в другой массив |
GetValue | экземплярный метод | Получение значения элемента массива |
IndexOf | статический метод | Поиск первого вхождения элемента в одномерный массив |
LastIndexOf | статический метод | Поиск последнего вхождения элемента в одномерный массив |
Reverse | статический метод | Изменение порядка следования элементов на обратный |
SetValue | экземплярный метод | Установка значения элемента массива |
Sort | статический метод | Упорядочивание элементов одномерного массива |
Вызов статических методов происходит через обращение к имени класса, например, Array.Sort(myArray). В данном случае мы обращаемся к статическому методу Sort класса Array и передаем данному методу в качестве параметра объект myArray - экземпляр класса Array.
Обращение к свойству или вызов экземплярного метода производится через обращение к экземпляру класса, например, myArray.Length или myArray.GetValue(i).
Пример:
class Program
{
static void Main()
{
try
{
int[] MyArray;
Console.Write("Введите размерность массива: ");
int n = int.Parse(Console.ReadLine());
MyArray = new int[n];
for (int i = 0; i < MyArray.Length; ++i)
{
Console.Write("a[{0}]=",i);
MyArray[i] = int.Parse(Console.ReadLine());
}
PrintArray("исходный массив:", MyArray);
Array.Sort(MyArray);
PrintArray("массив отсортирован по возрастанию", MyArray);
Array.Reverse(MyArray);
PrintArray("массив отсортирован по убыванию", MyArray);
}
catch (FormatException)
{
Console.WriteLine("неверный формат ввода данных");
}
catch (OverflowException)
{
Console.WriteLine("переполнение");
}
catch (OutOfMemoryException)
{
Console.WriteLine("недостаточно памяти для создания нового объекта");
}
}
static void PrintArray(string a, int[] mas)
{
Console.WriteLine(a);
for (int i = 0; i < mas.Length; i++) Console.Write("{0} ", mas[i]);
Console.WriteLine();
}
}
}
Задание. Добавьте в программу метод InputArray, предназначенный для ввода с клавиатуры элементов массива. Продемонстрируйте работу данного метода.
Многомерные массивы
Многомерные массивы имеют более одного измерения. Чаще всего используются двумерные массивы, которые представляют собой таблицы. Каждый элемент массива имеет два индекса, первый определяет номер строки, второй – номер столбца, на пересечении которых находится элемент. Нумерация строк и столбцов начинается с нуля.
Объявить двумерный массив можно одним из предложенных способов:
тип [,] имя__массива;
тип [,] имя__массива = new тип [размер1, размер2];
тип [,] имя__массива={{элементы 1-ой строки}, … , {элементы n-ой строки}};
тип [,] имя__массива= new тип [,]{{элементы 1-ой строки}, … ,{элементы n-ой строки}};
строки}};
Например:
int [,] a;
int [,] a= new int [3, 4];
int [,] a={{0, 1, 2}, {3, 4, 5}};
int [,] a= new int [,]{{0, 1, 2}, {3, 4, 5}};
Замечания.
1. Как и в случае с одномерными массивами, последние два описания являются избыточными.
2. При работе с многомерными массивами можно использовать приемы, которые мы рассмотрели для одномерных массивов.
3. При обращении к свойству Length для двумерного массива мы получим общее количество элементов в массиве. Чтобы получить количество строк нужно обратиться к методу GetLength с параметром 0. Чтобы получить количество столбцов – к методу GetLength с параметром 1.
Пример:
class Program
{
static void PrintArray(string a, int[,] mas)
{
Console.WriteLine(a);
for (int i = 0; i < mas.GetLength(0); i++)
{
for (int j = 0; j < mas.GetLength(1); j++)
Console.Write("{0} ", mas[i, j]);
Console.WriteLine();
}
}
static void Change(int[,] mas)
{
for (int i = 0; i < mas.GetLength(0); i++)
for (int j = 0; j < mas.GetLength(1); j++)
if (mas[i, j] % 2 == 0) mas[i, j] = 0;
}
static void Main()
{
try
{
int[,] MyArray ={ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
PrintArray("исходный массив:", MyArray);
Change(MyArray);
PrintArray("итоговый массив", MyArray);
}
catch (FormatException)
{
Console.WriteLine("неверный формат ввода данных");
}
catch (OverflowException)
{
Console.WriteLine("переполнение");
}
catch (OutOfMemoryException)
{
Console.WriteLine("недостаточно памяти для создания нового объекта");
}
}
}
Задания.
7) Добавьте в программу метод InputArray, предназначенный для ввода с клавиатуры элементов массива. Продемонстрируйте работу данного метода.
8) Измените метод Change так, чтобы он вычислял сумму четных элементов двумерного массива.
Ступенчатые массивы
В ступенчатых массивах количество элементов в разных строках может быть различным. В памяти ступенчатый массив хранится в виде массива массивов. Структура ступенчатого массива:
Массив | a | a[0] | a[0][0] | a[0][1] | … | ||||||
a[1] | |||||||||||
… | a[1][0] | a[1][1] | … | ||||||||
a[n] | |||||||||||
a[n][0] | a[n][1] | … |
Объявление ступенчатого массива:
тип [][] имя_массива;
Например:
int [][]a;
Фактически мы объявили одномерный массив ссылок на целочисленные одномерные массивы. При таком описании потребуется не только выделять память под одномерный массив ссылок, но и под каждый из целочисленных одномерных массивов. Такое распределение памяти позволяет определять произвольную длину каждой строки массива (отсюда и произошло название массива – ступенчатый). Например:
int [][] a= new int [3][]; // Создаем три строки
a[0]=new int [2]; // 0-ая строка ссылается на 2-х элементый одномерный массив
a[1]=new int [3]; // 1-ая строка ссылается на 3-х элементый одномерный массив
a[2]=new int [10]; // 2-ая строка ссылается на 10-х элементый одномерный массив
Другой способ выделения памяти:
int [][] a= {new int [2], new int [3], new int [10]};
Так как каждая строка ступенчатого массива фактически является одномерным массивом, то с каждой строкой можно работать как с экземпляром класса Array. Это является преимуществом ступенчатых массивов перед двумерными массивами.
Пример:
class Program
{
static void Main()
{
try
{
int[][] MyArray;
Console.Write("Ввведите количесвто строк: ");
int n = int.Parse(Console.ReadLine());
MyArray = new int[n][];
for (int i = 0; i < MyArray.Length; i++)
{
Console.Write("введите количество элементов в {0} строке: ", i);
int j = int.Parse(Console.ReadLine());
MyArray[i] = new int[j];
for (j = 0; j < MyArray[i].Length; j++)
{
Console.Write("a[{0}][{1}]= ", i, j);
MyArray[i][j] = int.Parse(Console.ReadLine());
}
}
PrintArray("исходный массив:", MyArray);
for (int i = 0; i < MyArray.Length; i++) Array.Sort(MyArray[i]);
PrintArray("измененный массив", MyArray);
}
catch (FormatException)
{
Console.WriteLine("неверный формат ввода данных");
}
catch (OverflowException)
{
Console.WriteLine("переполнение");
}
catch (OutOfMemoryException)
{
Console.WriteLine("недостаточно памяти для создания нового объекта");
}
}
static void PrintArray(string a, int[][] mas)
{
Console.WriteLine(a);
for (int i = 0; i < mas.Length; i++)
{
for (int j = 0; j < mas[i].Length; j++) Console.Write("{0} ", mas[i][j]);
Console.WriteLine();
}
}
}
Задание. Добавьте в программу метод MakeArray, предназначенный для создания ступенчатого массива, в котором количество элементов в каждой строке больше номера строки в два раза. А сам элемент равен сумме номеров строки и столбца, в котором он находится. Продемонстрируйте работу данного метода.