Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 3450 / 677 | Оценка: 4.41 / 4.23 | Длительность: 09:18:00
ISBN: 978-5-9963-0259-8
Лекция 2:

Типы и классы. Переменные и объекты

Исключения и охраняемые блоки. Первое знакомство

В этом примере мы впервые встретились с исключениями и охраняемыми try -блоками. Исключениям и способам их обработки посвящена отдельная лекция, но не стоит откладывать надолго знакомство со столь важным механизмом.

Начну с определений. Любая последовательность операторов программы, заключенная в фигурные скобки, образует блок. Блок, которому предшествует ключевое слово try, называется охраняемым блоком или try-блоком. Блок, которому предшествует конструкция catch(<catch параметр>), называется блоком перехватчиком исключения или catch-блоком.

Блоки играют важную роль в структуре программы. Синтаксически блок воспринимается как один оператор программы - составной оператор. Там, где по синтаксису должен быть один оператор, а содержательно необходима последовательность операторов, эта последовательность заключается в скобки, образуя блок. И синтаксис удовлетворен, и содержательная сторона не страдает. Поскольку каждый оператор внутри блока может быть в свою очередь блоком, то для блоков характерна вложенность.

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

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

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

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

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

Если в некотором модуле предполагается возможность появления исключений, то разумно предусмотреть и их обработку. В этом случае в модуле создается охраняемый try -блок. Вслед за этим блоком следуют один или несколько блоков, перехватывающих исключения, - catch -блоков. Каждый catch -блок имеет формальный параметр класса Exception или одного из его потомков. Если в try -блоке возникает исключение типа T, то catch -блоки начинают конкурировать в борьбе за перехват исключения. Первый по порядку catch -блок, тип формального аргумента которого согласован с типом T - совпадает с ним или является его потомком, - захватывает исключение и начинает выполняться; поэтому порядок написания catch -блоков небезразличен. Вначале должны идти специализированные обработчики. Универсальным обработчиком является catch -блок с формальным параметром родового класса Exception, согласованным с исключением любого типа T. Универсальный обработчик, если он есть, стоит последним, поскольку захватывает исключение любого типа. По сути, последовательность catch-блоков соответствует схеме разбора случаев, применяемой в операторе switch.

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

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

Контролируемый ввод данных

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

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

Тест 1. Объявления с инициализацией и без нее

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

На рис. 2.6 показана спроектированная форма в процессе работы.

Форма теста 1 в процессе работы

Рис. 2.6. Форма теста 1 в процессе работы

Давайте поговорим подробнее о демонстрируемом фрагменте кода:

int x, y;
y = 1;
double u = y + 0.5, v = u*u +2, w = u+v;
uint k = new uint(), l = k + 1;
int n = y + 1;
//int m = x + 1;

В первой строке объявляются две сущности с именами x и y типа int без инициализации. В результате этого объявления в стеке будут созданы две переменные (два объекта), им будет выделена нужного размера память под значения, но сами значения останутся неопределенными. Во второй строке кода в результате присваивания переменная y получит значение. В третьей строке кода объявляются с инициализацией три сущности с именами u, v, w типа double. В стеке будут созданы три переменные, им будет отведена память под значения. Поскольку на этапе компиляции инициализирующие выражения могут быть вычислены, то значения этих переменных будут определены.

В следующей строке кода объявляются с инициализацией две сущности с именами k и l типа uint. Для сущности k используется объектный стиль инициализации с вызовом конструктора объектов класса uint. Конструктор без параметров этого класса не только создает в стеке соответствующий объект, но и инициализирует его значением по умолчанию, равным нулю для этого типа. Переменная l в результате инициализации также получит значение.

Аналогичным образом в результате объявления в стеке будет создана и переменная с именем n, которая также получит значение. А вот попытка создать и инициализировать переменную m закончится ошибкой еще на этапе компиляции, поскольку выражение инициализатора ссылается на переменную x, значение которой все еще не определено. Поэтому соответствующий оператор кода закомментирован.

Вот еще один тест на эту же тему:

void SimpleVars()
{
  //Объявления локальных переменных
  int x, s; //без инициализации
  int y =0, u = 77; //обычный способ инициализации
  //допустимая инициализация
  float w1=0f, w2 = 5.5f, w3 =w1+ w2 + 125.25f;
  //допустимая инициализация в объектном стиле
  int z= new int();
  // Недопустимая инициализация.
  //Конструктор с параметрами не определен
  //int v = new int(77);
  
  x=u+y; //теперь x инициализирована
  if(x> 5) s = 4;
  for (x=1; x<5; x++)s=5;
  //Инициализация в if и for не рассматривается,
  //поэтому s считается неинициализированной переменной
  //Ошибка компиляции:
  //использование неинициализированной переменной
  //x = s;
}// SimpleVars

В первой строке объявляются переменные x, s с отложенной инициализацией. Последующие объявления переменных эквивалентны по сути, но демонстрируют два стиля инициализации - обычный и объектный. Обычная форма инициализации предпочтительнее не только в силу своей естественности, она еще и более эффективна, поскольку в этом случае инициализирующее выражение может быть достаточно сложным, с переменными и функциями. На практике объектный стиль для скалярных переменных используется редко. Вместе с тем полезно понимать, что объявление с инициализацией int y = 0 можно рассматривать как создание нового объекта ( new ) и вызова для него конструктора по умолчанию. При инициализации в объектной форме может быть вызван только конструктор по умолчанию, другие конструкторы с параметрами для базисных встроенных типов не определены. В примере закомментировано объявление переменной v с инициализацией в объектном стиле, приводящее к ошибке, где делается попытка дать переменной значение, передавая его конструктору в качестве параметра.

Откладывать инициализацию не стоит, как показывает пример с переменной s, объявленной с отложенной инициализацией. В вычислениях она дважды получает значение: один раз в операторе if, другой - в операторе цикла for. Тем не менее, при компиляции возникнет ошибка, утверждающая, что в присваивании x = s делается попытка использовать неинициализированную переменную s. Связано это с тем, что для операторов if и for на этапе компиляции не вычисляются условия, зависящие от переменных. Поэтому компилятор предполагает худшее - условия ложны, инициализация s в этих операторах не происходит. А за инициализацией наш компилятор следит строго, ты так и знай!

Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?