ВЫПОЛНЕНИЕ ОПЕРАЦИЙ ЧТЕНИЯ И ЗАПИСИ



Все программы выполняли файловые операции над символьными строками. По мере усложнения программ понадобится читать и писать массивы и структуры. Для этого программы могут использовать функции read и write. При использовании функций read и write следует указать буфер данных, в который данные будут читаться или из которого они будут записываться, а также длину буфера в байтах, как показано ниже:

input_file.read(buffer, sizeof(buffer)) ;
output_file.write(buffer, sizeof(buffer));

Например, следующая программа использует функцию write для вывода содержимого структуры в файл EMPLOYEE.DAT:

#include <iostream.h>

#include <fstream.h>

void main(void)

{
struct employee

{
char name[64];
int age;
float salary;
} worker = { "Джон Доу", 33, 25000.0 };

ofstream emp_file("EMPLOYEE.DAT") ;
emp_file.write((char *) &worker, sizeof(employee));
}

Функция write обычно получает указатель на символьную строку. Символы (char *) представляют собой оператор приведения типов, который информирует компилятор, что передается указатель на другой тип. Подобным образом следующая программа использует метод read для чтения из файла информации о служащем:

#include <iostream.h>

#include <fstream.h>

void main(void)

{
struct employee

{
char name [6 4] ;
int age;
float salary;
} worker = { "Джон Доу", 33, 25000.0 };

ifstream emp_file("EMPLOYEE.DAT");
emp_file.read((char *) &worker, sizeof(employee));
cout << worker.name << endl;
cout << worker.age << endl;
cout << worker.salary << endl;
}

Обработка исключительных ситуаций

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

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

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

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

Замечание:

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

Общий механизм обработки исключений

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

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

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

Отыскивается соответствующий обработчик исключения и ему передается управление.

Если обработчик исключения не найден, вызывается стандартная функция terminate, которая вызывает функцию abort, аварийно завершающую текущий процесс. Можно установить собственную функцию завершения процесса.

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

Синтаксис исключений

Ключевое слово try служит для обозначения контролируемого блока — кода, в котором может генерироваться исключение. Блок заключается в фигурные скобки:

try{...

}

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

Генерация (порождение) исключения происходит по ключевому слову throw, которое употребляется либо с параметром, либо без него:

throw [ выражение ];

Тип выражения, стоящего после throw, определяет тип порождаемого исключения. При генерации исключения выполнение текущего блока прекращается, и происхо­дит поиск соответствующего обработчика и передача ему управления. Как прави­ло, исключение генерируется не непосредственно в try-блоке, а в функциях, прямо или косвенно в него вложенных.

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

Обработчики исключений начинаются с ключевого слова catch, за которым в скобках следует тип обрабатываемого исключения. Они должны располагаться непосредственно за try-блоком. Можно записать один или несколько обработчи­ков в соответствии с типами обрабатываемых исключений. Синтаксис обработ­чиков напоминает определение функции с одним параметром — типом исключе­ния. Существует три формы записи:

catch (тип имя){ ... /* тело обработчика */ }

catch (тип){ ... /* тело обработчика */ }

catch (...){ ... /* тело обработчика */ }

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

Пример:

catch(int i){

... // Обработка исключений типа int

,„}

catch(const char *){

... // Обработка исключений типа const char*

}

catch(Overflow){

... // Обработка исключений класса Overflow

}

catch(...){

... // Обработка всех необслуженных исключений }

 

После обработки исключения управление передается первому оператору, находя­щемуся непосредственно за обработчиками исключений. Туда же, минуя код всех обработчиков, передается управление, если исключение в try-блоке не было сгенерировано.

 

Перехват исключений

Когда с помощью throw генерируется исключение, функции исполнительной библиотеки С++ выполняют следующие действия:

1) создают копию параметра throw в виде статического объекта, который сущест­вует до тех пор, пока исключение не будет обработано;

2) в поисках подходящего обработчика раскручивают стек, вызывая деструкторы локальных объектов, выходящих из области действия;

3) передают объект и управление обработчику, имеющему параметр, совместимый по типу с этим объектом.

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

Обработчик считается найденным, если тип объекта, указанного после throw:

  • тот же, что и указанный в параметре catch (параметр может быть записан в форме Т, const Т. T& или const T&, где Т— тип исключения);
  • является производным от указанного в параметре catch (если наследование производилось с ключом доступа public);
  • является указателем, который может быть преобразован по стандартным пра­вилам преобразования указателей к типу указателя в параметре catch.

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

Рассмотрим пример.

#include <fstream>

class Hel1o{

// Класс, информирующий о своем создании и уничтожении

public:

Hello () {cout << "Hello!" << endl;}

~Hello () {cout << "Bye!" << endl;}

};

void f1 (){

ifstream ifs("\\INVALID\\FILE\\NAME"); // Открываем файл if (!ifs){

cout « "Генерируем исключение" « endl; throw "Ошибка при открытии файла";}

}

void f2(){

Hello H; // Создаем локальный объект

f1(): // Вызываем функцию, генерирующую исключение

}

int main(){

try{

cout << "Входим в try-блок" << endl:

f2():

cout << "Выходим из try-блока" << endl;

}

catch (int i){

cout << "Вызван обработчик int. исключение - " << i << endl;

return -1;

}

catch (const char * p){

cout << "Вызван обработчик const char*, исключение - "

<< p << endl;

return -1;

}

catch (...){

cout << "Вызван обработчик всех исключений" << endl;

return -1:

}

return 0; // Все обошлось благополучно

}

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

Входим в try-блок

Hello!

Генерируем исключение

Bye!

Вызван обработчик const char *. исключение - Ошибка при открытии файла

Обратите внимание, что после порождения исключения был вызван деструктор локального объекта, хотя управление из функции fl было передано обработчику, находящемуся в функции main. Сообщение «Выходим из try-блока» не было вы­ведено. Для работы с файлом в программе использовались потоки, о которых будет рассказано позднее.

Таким образом, механизм исключений позволяет корректно уничтожать объекты при возникновении ошибочных ситуаций. Поэтому выделение и освобождение ресурсов полезно оформлять в виде классов, конструктор которых выделяет ресурс, а деструктор освобождает. В качестве примера можно привести класс для работы с файлом. Конструктор класса открывает файл, а деструктор — закрывает. В этом случае есть гарантия, что при возникновении ошибки файл будет корректно за­крыт, и информация не будет утеряна.

Как уже упоминалось, исключение может быть как стандартного, так и опреде­ленного пользователем тина. При этом нет необходимости определять этот тип глобально — достаточно, чтобы он был известен в точке порождения исключения и в точке его обработки. Класс для представления исключения можно описать внутри класса, при работе с которым оно может возникать. Конструктор копиро­вания этого класса должен быть объявлен как public, поскольку иначе будет не­возможно создать копию объекта при генерации исключения (конструктор копи­рования, создаваемый по умолчанию, имеет спецификатор public).

 


Дата добавления: 2018-02-15; просмотров: 168; ЗАКАЗАТЬ РАБОТУ