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

Равиль Ярупов
Равиль Ярупов
Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?