Главная страница

Язык программирования C++. Вводный курс. С для начинающих


Скачать 5.41 Mb.
НазваниеС для начинающих
Дата24.08.2022
Размер5.41 Mb.
Формат файлаpdf
Имя файлаЯзык программирования C++. Вводный курс.pdf
ТипДокументы
#652350
страница49 из 93
1   ...   45   46   47   48   49   50   51   52   ...   93
513
}
Что произойдет, если наша программа использует шаблон, определенный в пространстве имен, и мы хотим предоставить для него специализацию? (Явные специализации шаблонов рассматривались в разделе 10.6.) Допустим, мы хотим использовать шаблон min()
, определенный в cplusplus_primer, для нахождения минимального значения в массиве объектов типа SmallInt. Однако мы осознаем, что имеющееся определение шаблона не вполне подходит, поскольку сравнение в нем выглядит так: if ( array[i] < min_val )
В этой инструкции два объекта класса SmallInt сравниваются с помощью оператора <.
Но этот оператор неприменим к объектам, если только не перегружен в классе SmallInt
(мы покажем, как определять перегруженные операторы в главе 15). Предположим, что мы хотели бы определить специализацию шаблона min(), чтобы она пользовалась функцией compareLess() для сравнения двух подобных объектов. Вот ее объявление: bool compareLess( const SmallInt &parm1, const SmallInt &parm2 );
Как должно выглядеть определение этой функции? Чтобы ответить на этот вопрос, необходимо познакомиться с определением класса SmallInt более подробно. Данный класс позволяет определять объекты, которые хранят тот же диапазон значений, что и 8- разрядный тип unsigned char, т.е. от 0 до 255. Дополнительная функциональность состоит в том, что класс перехватывает ошибки переполнения и потери значимости. Во всем остальном он должен вести себя точно так же, как unsigned char. Определение
SmallInt выглядит следующим образом:
// ---- primer.h ---- namespace cplusplus_primer {
// определение шаблона скрыто в пространстве имен template
Type min( Type* array, int size ) { /* ... */ }
}
// ---- user.C ----
#include int ai[4] = { 12, 8, 73, 45 }; int main() { int size = sizeof(ai) / sizeof(ai[0]);
// ошибка: функция min() не найдена min( &ai[0], size ); using cplusplus_primer::min; // using- объявление
// правильно: относится к min() в пространстве имен cplusplus_primer min( &ai[0], size );
// функция сравнения объектов SmallInt
// возвращает true, если parm1 меньше parm2

С++ для начинающих
514
};
В этом классе есть один закрытый член value, в котором хранится значение объекта типа
SmallInt
. Класс также содержит конструктор с параметром ival:
SmallInt( int ival ) : value( ival ) {}
Его единственное назначение – инициализировать член класса value значением ival.
Вот теперь можно ответить на ранее поставленный вопрос: как должна быть определена функция compareLess()? Она будет сравнивать члены value переданных ей аргументов типа SmallInt:
}
Заметим, однако, что член value является закрытым. Как может глобальная функция обратиться к закрытому члену, не нарушив инкапсуляции класса SmallInt и не вызвав тем самым ошибку компиляции? Если вы посмотрите на определение класса SmallInt, то заметите, что глобальная функция compareLess() объявлена как дружественная
(friend). Если функция объявлена таким образом, то ей доступны закрытые члены класса. (Друзья классов рассматриваются в разделе 15.2.)
Теперь мы готовы определить специализацию шаблона min(). Она следующим образом использует функцию compareLess().
} class SmallInt { public:
SmallInt( int ival ) : value( ival ) {} friend bool compareLess( const SmallInt &, const SmallInt & ); private: int value; // член
// конструктор класса SmallInt
// возвращает true, если parm1 меньше parm2 bool compareLess( const SmallInt &parm1, const SmallInt &parm2 ) { return parm1.value < parm2.value;
// специализация min() для массива объектов SmallInt template<> SmallInt min( SmallInt* array, int size )
{
SmallInt min_val = array[0]; for (int i = 1; i < size; ++i)
// при сравнении используется функция compareLess() if ( compareLess( array[i], min_val ) ) min_val = array[i]; print( "Minimum value found: " ); print( min_val ); return min_val;

С++ для начинающих
515
Где мы должны объявить эту специализацию? Предположим, что здесь:
// ...
К сожалению, этот код не работает. Явная специализация шаблона функции должна быть объявлена в том пространстве имен, где определен порождающий шаблон. Поэтому мы обязаны определить специализацию min() в пространстве cplusplus_primer. В нашей программе это можно сделать двумя способами.
Напомним, что определения пространства имен не обязательно непрерывны. Мы можем повторно открыть пространство имен cplusplus_primer для добавления специализации:
}
Можно определить специализацию так, как мы определяем любой другой член пространства имен вне определения самого пространства: квалифицировав имя члена именем объемлющего пространства.
// ---- primer.h ---- namespace cplusplus_primer {
// определение шаблона скрыто в пространстве имен template
Type min( Type* array, int size ) { /* ... */ }
}
// ---- user.h ---- class SmallInt { /* ... */ }; void print( const SmallInt & ); bool compareLess( const SmallInt &, const SmallInt & );
// ---- user.C ----
#include
#include "user.h"
// ошибка: это не специализация для cplusplus_primer::min() template<> SmallInt min( SmallInt* array, int size )
{ /* ... */ }
// ---- user.C ----
#include
#include "user.h" namespace cplusplus_primer {
// специализация для cplusplus_primer::min() template<> SmallInt min( SmallInt* array, int size )
{ /* ... */ }
}
SmallInt asi[4]; int main() {
// задать значения элементов массива asi с помощью функции-члена set() using cplusplus_primer::min; // using- объявление int size = sizeof(asi) / sizeof(SmallInt);
// конкретизируется min(SmallInt*,int) min( &asi[0], size );

С++ для начинающих
516
// ...
Если вы, пользуясь библиотекой, содержащей определения шаблонов, захотите написать их специализации, то должны будете удостовериться, что их определения помещены в то же пространство имен, что и определения исходных шаблонов.
Упражнение 10.15
Поместим содержимое заголовочного файла из упражнения 10.14 в пространство имен cplusplus_primer. Как надо изменить функцию main(), чтобы она могла конкретизировать шаблон max(), находящийся в cplusplus_primer?
Упражнение 10.16
Снова обращаясь к упражнению 10.14, предположим, что содержимое заголовочного файла помещено в пространство имен cplusplus_primer. Допустим, мы хотим специализировать шаблон функции max() для массивов объектов класса
LongDouble
Нужно, чтобы специализация шаблона использовала функцию compareGreater()
для сравнения двух объектов класса LongDouble, объявленную как: const LongDouble &parm2 );
Определение класса LongDouble выглядит следующим образом:
};
Напишите определение функции compareGreater() и специализацию max(), в которой эта функция используется. Напишите также функцию main(), которая задает элементы массива ad, а затем вызывает специализацию max(), доставляющую его максимальный элемент. Значения, которыми инициализируется массив ad, должны быть получены чтением из стандартного ввода cin.
// ---- user.C ----
#include
#include "user.h"
// специализация для cplusplus_primer::min()
// имя специализации квалифицируется namespace { template<> SmallInt cplusplus_primer:: min( SmallInt* array, int size )
{ /* ... */ }
// функция сравнения объектов класса LongDouble
// возвращает true, если parm1 больше parm2 bool compareGreater( const LongDouble &parm1, class LongDouble { public:
LongDouble(double dval) : value(ival) {} friend bool compareGreater( const LongDouble &, const LongDouble & ); private: double value;

С++ для начинающих
517
10.11.
Пример шаблона функции
В этом разделе приводится пример, показывающий, как можно определять и использовать шаблоны функций. Здесь определяется шаблон sort(), который затем применяется для сортировки элементов массива. Сам массив представлен шаблоном класса Array (см. раздел 2.5). Таким образом, шаблоном sort() можно пользоваться для сортировки массивов элементов любого типа.
В главе 6 мы видели, что в стандартной библиотеке C++ определен контейнерный тип vector
, который ведет себя во многом аналогично типу Array. В главе 12 рассматриваются обобщенные алгоритмы, способные манипулировать контейнерами, описанными в главе 6. Один из таких алгоритмов, sort(), служит для сортировки содержимого вектора. В этом разделе мы определим собственный “обобщенный алгоритм sort()
” для манипулирования классом Array, упрощенной версии алгоритма из стандартной библиотеки C++.
Шаблон функции sort() для шаблона класса Array определен следующим образом:
}
В sort() используются две вспомогательные функции: min() и swap(). Обе они должны определяться как шаблоны, чтобы иметь возможность обрабатывать любые типы фактических аргументов, с которыми может быть конкретизирован шаблон sort(). min()
определена как шаблон функции для поиска минимального из двух значений любого типа:
} swap()
– шаблон функции для перестановки двух элементов массива любого типа: template void sort( Array &array, int low, int high ) { if ( low < high ) { int lo = low; int hi = high + 1; elemType elem = array[lo]; for (;;) { while ( min( array[++lo], elem ) != elem && lo < high ) ; while ( min( array[--hi], elem ) == elem && hi > low ) ; if (lo < hi) swap( array, lo, hi ); else break;
} swap( array, low, hi ); sort( array, low, hi-1 ); sort( array, hi+1, high );
} template
Type min( Type a, Type b ) { return a < b ? a : b;

С++ для начинающих
518
}
Убедиться в том, что функция sort() действительно работает, можно с помощью отображения содержимого массива после сортировки. Поскольку функция display() должна обрабатывать любой массив, конкретизированный из шаблона класса Array, ее тоже следует определить как шаблон:
}
В этом примере мы пользуемся моделью компиляции с включением и помещаем шаблоны всех функций в заголовочный файл Array.h вслед за объявлением шаблона класса Array.
Следующий шаг – написание функции для тестирования этих шаблонов. В sort() поочередно передаются массивы элементов типа double, типа int и массив строк. Вот текст программы: template void swap( Array &array, int i, int j )
{ elemType tmp = array[ i ]; array[ i ] = array[ j ]; array[ j ] = tmp;
#include template void display( Array &array )
{ //
формат отображения: < 0 1 2 3 4 5 > cout << "< "; for ( int ix = 0; ix < array.size(); ++ix ) cout << array[ix] << " "; cout << ">\n";

С++ для начинающих
519
}
Если скомпилировать и запустить программу, то она напечатает следующее (эти строки искусственно разбиты на небольшие части): sort array of doubles (size == 10)
< 1.7 5.7 11.7 14.9 15.7 19.7 26.7 37.7 48.7 59.7 61.7 > sort array of ints (size == 16)
< 61 87 154 170 275 426 503 509 512 612 653 677 703 765 897 908 > sort array of strings (size == 11)
< "a" "falling" "heavy" "left" "police" "snow"
"station" "the" "they" "was" "when" >
#include
#include
#include "Array.h" double da[10] = {
26.7, 5.7, 37.7, 1.7, 61.7, 11.7, 59.7,
15.7, 48.7, 19.7 }; int ia[16] = {
503, 87, 512, 61, 908, 170, 897, 275, 653,
426, 154, 509, 612, 677, 765, 703 }; string sa[11] = {
"a", "heavy", "snow", "was", "falling", "when",
"they", "left", "the", "police", "station" }; int main() {
// вызвать конструктор для инициализации arrd
Array arrd( da, sizeof(da)/sizeof(da[0]) );
// вызвать конструктор для инициализации arri
Array arri( ia, sizeof(ia)/sizeof(ia[0]) );
// вызвать конструктор для инициализации arrs
Array arrs( sa, sizeof(sa)/sizeof(sa[0]) ); cout << "sort array of doubles (size == "
<< arrd.size() << ")" << endl; sort(arrd, 0, arrd.size()-1 ); display(arrd); cout << "sort array of ints (size == "
<< arri.size() << ")" << endl; sort(arri, 0, arri.size()-1 ); display(arri); cout << "sort array of strings (size == "
<< arrs.size() << ")" << endl; sort(arrs, 0, arrs.size()-1 ); display(arrs); return 0;

С++ для начинающих
520
В числе обобщенных алгоритмов, имеющихся в стандартной библиотеке C++ (и в главе
12), вы найдете также функции min() и swap(). В главе 12 мы покажем, как их использовать.

С++ для начинающих
521
11
11.
Обработка исключений
Обработка исключений – это механизм, позволяющий двум независимо разработанным программным компонентам взаимодействовать в аномальной ситуации, называемой
исключением. В этой главе мы расскажем, как генерировать, или возбуждать, исключение в том месте программы, где имеет место аномалия. Затем мы покажем, как связать catch
-обработчик исключений с множеством инструкций программы, используя try- блок. Потом речь пойдет о спецификации исключений – механизме, с помощью которого можно связать список исключений с объявлением функции, и функция не сможет возбудить никаких других исключений. Закончится эта глава обсуждением решений, принимаемых при проектировании программы, в которой используются исключения.
11.1.
Возбуждение исключения
Исключение – это аномальное поведение во время выполнения, которое программа может обнаружить, например: деление на 0, выход за границы массива или истощение свободной памяти. Такие исключения нарушают нормальный ход работы программы, и на них нужно немедленно отреагировать. В C++ имеются встроенные средства для их возбуждения и обработки. С помощью этих средств активизируется механизм, позволяющий двум несвязанным (или независимо разработанным) фрагментам программы обмениваться информацией об исключении.
Когда встречается аномальная ситуация, та часть программы, которая ее обнаружила, может сгенерировать, или возбудить, исключение. Чтобы понять, как это происходит, реализуем по-новому класс iStack, представленный в разделе 4.15, используя исключения для извещения об ошибках при работе со стеком. Определение класса iStack выглядит следующим образом:
};
#include class iStack { public: 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;

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

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

запрашивается операция push(), но стек полон.
Вызвавшую функцию нужно уведомить об этих ошибках посредством исключений. С чего же начать?
Во-первых, мы должны определить, какие именно исключения могут быть возбуждены. В
C++ они чаще всего реализуются с помощью классов. Хотя в полном объеме классы будут представлены в главе 13, мы все же определим здесь два из них, чтобы использовать их как исключения для класса iStack. Эти определения мы поместим в заголовочный файл stackExcp.h: class pushOnFull { /* ... */ };
В главе 19 исключения в виде классов обсуждаются более подробно, там же рассматривается иерархия таких классов, предоставляемая стандартной библиотекой
C++.
Затем надо изменить определения функций-членов pop() и push() так, чтобы они возбуждали эти исключения. Для этого предназначена инструкция throw, которая во многих отношениях напоминает return. Она состоит из ключевого слова throw, за которым следует выражение того же типа, что и тип возбуждаемого исключения. Как выглядит инструкция throw для функции pop()? Попробуем такой вариант: throw popOnEmpty;
К сожалению, так нельзя. Исключение – это объект, и функция pop() должна генерировать объект класса соответствующего типа. Выражение в инструкции throw не может быть просто типом. Для создания нужного объекта необходимо вызвать конструктор класса. Инструкция throw для функции pop() будет выглядеть так: throw popOnEmpty();
Эта инструкция создает объект исключения типа popOnEmpty.
// stackExcp.h class popOnEmpty { /* ... */ };
// увы, это не совсем правильно
// инструкция является вызовом конструктора

С++ для начинающих
523
Напомним, что функции-члены pop() и push() были определены как возвращающие значение типа bool: true означало, что операция завершилась успешно, а false – что произошла ошибка. Поскольку теперь для извещения о неудаче pop() и push() используют исключения, возвращать значение необязательно. Поэтому мы будем считать, что эти функции-члены имеют тип void:
};
Теперь функции, пользующиеся нашим классом iStack, будут предполагать, что все хорошо, если только не возбуждено исключение; им больше не надо проверять возвращенное значение, чтобы узнать, как завершилась операция. В двух следующих разделах мы покажем, как определить функцию для обработки исключений, а сейчас представим новые реализации функций-членов pop() и push() класса iStack:
}
Хотя исключения чаще всего представляют собой объекты типа класса, инструкция throw может генерировать объекты любого типа. Например, функция mathFunc() в следующем примере возбуждает исключение в виде объекта-перечисления . Это корректный код C++: class iStack { public:
// ...
// больше не возвращают значения void pop( int &value ); void push( int value ); private:
// ...
#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;

С++ для начинающих
1   ...   45   46   47   48   49   50   51   52   ...   93


написать администратору сайта