Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Козак Н.В. Лекции Основы создания программ в Си...doc
Скачиваний:
24
Добавлен:
23.09.2019
Размер:
2.24 Mб
Скачать

Реализация виртуального механизма

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

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

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

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

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

Как видите, виртуальный механизм, Kaк и все хорошее в этом мире, связан с некоторыми издержками, как в плане занимаемой памяти (виртуальная таблица), так и в плане времени выполнения (дополнительные косвенные ссылки при вызове) Однако эти издержки, как это hе удивительно очень малы.

Лекция.5.Семинар 1-2: Обработка исключений

Обработка исключений – это механизм, позволяющий двум независимо разработанным

программным компонентам взаимодействовать в аномальной ситуации, называемой

исключением.

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

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

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

#include <vector>

class iStack

{

public:

// конструктор со списком инициализации переменных _stack и _top

iStack( int capacity ) : _stack( capacity ), _top( 0 ) { }

bool pop( int &top_value );

bool push( int value );

bool full();

bool empty();

void display();

int size();

private:

int _top;

vector< int > _stack;

};

Элементы стека хранятся в векторе _stack. Переменная _top содержит индекс первой свободной ячейки стека.

// *.cpp файл

inline int iStack::size() { return _top; };

inline bool iStack::empty()

{

return _top ? false : true;

}

inline bool iStack::full()

{

return _top < _stack.size()-l ? false : true;

}

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

bool iStack::pop( int &top_va1ue )

{

if ( empty() )

return false;

top_value = _stack[ --_top ];

cout << "iStack::pop(): " << top_value << endl;

return true;

}

bool iStack::push( int value )

{

cout << "iStack::push( " << value << " )\n";

if ( full() )

return false;

_stack[ _top++ ] = value;

return true;

}

void iStack::display()

{

cout << "( " << size() << " )( bot: ";

for ( int ix = 0; ix < _top; ++ix )

cout << _stack[ ix ] <<" ";

cout << " :top )\n";

}

Стек реализован на основе вектора из элементов типа int. При создании объекта класса

iStack его конструктор создает вектор из int, размер которого (максимальное число

элементов, хранящихся в стеке) задается с помощью начального значения. Например, следующая инструкция создает объект myStack, который способен содержать не более 20 элементов типа int:

#include <iostream>

#include "iStack.h"

int main()

{

iStack stack( 32 ) ;

stack.display();

for ( int ix = 1; ix < 51; ++ix )

{

if ( ix%2 == 0 )

stack.push( ix );

if ( ix%5 == 0 )

stack.display();

if ( ix%10 == 0 )

{

int dummy;

stack.pop( dummy );

stack.pop( dummy );

stack.display();

}

}

}

Результат выполнения

( 0 )( bot: :top )

iStack push( 2 )

iStack push( 4 )

( 2 )( bot: 2 4 :top )

iStack push( 6 )

iStack push( 8 )

iStack push ( 10 )

( 5 )( bot: 2 4 6 8 10 :top )

iStack pop(): 10

iStack pop(): 8

( 3 )( bot: 2 4 6 :top )

iStack push( 12 )

iStack push( 14 )

( 5 )( bot: 2 4 6 12 14 :top )

iStack::push( 16 )

iStack::push( 18 )

iStack::push( 20 )

( 8 )( bot: 2 4 6 12 14 16 18 20 :top )

iStack::pop(): 20

iStack::pop(): 18

( 6 )( bot: 2 4 6 12 14 16 :top )

iStack::push( 22 )

iStack::push( 24 )

( 8 )( bot: 2 4 6 12 14 16 22 24 :top )

iStack::push( 26 )

iStack::push( 28 )

iStack::push( 30 )

( 11 )( bot: 2 4 6 12 14 16 22 24 26 28 30 :top )

iStack::pop(): 30

iStack::pop(): 28

( 9 )( bot: 2 4 6 12 14 16 22 24 26 :top )

iStack::push( 32 )

iStack::push( 34 )

( 11 )( bot: 2 4 6 12 14 16 22 24 26 32 34 :top )

iStack::push( 36 )

iStack::push( 38 )

iStack::push( 40 )

( 14 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 38 40 :top )

iStack::рор(): 40

iStack::popQ: 38

( 12 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 :top )

iStack::push( 42 )

iStack::push( 44 )

( 14 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 42 44 :top )

iStack::push( 46 )

iStack::push( 48 )

iStack::push( 50 )

( 17 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 42 44 46 48 50 :top )

iStack::pop(): 50

iStack::pop(): 48

( 15 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 42 44 46 :top )

При манипуляциях с объектом myStack могут возникнуть две ошибки:

• запрашивается операция pop(), но стек пуст;

• запрашивается операция push(), но стек полон.

Вызвавшую функцию нужно уведомить об этих ошибках посредством исключений. Во-первых, мы должны определить, какие именно исключения могут быть возбуждены. В C++ они чаще всего реализуются с помощью классов. определим здесь два из них, чтобы использовать их как исключения для класса iStack. Эти определения мы поместим в заголовочный файл stackExcp.h:

// stackExcp.h

class popOnEmpty { /* ... */ };

class pushOnFull { /* ... */ };

Затем надо изменить определения функций-членов pop() и push() так, чтобы они возбуждали эти исключения. Для этого предназначена инструкция throw, которая во многих отношениях напоминает return. Она состоит из ключевого слова throw, за которым следует выражение того же типа, что и тип возбуждаемого исключения. Инструкция throw для функции pop() будет выглядеть так: Эта инструкция создает объект исключения типа popOnEmpty.

throw popOnEmpty();

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

#include "stackExcp.h"

void iStack::pop( int &top_value )

{

if ( empty() )

throw popOnEmpty();

top_value = _stack[ --_top ];

cout << "iStack::pop(): " << top_value << endl;

}

void iStack::push( int value )

{

cout << "iStack::push( " << value << " )\n";

if ( full() )

throw pushOnFull( value );

_stack[ _top++ ] = value;

}

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

enum EHstate { noErr, zeroOp, negativeOp, severeError };

int mathFunc( int i )

{

if ( i == 0 )

throw zeroOp; // исключение в виде объекта-перечисления

// ... продолжение корректной работы

}

try-блок

В нашей программе тестируется определенный в предыдущем разделе класс iStack и его функции-члены pop() и push(). Выполняется 50 итераций цикла for. Изменим функцию main(), чтобы она обрабатывала исключения, возбуждаемые функциями-членами класса iStack. Инструкции, которые могут возбуждать исключения, должны быть заключены в try-блок. Такой блок начинается с ключевого слова try, за которым идет последовательность инструкций, заключенная в фигурные скобки, а после этого – список обработчиков, называемых catch-предложениями.

int main()

{

iStack stack( 32 );

stack.display();

for ( int ix = 1; ix < 51; ++ix )

{

try

{ // try-блок для исключений pushOnFull

if ( ix % 3 == 0 )

stack.push( ix );

}

catch ( pushOnFull )

{ //...

}

if ( ix % 4 == 0 )

stack.display();

try

{ // try-блок для исключений popOnEmpty

if ( ix % 10 == 0 )

{

int dummy;

stack.pop( dummy );

stack.display();

}

}

catch ( popOnEmpty )

{ //...

}

}

}

return 0;

}

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

try

{

for ( int ix = 1; ix < 51; ++ix )

{

}

}

catch ( pushOnFull )

{ //...

}

catch ( popOnEmpty )

{ //...

}

С try-блоком ассоциированы два catch-предложения, которые могут обработать исключения pushOnFull и popOnEmpty, возбуждаемые функциями-членами push() и pop() внутри этого блока. Каждый catch-обработчик определяет тип “своего” исключения. Код для обработки исключения помещается внутрь составной инструкции (между фигурными скобками), которая является частью catch-обработчика.

Try-блок может содержать любую инструкцию языка C++: как выражения, так и объявления. Он вводит локальную область видимости, так что объявленные внутри него переменные недоступны вне этого блока, в том числе и в catch-обработчиках.

После завершения обработчика выполнение возобновляется с инструкции, идущей за последним catch-обработчиком в списке. Механизм обработки исключений в C++ невозвратный: после того как

исключение обработано, управление не возобновляется с того места, где оно было возбуждено. Например, управление не возвращается в функцию-член pop(), возбудившую исключение.

<M-2-8> отсюда