Бьерн Страуструп. Язык программирования С++


Обработка особых ситуаций



бет9/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   5   6   7   8   9   10   11   12   ...   124

1.4.4 Обработка особых ситуаций


По мере роста программ, а особенно при активном использовании

библиотек появляется необходимость стандартной обработки ошибок (или, в

более широком смысле, "особых ситуаций"). Языки Ада, Алгол-68 и Clu

поддерживают стандартный способ обработки особых ситуаций.
Снова вернемся к классу vector. Что нужно делать, когда операции

индексации передано значение индекса, выходящее за границы массива?

Создатель класса vector не знает, на что рассчитывает пользователь в таком

случае, а пользователь не может обнаружить подобную ошибку (если бы мог,

то эта ошибка вообще не возникла бы). Выход такой: создатель класса

обнаруживает ошибку выхода за границу массива, но только сообщает о ней

неизвестному пользователю. Пользователь сам принимает необходимые меры.

Например:


class vector {

// определение типа возможных особых ситуаций

class range { };

// ...


};
Вместо вызова функции ошибки в функции vector::operator[]() можно

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

Это называется "запустить особую ситуацию" ("throw the exception"):
int & vector::operator [] ( int i )

{

if ( i < 0 || sz <= i ) throw range ();



return v [ i ];

}
В результате из стека будет выбираться информация, помещаемая туда при

вызовах функций, до тех пор, пока не будет обнаружен обработчик особой

ситуации с типом range для класса вектор (vector::range); он и будет

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

блока:
void f ( int i )

{

try


{

// в этом блоке обрабатываются особые ситуации

// с помощью определенного ниже обработчика

vector v ( i );

// ...

v [ i + 1 ] = 7; // приводит к особой ситуации range



// ...

g (); // может привести к особой ситуации range

// на некоторых векторах

}

catch ( vector::range )



{

error ( "f (): vector range error" );

return;

}

}


Использование особых ситуаций делает обработку ошибок более

упорядоченной и понятной. Обсуждение и подробности отложим до главы 9.




1.4.5 Преобразования типов


Определяемые пользователем преобразования типа, например, такие, как

преобразование числа с плавающей точкой в комплексное, которое необходимо

для конструктора complex(double), оказались очень полезными в С++.

Программист может задавать эти преобразования явно, а может полагаться на

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

и однозначны:
complex a = complex ( 1 );

complex b = 1; // неявно: 1 -> complex ( 1 )

a = b + complex ( 2 );

a = b + 2; // неявно: 2 -> complex ( 2)


Преобразования типов нужны в С++ потому, что арифметические операции

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

задачах. Кроме того, большая часть пользовательских типов, используемых

для "вычислений" (например, матрицы, строки, машинные адреса) допускает

естественное преобразование в другие типы (или из других типов).
Преобразования типов способствуют более естественной записи программы:
complex a = 2;

complex b = a + 2; // это означает: operator + ( a, complex ( 2 ))

b = 2 + a; // это означает: operator + ( complex ( 2 ), a )
В обоих случаях для выполнения операции "+" нужна только одна функция,

а ее параметры единообразно трактуются системой типов языка. Более того,

класс complex описывается так, что для естественного и беспрепятственного

обобщения понятия числа нет необходимости что-то изменять для целых чисел.




1.4.6 Множественные реализации


Основные средства, поддерживающие объектно-ориентированное

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

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

несколько реализаций одного типа. Вернемся к примеру со стеком:
template < class T >

class stack

{

public:


virtual void push ( T ) = 0; // чистая виртуальная функция

virtual T pop () = 0; // чистая виртуальная функция

};
Обозначение =0 показывает, что для виртуальной функции не требуется

никакого определения, а класс stack является абстрактным, т.е. он может

использоваться только как базовый класс. Поэтому стеки можно использовать,

но не создавать:


class cat { /* ... */ };

stack < cat > s; // ошибка: стек - абстрактный класс


void some_function ( stack & s, cat kitty ) // нормально

{

s.push ( kitty );



cat c2 = s.pop ();

// ...


}
Поскольку интерфейс стека ничего не сообщает о его представлении, от

пользователей стека полностью скрыты детали его реализации.


Можно предложить несколько различных реализаций стека. Например, стек

может быть массивом:


template < class T >

class astack : public stack < T >

{

// истинное представление объекта типа стек



// в данном случае - это массив

// ...


public:

astack ( int size );

~astack ();
void push ( T );

T pop ();

};
Можно реализовать стек как связанный список:
template < class T >

class lstack : public stack < T >

{

// ...


};
Теперь можно создавать и использовать стеки:
void g ()

{

lstack < cat > s1 ( 100 );



astack < cat > s2 ( 100 );
cat Ginger;

cat Snowball;


some_function ( s1, Ginger );

some_function ( s2, Snowball );

}
О том, как представлять стеки разных видов, должен беспокоиться только

тот, кто их создает (т.е. функция g()), а пользователь стека (т.е. автор

функции some_function()) полностью огражден от деталей их реализации.

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

должны быть виртуальными функциями.




Достарыңызбен бөлісу:
1   ...   5   6   7   8   9   10   11   12   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет