Главная » Статьи » Мои статьи » С++ для начинающих |
Для решения данной проблемы в С++ введен механизм шаблонов. В объявлениях классов и
функций допускается использование параметризованных
типов. Типы-параметры заменяются в процессе компиляции настоящими типами,
встроенными или определенными пользователем. Мы можем создать шаблон класса Array,
заменив в классе IntArray тип элементов int
на обобщенный тип-параметр. Позже мы конкретизируем
типы-параметры, подставляя вместо них реальные типы int, double
и string.
В результате появится способ использовать эти конкретизации так, как будто мы
на самом деле определили три разных класса для этих трех типов данных. Вот как может выглядеть шаблон класса Array:
}; Ключевое слово template
говорит о том, что задается шаблон, параметры
которого заключаются в угловые скобки (<>).
В нашем случае имеется лишь один параметр elemType;
ключевое слово class перед его именем сообщает, что этот
параметр представляет собой тип. При конкретизации класса-шаблона Array
параметр elemType
заменяется на реальный тип при каждом использовании, как показано в примере:
} Здесь определены три экземпляра класса Array:
Array<double> da(array_size); Array<char> ca(array_size); Что делает компилятор, встретив такое объявление?
Подставляет текст шаблона Array, заменяя параметр elemType
на тот тип, который указан в каждом конкретном случае. Следовательно,
объявления членов приобретают в первом случае такой вид: // Array<int> ia(array_size); int _size; int *_ia; Заметим, что это в точности соответствует определению массива IntArray. Для оставшихся двух случаев мы получим следующий код:
int _size; double *_ia; // Array<char> ca(array_size); int _size; char *_ia; Что происходит с функциями-членами? В них тоже тип-параметр elemType
заменяется на реальный тип, однако компилятор не конкретизирует те функции,
которые не вызываются в каком-либо месте программы. (Подробнее об этом в разделе
16.8.) При выполнении программа этого примера выдаст следующий
результат: [ 0 ] ia: 0 ca: a da: 0 [ 1 ] ia: 1 ca: b da: 1.75 [ 2 ] ia: 2 ca: c da: 3.5 [ 3 ] ia: 3 ca: d da: 5.25 Механизм шаблонов можно использовать и в наследуемых классах. Вот как выглядит определение шаблона класса ArrayRC:
#include "Array.h" template <class elemType> class ArrayRC : public Array<elemType> { public: ArrayRC( int sz =
DefaultArraySize ) : Array<elemType>( sz
) {} ArrayRC( const ArrayRC& r ) : Array<elemType>(
r ) {} ArrayRC( const elemType *ar, int
sz ) : Array<elemType>(
ar, sz ) {} elemType&
ArrayRC<elemType>::operator[]( int ix ) { assert( ix >= 0 &&
ix < Array<elemType>::_size ); return _ia[ ix ]; } private: // ... }; Подстановка реальных параметров вместо типа-параметра elemType
происходит как в базовом, так и в производном классах. Определение ArrayRC<int> ia_rc(10); ведет себя точно так же, как определение IntArrayRC
из предыдущего раздела. Изменим пример использования из предыдущего раздела.
Прежде всего, чтобы оператор
swap( ia1, 1, ia1.size() ); был допустимым, нам потребуется представить функцию swap() в
виде шаблона.
template <class elemType> inline void swap( Array<elemType> &array, int i, int j ) { elemType tmp = array[ i ]; array[ i ] = array[ j ]; array[ j ] = tmp; } При каждом вызове swap() генерируется подходящая конкретизация, которая зависит от типа массива. Вот как выглядит программа, использующая шаблоны Array и ArrayRC:
#include "Array.h" #include "ArrayRC.h" template <class elemType> inline void swap( Array<elemType> &array, int i, int j ) { elemType tmp = array[ i ]; array[ i ] = array[ j ]; array[ j ] = tmp; } int main() { Array<int> ia1; ArrayRC<int> ia2; cout << "swap() with
Array<int> ia1" << endl; int size = ia1.size(); swap( ia1, 1, size ); cout << "swap() with
ArrayRC<int> ia2" << endl; size = ia2.size(); swap( ia2, 1, size ); return 0; } Упражнение 2.13 Пусть мы имеем следующие объявления типов:
enum Status { ... }; typedef string *Pstring; Есть ли ошибки в приведенных ниже описаниях объектов?
(e) Array< Pstring > aps(1024); Упражнение 2.14 Перепишите следующее определение, сделав из него шаблон
класса:
} Упражнение 2.15 Имеется следующий шаблон класса:
public: explicit Example2 (elemType val=0) : _val(val) {}; bool min(elemType value) { return _val < value; } void value(elemType new_val) { _val = new_val; } void print (ostream &os) { os << _val; } private: elemType _val; } template <class elemType> ostream& operator<<(ostream &os,const Example2<elemType> &ex) { ex.print(os); return os; } Какие действия вызывают следующие инструкции?
(f) cout << "exs: " << exs << endl; Упражнение 2.16 Пример из предыдущего упражнения накладывает определенные ограничения на типы данных, которые могут быть подставлены вместо elemType. Так, параметр конструктора имеет по умолчанию значение 0: explicit Example2 (elemType val=0) : _val(val) {}; Однако не все типы могут быть инициализированы нулем
(например, тип string), поэтому определение объекта Example2<string> exs("Walden"); является правильным, а Example2<string> exs2; приведет к синтаксической ошибке[1].
Также ошибочным будет вызов функции min(),
если для данного типа не определена операция меньше. С++ не позволяет задать ограничения для типов,
подставляемых в шаблоны. Как вы думаете, было бы полезным иметь такую
возможность? Если да, попробуйте придумать синтаксис задания ограничений и
перепишите в нем определение класса Example2.
Если нет, поясните почему. Упражнение 2.17 Как было показано в предыдущем упражнении, попытка использовать шаблон Example2 с типом, для которого не определена операция меньше, приведет к синтаксической ошибке. Однако ошибка проявится только тогда, когда в тексте компилируемой программы действительно встретится вызов функции min(), в противном случае компиляция пройдет успешно. Как вы считаете, оправдано ли такое поведение? Не лучше ли предупредить об ошибке сразу, при обработке описания шаблона? Поясните свое мнение. [1] Вот как выглядит общее решение этой проблемы: Example2( elemType nval = elemType() ) " _val( nval ) {} | ||||||||||||||
Просмотров: 4350 | Комментарии: 12 | | |
Всего комментариев: 0 | |