Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Подбельский учебник с++ / Подбельский - главы 10-12

.pdf
Скачиваний:
29
Добавлен:
22.05.2015
Размер:
1.46 Mб
Скачать

436

Язык Си++

При открытии файлов с потоками класса ofstream второй параметр по умолчанию устанавливается равным i os: : out, т.е. файл открывается только для вывода. Таким образом, файл с: \USER\RESULT . DAT после удачного выполнения функции open О будет при необходимости (если он не существовал ранее) создан, а затем открыт для вывода (записи) данных в текстовом режиме обмена и присоединен к потоку outFile. Теперь к потоку outFile может применяться, например, операция включения «, как к стандартным выходным потокам cout, cerr.

Поток inFil e класса ifstream в нашем примере пр"соединяется функцией open () к файлу с именем DATA.TXT. ЭТОТ файл • .ткрывается для чтения из него данных в текстовом режиме. Если файла с иучем DATA.TXT не существует, то попытка вызвать функцию inFile.орап() приведет к ошибке.

Для проверки удачности завершения функции open О используется перегруженная операция \. Если унарная операция ! применяется к потоку, то результат ненулевой при наличии ошибок. Если ошибок не было, то выражение !имя_потока имеет нулевое значение. Таким образом, можно проверить результат выполнения функции open ():

if (•inFile)

{ cerr « "Ошибка при открытии файла!\п" exit(l);

Для потоков класса fstream второй аргумент функции ореп<) должен быть задан явно, так как по умолчанию неясно, в каком направлении предполагается выполнять обмен с потоком. В примере файл CHANGE.DAT открывается для записи и связывается с потоком ioFile, который будет выходным потоком до тех пор, пока с помощью повторного открытия файла явно не изменится направление обмена с файлом или потоком. (Чтобы изменить режимы доступа к файлу, его нужно предварительно закрыть с помощью функции close (), унаследованной всеми тремя файловыми классами из базового класса f streambase.)

В классе fstreambase, который служит основой для файловых классов, имеются и другие средства для открытия уже существующих файлов.

Глава 11. Ввод-вывод в языке Си++

437

Если файл явно создан с помощью библиотечной функции "нижнего уровня" creat (), то для него определен дескриптор файла. Этот дескриптор можно использовать в качестве фактического параметра функции f streambase: : attach (). При вызове этой функции используется уточненное имя, содержащее название того потока, который предполагается присоединить к уже созданному файлу с известным дескриптором:

•include <fstream.h> // Классы файловых потоков

#include <sys\stat.h> // Константы режимов доступа к файлам

char name[20];

// Вспомогательный массив

cin » name;

// Ввести имя создаваемого файла

int descrip = create(name,S_WRITE); // Создать файл if (descrip == -1)

{ cout « "\n Ошибка при создании файла"); exit () ;

}

//Определение выходного файлового потока: ofstream fileOut;

//Присоединение потока к файлу: fileOut.attach(descrip);

if (!fileOut)

{cerr « "\пОшибка присоединения файла!")

£exit(l);

Вклассах ifstream, ofstream, fstream опоеделены конструкторы, позволяющие по-иному выполнять создание и открытие файлов. Типы конструкторов для потоков разных классов очень похожи:

имя_класса();

создает поток, не присоединяя его ни к какому файлу;

HM*_iuiacca(int fd) ;

создает поток и присоединяет его к уже открытому файлу, дескриптор которого используется в качестве параметра fd;

имя_класса(int fd, ehar *buf, int);

создает поток, присоединяя его к уже открытому файлу с дескриптором fd, и использует явно заданный буфер (параметр buf);

имя_класса(char *FileName, int mode, int = ...) ;

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

438

Язык Си++

Детали и особенности перечисленных конструкторов лучше изучать по документации конкретной библиотеки ввода-вывода.

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

ifstream fi; // Создает входной файловый поток fi ostream fo; // Создает выходной файловый поток fo fstream ff; // Создает файловый поток ввода-вывода ff

После выполнения каждого из этих конструкторов файловый поток можно присоединить к конкретному файлу, используя уже упомянутую компонентную функцию open ():

void open(char *FileName, int режим, int защита);

Примеры:

fi.open("Filel.txt",ios::in); // Поток fi соединен

 

 

// с файлом Filel.txt

fi.close О;

//

Разорвана связь потока fi с файлом

 

//

Filel.txt

fi.openCFile2.txt"); // Поток fi присоединен к файлу

 

//

File2.txt

fo.open("NewFile");

//

Поток fo присоединяется к файлу

 

'//

NewFile; если такой файл

 

//

отсутствует - он будет создан

При обращении к конструктору с явным указанием в параметре имени файла остальные параметры можно не указывать, они выбираются по умолчанию.

П р и м е р ы :

ifstream flowl("File.1");

создает входной файловый поток с именем flowl для чтения данных. Разыскивается файл с названием File.l. Если такой файл не существует, то конструктор завершает работу аварийно. Проверка:

if (Iflowl) cerr « "Не открыт файл F i l e . l ! " ;

ofstream f l o w 2 ( " F i l e . 2 " ) ;

создается выходной файловый поток с именем fiow2 для записи информации. Если файл с названием File.2 не существует, он будет создан, открыт и соединен с потоком

Глава11.Ввод-выводвязыкеСи++

flow2. Еслифайлужесуществует,топредыдущийвариантбудетудаленипустойфайлсоздаетсязаново. Проверка:

if (!flow2) cerr « "Не открыт файл File.2'"; fstream flow3("File.3");

создается файловый поток flow3, открывается файл Fiie.3 и присоединяетсякпотокуfiow3.

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

В качестве иллюстрации основных особенностей работы с файлами рассмотрим несколько программ.

//Р11-17.СРР - чтение текстового файла с помощью

//

операции »

 

 

#include <stdlib.h>

//

Для функции exit()

#include <fstream.h>

//

Для

файловых потоков

const int lenName = 13;

//

max

длина имени файла

// Длина вспомогательного массива: const int lenString = 60;

void main()-

( char soufte[lenName]; // Массив для имени файла cout « "ХпВведите имя исходного файла: ";

cin » source;

ifstream inFile;

// Входной файловый поток

// Открыть файл source и связать его с потоком inFile:

inFile.open(source);

if (finFile)

// Проверить правильность открытия файла

( cerr « "ХпОшибка при открытии файла " « source;

exit(l);

// Завершение программы

)

 

// Вспомогательный массив для чтения: char string[lenString];

char next;

cout « "\n Текст файла:\n\n";

cin.get();

//

Убирает код из

потока cin

while (1)

//

Неограниченный

цикл

{// Ввод из файла одного слова до пробельного

//символа либо EOF:

inFile » string;

// Проверка следующего символа:

next = inFile.peek();

440

Язык Си++

//Выход при достижении конца файла: if (next = EOF) break;

//Печать с добавлением разделительного пробела:

cout

« string «

"

";

if (next =

'\n')

//

Обработка конца строки

(

cout

« '\п•;

 

//4 - смещение для первой страницы экрана: static int i = 4;

//Деление по страницам до 20 строк каждая:

if (!(++i %

20))

{ cout «

"\пДля продолжения вывода "

 

"нажмите ENTER.\n" « endl;

cin.get();

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

Программа демонстрирует неудобства чтения текста из файла с помощью операции извлечения », которая реагирует на каждый обобщенный пробельный символ. Между словами, прочитанными из файла, принудительно добавлено по одному пробелу. А сколько их (пробелов) было в исходном тексте, уже не известно. Тем самым искажается содержащийся в файле текст. Читать пробельные символы позволяет компонентная функция getlineO класса istream, наследуемая классом ifstream. Текст из файла будет читаться и выводиться на экран (в поток cout) без искажений (без пропусков пробелов), если в предыдущей программе чтение и вывод в поток cout организовать таким образом:

while(1) // Неограниченный цикл

{ inFile.getline(string,lenString); next = inFiie.peek();

if (next = EOF) break; cout « string;

Следующая программа читает текстовый файл и разделяет его на две части - строки, не содержащие последовательности из двух символов '//', и строки, начинающиеся такой парой символов. Иначе го-

Глава 11. Ввод-вывод в языке Си++

441

воря, эта программа позволяет удалить из исходного текста программы на языке Си++ комментарии, начинающиеся парой символов ' //' и заканчивающиеся признаком конца строки '\п'. В программе определены два выходных потока outtext и outcom, связанные соответственно с создаваемыми заново файлами text.cpp и comment. Имя входного файла с текстом анализируемой программы на языке Си++ определяет (вводит с клавиатуры) пользователь. С этим файлом "связывается" функцией ореп() входной поток inFiie. Для проверки безошибочного открытия файлов проверяются значения выражений (! имя_потока). При истинности результата вызывается вспомогательная функция errorFO. Вспомогательная переменная int len, позволяет проследить за необходимостью перехода к новой строке в потоке outtext, если во входном потоке inFiie обнаружена пара символов ' // •. Символы входного потока последовательно читаются в переменную simb и выводятся в нужный выходной поток. Если не встречен символ • / •, то все просто - вывод идет в поток outtext. Так как обнаружение во входном отдельного символа ' /' не есть признак начала комментария, то в этом случае анализируется следующий символ, читаемый из входного потока в переменную next. Если next имеет значение • / •, то это начало комментария, и последующий вывод нужно вести в поток outcom, предварительно "закрыв" строку в потоке outtext символом 'jflb •. Комментарии в тексте программы поясняют остальные детали алгоритма.

//Р11-18.СРР - выделение комментариев из текста на Си++;

//посимвольные чтение и запись из текстового

//файла

#include

<stdlib.h>

 

#include

<fstream.h>

 

void errorF(char *ss)

// Вспомогательная функция

{ cerr «

"ХпОшибка при открытии файла" « ' ' «

 

 

ss « '\n';

 

exit(l);

 

const int lenName = 23;

// Длина массива для имени файла

void main()

 

char progName[lenName];

// Массив для имени файла

cout « "ХпВведите полное имя анализируемой программы:

cin

»

progName;

 

ifstream inFiie;

// Входной поток

// Связываем входной поток с файлом программы:

inFiie.open(progName);

 

if

(!inFiie) errorF(progName);

Р11-18.СРР,

442

Язык Си++

char simb, last, next;

// Вспомогательные переменные

ofstream outtext, outcom; // Два выходных потока // Переменная для вычисления длин строк программы:

int

len

• 0;

 

 

 

 

 

 

outtext.open("text.cpp",ios::ate);

 

 

if

(!outtext)

errorF("text.cpp");

 

 

outcom.open("comment",ios::app);

 

 

if

(!outcom) errorF("comment");

 

 

while

(inFile.get(simb))

//

Читает

символы до EOF

 

{

len++;

 

//

Длина

очередной строки программы

 

 

if

(simb

=

'\n')

 

 

 

 

 

 

 

len = 0; // Начнется

новая

строка

программы

 

 

if

(simb

!='/')

//

Это не

начало

комментария

 

 

 

// Вывод символа строки программы:

 

 

 

 

outtext.put(simb);

 

 

 

 

 

else

 

 

 

 

 

 

 

 

 

 

//

Когда

simb =

'/'

- возможно начало

 

 

 

//

комментария

 

 

 

 

{ // Проверка на EOF:

if (!inFile.get(next)) break; if (next =='/')

{ // Теперь уже точно комментарий if (len •= 1)

// "Закрываем" строку программы: outtext.put('\n');

outcom.put(simb);

outcom.put(next);

//Цикл до конца комментария,

//т.е. до конца строки:

do

{ // Чтение символа из файла: inFile.get(simb);

// Запись символа в поток: outcom.put(simb);

} while (simb!='\n');

}

else

//Вывод символов, не входящих

//в комментарий:

( outtext.put(simb); outtext.put(next);

Глава 11. Ввод-вывод в языке Си++

443

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

Для разнообразия при открытии файлов text.cpp и comment в функциях open () использованы разные флаги, определяющие режим работы с соответствующим потоком. Результат одинаков - флаги ios: :ate и ios: :app в этом случае неразличимы. Запись в файлы идет с их дополнением. После каждого нового выполнения программы новая "порция" текстовой информации дописывается в каждый файл. Если необходимо, чтобы сохранялся в файлах только последний результат, второй параметр функции open о проще всего задавать по умолчанию.

Как и для других потоков, для потоков, связанных с файлами, допустима перегрузка операций обмена. Для иллюстрации приведем следующую программу Р11-19.СРР, в которой перегружена операция включения в поток «. Действие операции распространено на аргументы типа ofstreams и element, где element - пользовательский тип, а именно структура. В программе с помощью конструктора класса ofstream определяется поток fjAel и связывается с файлом две.

Текст программы:

//Р11-19.СРР - запись структур в файл перегруженной

//

 

 

операцией

«

 

 

finclude

<fstream.h>

 

 

 

 

 

 

struct element,!

 

//

Определение

некоторой структуры

 

 

 

 

int nJc,

nl ;

 

 

 

 

 

 

float zn;

 

 

 

 

 

 

);

 

 

 

 

 

 

// Операция-функция, расширяющая

действие операции «

ofstreams operator «

(ofstreami

out,

element el)

{

out «

'

« el.nk

«

 

• ' «

el.nl «

 

 

1

'

« el. zn «

' \n' ;

 

 

 

return

out;

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

int main()

 

 

 

 

 

 

 

 

(

const

int numbeEl

= 5 ;

 

// Количество структур в массиве

 

element arel[numbeEl]

«

( 1, 2, 3.45, 2, 3, 4.56,

 

 

 

22, 11, 45.6, 3, 24, 4.33, 3, 6, -5.3 );

 

// Определяем поток и связываем его с новым файлом ABC:

 

ofstream

filel("abc");

 

 

 

 

444

Язык Си++

if

(!filel)

 

{ cerr « "Неудача при открытии файла АВС.";

 

return 1;

)

// Запись в файл ABC массива структур: for (int i = 0; i < numbEl; i++)

filel « arel[i];

}

Результат выполнения программы - создание файла с именем АВС в текущем каталоге и запись в этот файл элементов массива из пяти структур element. Содержимое файла АВС:

12 3.45

23 4.56

22 11 45.599998

3 24 4.33

3 б - 5 . 3

Файл АВС создается заново при каждом выполнении программы. Чтобы файл создавался один раз и была возможность его дополнения, нужно добавить в конструктор второй параметр таким образом:

ofstream filel("abc",ios::арр);

В этом случае при двух последовательных выполнениях программы результат в файле АВС будет таким:

12 3.45

23 4.56

22 11 45.599998

3 24 4.33

3 б - 5 . 3

12 3.45

23 4.56

22 11 45.599998

3 24 4.33

3 6 -5.3

Глава12.ОБРАБОТКАОСОБЫХ (ИСКЛЮЧИТЕЛЬНЫХ) СИТУАЦИЙ

В последних версиях компиляторов языка Си+ + наконец появились рекомендованные стандартом ANSI средства для обработки особых ситуаций. Такие ситуации в Си++ называют исключительными ситуациями или исключениями (exceptions). Для компиляции приведенных ниже программ можно использовать, например, компиляторы Borland C++ версий 4.0 или 4.5, в которых реализованы все описанные средства.

12.1.Общиепринципы механизма обработки исключений ^

Механизм обработки особых ситуаций присутствовал в разных языках программирования до появления Си++. Один из примеров - язык ПЛ/1, в котором программисты могли работать как со встроенными (заранее запланированными) ситуациями, так и с ситуациями, создаваемымми (формируемыми) по указанию программиста при наступлении того или иного события. Типичные встроенные ситуации это "деление на нуль", "достижение конца файла", "переполнение в арифметических операциях" и т.п. В языке Си++ практически любое состояние, достигнутое в процессе выполнения программы, можно заранее определить как особую ситуацию (исключение) и предусмотреть действия, которые нужно выполнить при ее возникновении.

Для реализации механизма обработки исключений в язык Си++ введены следующие три ключевых (служебных) слова: try (контролировать), catch (ловить), throw (генерировать, порождать, бросать, посылать, формировать).

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

try { операторы )

446

Язык Си++

Среди операторов, заключенных в фигурные скобки могут быть: описания, определения, обычные операторы языка Си++ и специальные операторы генерации (порождения, формирования) исключений:

throw выражекие_генерации_исключения;

Когда выполняется такой оператор, то с помощью выражения, использованного после служебного слова throw, формируется специальный объект, называемый исключением. Исключение создается как статический объект, тип которого определяется типом значения выражекия_генерации_исключения. После формирования исключения исполняемый оператор throw автоматически передает управление (и само исключение как объект) непосредственно за пределы контролируемого блока. В этом месте (за закрывающейся фигурной скобкой) обязательно находятся один или несколько обработчиков исключений, каждый из которых идентифицируется служебным словом catch и имеет в общем случае следующий формат:

catch (тип_исключения имя) { операторы }

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

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

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

Глава 12. Обработка особых (исключительных) ситуаций в Си< *•

447

 

Классический алгоритм Евклида определения наибольшего общего делителя двух целых чисел (х, у) может применяться только при следующих условиях:

оба числа хну неотрицательные;

оба числа х и у отличны от нули.

На каждом шаге алгоритма выполняются срачнения:

если х == у, то ответ найден;

если х < у, то у заменяется значением у - х;

если х > у, то х заменяется значением х-у.

Следующая программа содержит функцию GCM() для определения наибольшего общего делителя, включающую контролируемый блок с проверкой исходных данных. В основной программе main() трижды вызывается функция GCM(). Два последних вызова выполнены с неверными значениями параметров.

//Р12-01.СРР - GCM - Greatest Common Measure #include <iostream.h>

//Определение функции с генерацией, контролем и

//обработкой исключений:

int GCM(int х, int у)

{ // Контролируемый блок:

try { if (х=0 || у = 0 ) throw "\uiERO!";

if (x < 0) throw "\nNegative parameter 1."; if (у < 0) throw "XnNegati^ parameter 2."; while (x != y)

{if (x > y) x = x - y: else

у = у - x;

}

return x;

}// Конец контролируемого блока

//Обработчик исключений стандартного типа "строка": catch (const char «report)

{ cerr « report « " x = " « x «

 

", у = » « у;

return

0;

)

 

}

// Конец определения функции

void main()

 

{ // Безошибочный вызов:

cout « "\nGCM(66,44) = » « GCM(66,44); // Нулевой параметр:

cout « 11\nGCM(0/7) = " « GCM(0,7);

448

ЯзыкСи++

// Отрицательный параметр:

cout « "\nGCM(-12,8) = " « GCM(-12,8);

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

GCM(66,44) = 22

ZERO! x = О, У = 7

GCM(0,7) = 0

Negative parameter 1. х = - 12, у = 8

GCM(-12,8) = 0

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

Служебное слово try определяет следующий за ним набор операторов в фигурных скобках как контролируемый блок. Среди операторов этого контролируемого блока три условных оператора анализируют значения параметров. При истинности проверяемого условия в каждом из них с помощью оператора генерации throw формируется исключение, т.е. создается объект - символьная строка, имеющая атрибуты const char *. При выполнении любого из операторов throw естественная последовательность исполнения операторов функции прерывается и управление автематически без каких-либо дополнительных указаний программиста передается обработчику исключений, помещенному непосредственно за контролируемым блоком (Это чуть-чуть похоже на оператор goto). Так как в данной программе обработчик исключений локализован в теле функции, то ему доступны значения ее параметров (х, у). Поэтому при возникновении каждого исключения в поток вывода сообщений об ошибках сегг выводится символьная строка с информацией о характере ошибки (нулевые параметры или отрицательные значения параметров) и значения параметров, приведшие к возникновению особой ситуации и к генерации исключения. Здесь же в составном операторе обработчика исключений выполняется оператор return О; Тем самым при ошибках возвращается необычное нулевое значение наибольшего общего делителя. При естественном окончании выполнения функции GCMO, когда становятся равными значения х и у, функция возвращает значение X.

Так как по умолчанию и выходной поток cout, и поток сегг связываются с экраном дисплея, то результаты как правильного, так и ошибочного выполнения функции GCMO ВЫВОДЯТСЯ на один экран. Обратите внимание, что исключения (const char *) одного типа посылаются в ответ на разные ситуации, возникающие в функции.

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

ввызывающей программе.

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

иобработчик исключений перенесены в функцию main (). Все вызовы функции (верный и с ошибками в параметрах) помещены в контролируемый блок.

//Р12-02.СРР - функция с генерацией, но без контроля

//

исключений

 

#include <iostream.h>

^

int GCM_NEW(int x, int у)

// Определение функции

{ if (х == 0 |I у = 0) throw "\nZERO!";

if (x < 0) throw "\nNegative parameter 1."; if (у < 0) throw "\nNegative parameter 2."; while (x •= y)

( if (x > y) x = x - y;

else у = у - x;

}

return x;

}

// Контроль и обработка исключений в вызывающей программе void main()

'try

// Контролируемый блок

( cout « M\nGCM_NEW(66,44) = " « GCM_NEW(66,44); cout « "\nGCM_NEW(0,7) = " « GCM_NEW(0,7); cout « "\nGCM_NEty(-12,8) = " « GCM_NEW(-12,8);

}

29-2432

450

Язык Си++

catch (const char 'report) // Обработчик исключений ( cerr « report; )

}

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

GCM_NEW(66,44) = 22

ZERO!

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

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

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

Вследующей программе определен класс DATA С компонентами, позволяющими отображать в объекте-исключении как целочисленные параметры функции, так и указатель на строку с сообщением (о смысле события, при наступлении которого сформировано исключение).

//Р12-03.ОРР - исключения глобального пользовательского

//

типа

#include <iostream.h>

struct DATA

// Глобальный класс объектов-исключений

{ int n, m; char *s;

DATA(int x, int у, char *c) // Конструктор класса DATA

 

{ n = x; m =» у; s

= с; )

 

int GCM_ONE(int x, int

у)

// Определение функции

{ if

(x = 0 I| у = 0)

throw DATA(x,y,"\nZERO!")

if

(x <

0)

throw DATA(x,y, "^nNegative parameter 1.")

if

(у <

0)

throw DATA(x,y, "\nNegative parameter 2.')

while (x !<• У)

{if (x > y> x = x - y, else у = у - x;

Глава 12. Обработка особых (исключительных) ситуаций в Си++

4 5 1

return x;

)

void main() ( try

cout « M\nGCM_ONE(66,44) = cout « "\nGCM_ONE(0,7) = " cout « "\nGCM_ONE(-12,8) =

)

eaten

(DATA d)

 

{

cerr « d.s «

« d.n

« GCM_ONE(66,44) GCM_ONE(0,7);

«GCM_ONE(-12,8)

«У=" « d.m; )

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

GCM_ONE(66,44) = 22 ZERO! x = 0, у = 7

Отметим, что объект класса DATA формируется в теле функци.1 при выполнении конструктора класса. Если бы этот объект не был исключением, он был бы локализован в теле функции и недоступен в точке ее вызова. Но по определению исключений они создаются как временные статические объекты. В данном примере исключения как безымянные объекты класса DATA формируются в теле функции, вызываемой из контролируемого блока. В блоке обработчика исключений безымянный объект типа DATA инициализирует переменную (параметр) DATA d и тем самым информация из исключения становится доступной в теле обработчика исключений, что демонстрирует результат.

Итак, чтобы исключение было достаточно информативным, оно должно быть объектом класса, причем класс обычно определяется специально. В приведенной программе класс для ц^слючений определен как глобальный, т.е. он доступен как в функции GCM_ONE(), где формируются исключения, так и в основной программе, где выполняется контроль за ними и, при необходимости, их обработка. Внешне исключение выглядит как локальный объект той функции, где око формируется. Однако исключение не локализуется в блоке, где использован оператор его генерации. Исключение как объект возникает

вточке генерации, распознается в контоолируемом блоке и передается

вобработчик исключений. Только после обработки оно может исчезнуть. Нет необходимости в глобальном определении класса объектовисключений. Основное требование к нему - известность в точке формирования (throw) и в точке обработки (catch). Следующий пример и-тпюстрирует скачанное. Класс (структура) DATA определен отдельно

как внутри функции GCM_TWO(), так и в основной программе. Ника-

29-

452

Язык Си++

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

//Р12-04.СРР - локализация определений типов (классов) // исключений

#include <iostream.h>

int GCM ONE(int x, int y)

( struct DATA

// Определение типа локализовано в функции

{ int n, m;

 

char *s;

 

_,m«

DATA(int x, int y, char *c> // Конструктор класса DATA

{ n = x; m = у; s = с;)

if (x = 0

I I

у = 0) throw DATA(x,y,"\nZERO!");

if (x < 0)

throw DATA(x,y,"\nNegative parameter 1.");

if (y < 0)

throw DATA(x,y,"\nNegative parameter 2.");

while (x != y)

{if (x > y) x = x - y; else у = у - x;

>

return x;

}

 

void main()

. ..

{ struct DATA

// Определение типа локализовано в mam()

{ int n, m;

 

DATA(int x, int y, char *c) II Конструктор класса DATA { n = x; m = у; s = с;)

{ cout « "\nGCM ONE(66,44) = " « GCM_ONE(66,44); cout « "\nGCM~ONE(-12,8) = « « GCM_ONE(-12,8); cout « "\nGCM_ONE(0,7) = " « GCM_ONE<0,7);

}

catch (DATA d)

( cerr « d.s « " x = " « d.n « ", у = " « d.m

}

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

GCM_TWO<66,44) = 22 Negative parameter 1. x = -12, у = 8

Глава 12. Обработка особых (исключительных) ситуаций в Си++

453

12.2.Синтаксис и семантика генерации

иобработки исключений

Если проанализировать приведенные выше npi граммы, то окажется, что в большинстве из них механизм генерации и обработки исключений можно имитировать "старыми" средствами. В этом случае, определив некоторое состояние программы как особое, ее автор предусматривает анализ результатов выполнения оператора, в котором это состояние может быть достигнуто, либо проверяет исходные данные, использование которых в операторе может привести к возникновению указанного состояния. Далее выявленное состояние обрабатывается. Чаще всего при обработке выводится сообщение о достигнутом состоянии и либо завершается выполнение программы, либо выполняются заранее предусмотренные коррекции. Описанная схема имитации механизма обработки особых ситуаций неудобна в тех случая,<, когда существует "временной разрыв" между написанием частей программы, где возникает (выявляется) ситуация и где она обрабатывается. Например, это типично при разработке библиотечных функций, когда реакции на необычные состояния в функциях должен определять не автор функций, а программист, применяющий их в своих программах. При возникновении аварийной (особой) ситуации в библиогечной (или просто заранее написанной ) функции желательно передать управление и информацию о характере ситуации вызывающей программе, где программист может по своему предусмотреть обработку возникшего состояния. Именно такую возможность в языке Си++ обеспечивает механизм обработки исключений.

Итак, исключения введены в язык в основном для того, чтэ^ы дать возможность программисту динамически (run-time) проводить обработку возникающих ситуаций, с которыми не может справиться исполняемая функция. Основная идея состоит в том, что "функция, сталкивающаяся с неразрешимой проблемой, формирует исключение в надежде на то, что вызывающая ее (прямо ил^косвенно) функция сможет обработать проблему" [26].

Механизм исключений позволяет переносить анализ и обработку ситуации из точки ее возникновения (throw point), в другое место программы, специально предназначенное для ее обработки. Кроме того, из точки возникновения ситуации в место ее обработки (в список обработчиков исключений) может быть передано любое количество необходимой информации, например, сведения о том, какие данные и Действия привели к возникновению такой ситуации.

454

Язык Си++

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

Необходимо лишь помнить, что механизм исключений предназначен только для синхронных событий, то-есть таких, которые порождаются в результате работы самой программы (к примеру, попытка прерывания программы нажатием Ctrl+C во время ее выполнения не является синхронным событием).

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

try

{операторы контролирувмого_блока } catch (спецификация исключения)

{операторы_обрабО'-'чика_исключений }

catch (спецификация_исключекия)

{операторы_обработчика_исключекий )

Вприведенных выше программах использовалось по одному обработчику исключений. Это объясняется "однотипностью" формируемых исключений (только типа const char * или только типа DATA). В общем случае в контролируемом блоке могут формироваться исключения разных типов и обработчиков может быть несколько. Размещаются они подряд, последовательно друг за другом и каждый обработчик "настроен" на исключение конкретного типа. Спецификация исключения, размещенная в скобках после служебного слова catch, имеет три формы:

c a t c h

( т и п и м я ;

<

. . .

}

c a t c h

( т и п )

{

. . .

}

 

c a t c h

( . . . )

{

. . .

)

 

Первый вариант подобен спецификации формального параметра* определении функции. Имя этого параметра используется в оператО"

pax обработки исключения. С его помощью к ним передается информация из обрабатываемого исключения.

Второй вариант не предполагает использования значения исключения. Для обработчика важен только его тип и факт его получения.

В третьем случае (многоточие) обработчик реагирует на любое исключение независимо от его типа. Так как сравнение "посланного" исключения со спецификациями обработчиков выполняется последовательно, то обработчик с многоточием в качестве спецификации следует помещать только в конце списка обработчиков. В противном случае все возникающие исключения "перехватит" обработчик с многоточием в качестве спецификации. В случае, если описать его не последним обработчиком, компилятор Borland C++, к примеру, выдаст сообщение об ошибке:

The ' . . . ' handler must be last in function .. . ()

(Обработчик '...' должен идти последним в функции ...0).

Продемонстрируем некоторые из перечисленных особенностей обработки исключений еще одной программой:

//Р12-05.СРР - исключения без передачи информации

Imclude <iostream.h>

 

class ZeroDivide ( } ;

// Класс без компонентов

class Overflow () ;

// Класс без компонентов

// Определение функции с генерацией исключений:

float div(float n, float d)

 

{if (d >» 0.0)

 

throw ZeroDivide();

// Вызов конструктора

double b = n/d;

 

if (b > le+30) throw Overflow(); // Вызов конструктора ) return b;

float ж = le-20, у = 5.5, z =• le+20, w = 0.0;

// Вызывающая функция с выявлением и обработкой исключений: void RR(void)

I I! Контролируемый блок: try { у = div(4.4,w);

г = div(z,x) ;

)

// Последовательность обработчиков исключений: catch (overflow)

{ cerr « "\nOverflow"; z = 1еЗО; } catch (zeroDivide)

{ cerr « "\nZeroDivide"; w = 1.0; }