ВЫПОЛНЕНИЕ ОПЕРАЦИЙ ЧТЕНИЯ И ЗАПИСИ
Все программы выполняли файловые операции над символьными строками. По мере усложнения программ понадобится читать и писать массивы и структуры. Для этого программы могут использовать функции 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; просмотров: 914; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!