Опубликован: 10.10.2006 | Уровень: специалист | Доступ: свободно
Лекция 1:

Краткий обзор С++

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;
  }
}

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

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 <cat> & 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() ) полностью огражден от деталей их реализации. Платой за подобную гибкость является то, что все операции над стеками должны быть виртуальными функциями.

Равиль Ярупов
Равиль Ярупов
Привет !
Федор Антонов
Федор Антонов
Оплата и обучение
Роман Островский
Роман Островский
Украина
Оксана Пагина
Оксана Пагина
Россия, Москва