Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

OOP_C++ / 13

.htm
Скачиваний:
20
Добавлен:
02.02.2015
Размер:
21.37 Кб
Скачать

13 - Композиция классов. Наследование и полиморфизм Содержание     Предыдущее занятие     Следующее занятие

Занятие 13 Использование шаблонов 1 Описание и использование шаблонных функций Шаблонные функции предназначены для записи обобщенных функций, которые могут работать с данными различного типа.

Определение и объявление шаблона функции начинается служебным словом template. За ним идет заключенный в угловые скобки ("") список формальных параметров шаблона, разделенных запятыми. Список формальных параметров шаблона не может быть пустым. Каждый формальный параметр, определяющий тип, состоит из служебного слова class, за которым идет идентификатор. Имя формального параметра в списке должно быть уникальным.

Формальные параметры шаблонов могут использоваться для определения типа результата и формальных параметров шаблонной функции. В теле шаблонной функции также могут использоваться формальные параметры шаблона.

В качестве примера шаблона приведем функцию суммирования элементов массива произвольного типа. Главное, чтобы для элементов массива были определены операции присваивания, в том числе присваивания константы "ноль", и "+=".

template <class SomeType> SomeType sumOfArray(SomeType *a, const int size) { SomeType sum = 0; for (int i = 0; i < size; i++) sum += a[i]; return sum; } Кроме того, можно определить шаблон функции вывода в стандартный поток элементов массива:

template <class SomeType> void printArray(SomeType *a, const int size) { for (int i = 0; i < size; i++) cout << a[i] << ' '; cout << endl; } Конкретные определения функций, соответствующих шаблону, компилятор генерирует при вызове или взятии адреса шаблонной функции с параметрами конкретного типа. Для описанного выше примера можно предложить следующее использование шаблонных функций:

int main(int argc, char* argv[]) { int a[] = {1, 2, 3}; printArray(a, 3); cout << sumOfArray(a, 3) << endl; double b[] = {1.1, 2.2, 3.3, 4.5}; printArray(b, 4); cout << sumOfArray(b, 4) << endl; cin.get(); return 0; } Шаблонная функция может перегружаться при условии, что список формальных параметров каждого варианта отличается от остальных либо типами параметров, либо их числом.

Бывают случаи, когда для каких-то конкретных типов нужно дать особое определение шаблонной функции. В этом случае программист должен задать свой специальный вариант функции. Например, шаблон функции min() работает для типов, для которых определена операция "<":

template <class Type> Type min(Type a, Type b) { return a < b ? a : b; } Данный шаблон не подходит для варианта сравнения строк. Для них определяется специальный вариант функции:

char* min (char* s1, char* s2) { return strcmp(s1, s2) < 0 ? s1 : s2; } Порядок разрешения обращения к функции будет следующим.

Исследуются все нешаблонные варианты функции. Если есть одно точное сопоставление, то вызвать вариант функции, если более одного - то выдать ошибку.

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

Повторно исследовать все нешаблонные варианты функции, пытаясь применить пользовательское преобразование.

Для того, чтобы можно было конкретизировать шаблон, компилятор должен видеть не только объявление, но и определение функции. Поэтому определения шаблонных функций можно и нужно помещать в заголовочные файлы.

При вызове функции фактический параметр шаблона можно указать явно, например:

int i = min<int>(2, 3); 2 Параметризированные классы Предварительное объявление и определение шаблонного класса начинается со служебного слова template. За ним следует список формальных параметров шаблона типа. Этот список не может быть пустым.

Параметрами шаблонов могут быть параметры-типы, параметры обычных типов и параметры-шаблоны. У шаблона может быть несколько параметров. Целые аргументы используются обычно для задания размеров и границ. Целый аргумент шаблона должен быть константой. Возможно задание параметров шаблона по умолчанию.

Для каждого имени шаблона типа в программе должно существовать только одно определение.

Параметризированные классы допускают использование механизма наследования. Возможно создание производного параметризированного класса как от шаблона, так и от класса, не являющегося шаблоном.

Функция-элемент шаблонного класса считается неявной шаблонной функцией, а параметры шаблона типа для ее класса - ее шаблонными параметрами. Для некоторых типов стандартные функции-элементы не подходят. В таких случаях можно явно задавать реализацию функции, рассчитанной на конкретный тип. Перед реализацией таких функций требуется специальное объявление template без параметров. Кроме того, можно дать особое определение шаблонного класса, рассчитанное на конкретный тип. Функция-друг для шаблона типа не является неявной шаблонной функцией.

Шаблон класса может иметь статические элементы. Каждый класс, сгенерированный по шаблону, имеет свою копию статических элементов.

Сочетание производных классов (наследование) и шаблонов типа может быть мощным средством. Шаблон типа выражает общность между всеми типами, которые используются как его параметры, а базовый класс выражает общность между всеми представлениями (объектами) и называется интерфейсом.

Два созданных по одному шаблону типа будут различны и между ними невозможно отношение наследования кроме единственного случая, когда у этих типов идентичны параметры шаблона. Например:

template<class T> class X { /* ... */ } X<int> x1; x<short> x2; X<int> x3; Здесь x1 и x3 одного типа, а x2 имеет совершенно другой тип. Из того факта, что short неявно преобразуется в int, не следует, что есть

неявное преобразование X<short> в X<int>:

x2 = x3; // несоответствие типов 3 Алгоритмы cтандартной библиотеки Алгоритм (algorithm): - это шаблонная функция, объявленная в пространстве имен std и в заголовочном файле <algorithm>, которая работает с элементами произвольных последовательностей, заданных итераторами и определяет вычислительную процедуру.

Все алгоритмы отделены от деталей реализации структур данных и используют в качестве параметров типы итераторов. Поэтому они могут работать с определяемыми пользователем структурами данных, когда эти структуры данных имеют типы итераторов, удовлетворяющие предположениям в алгоритмах. Шаблонные алгоритмы Стандартной библиотеки работают не только со структурами данных в библиотеке, но также и с встроенными структурами данных C++.

Большинство алгоритмических шаблонов библиотеки, которые работают со структурами данных, имеют интерфейсы, которые используют диапазоны. Диапазон - это пара итераторов, которые указывают начало и конец последовательности. Диапазон [i, j) допустим, если j доступен из i. Результат применения алгоритмов к недопустимым диапазонам не определён.

Существует несколько групп алгоритмов:

алгоритмы, не модифицирующие последовательности;

алгоритмы, модифицирующие последовательность;

алгоритмы сортировки;

алгоритмы для работы с множествами;

алгоритмы для работы с кучей (heap);

алгоритмы нахождения максимумов и минимумов;

алгоритмы перестановок.

Рассмотрение общих свойств алгоритмов и использование вспомогательных объектов можно произвести на примере простого алгоритма, не меняющего последовательности - for_each(). С помощью данного алгоритма задаются операции с каждым элементом последовательности.

Алгоритм for_each(InputIterator first, InputIterator last, Function f) применяет f к результату разыменования каждого итератора в диапазоне [first, last). Если f возвращает результат, результат игнорируется.

В большинстве случаев вместо for_each() целесообразно использовать другие алгоритмы. Однако в качестве примера можно вывести на экран значения целых с помощью алгоритма for_each():

#include <iostream> #include <algorithm> #include <vector> using namespace std; void writeInt(const int& i) { cout > a.pa[i]; return in; } private: Type *pa; int size; public: class OutOfBounds { int index; public: OutOfBounds(int i) : index(i) { } int getIndex() const { return index; } }; Array() { pa = 0; size = 0; } Array(int n); Array(Array& arr); ~Array() { if (pa) delete [] pa; } void addElem(Type elem); Type& operator[](int index); const Array& operator=(const Array& a); bool operator==(const Array& a) const; Type getMin(); int getSize() const { return size; } }; Определение внутри класса дружественных функций, перегружающих операции ввода-вывода, не является требованием стандарта, но оно необходимо при реализации класса в Borland C++Builder. В описании класса добавлено объявление функции getMin(), определяющей минимальный элемент массива.

Функции-элементы класса преобразуются в соответствующие шаблонные функции. До применения операции разрешения области видимости класса, его имя должно сопровождаться указанием параметра шаблона:

template <class Type> Array<Type>::Array(int n) { pa = new Type [size = n]; } template <class Type> Array<Type>::Array(Array& arr) { size = arr.size; pa = new Type [size]; for (int i = 0; i < size; i++) pa[i] = arr.pa[i]; } template <class Type> void Array<Type>::addElem(Type elem) { Type *temp = new Type [size + 1]; if (pa) { for (int i = 0; i < size; i++) temp[i] = pa[i]; delete [] pa; } pa = temp; pa[size] = elem; size++; } template <class Type> Type& Array<Type>::operator[](int index) { if (index < 0 || index >= size) throw OutOfBounds(index); return pa[index]; } template <class Type> const Array<Type>& Array<Type>::operator=(const Array& a) { if (&a != this) { if (pa) delete [] pa; size = a.size; pa = new Type [size]; for (int i = 0; i < size; i++) pa[i] = a.pa[i]; } return *this; } template <class Type> bool Array<Type>::operator==(const Array& a) const { if (&a == this) return true; if (size != a.size) return false; for (int i = 0; i < size; i++) if (pa[i] != a.pa[i]) return false; return true; } template <class Type> Type Array<Type>::getMin() { Type min = pa[0]; for (int i = 1; i < size; i++) if (min > pa[i]) min = pa[i]; return min; } Функция getMin() подходит только для таких типов, для которых определена операция >. Для тех типов, для которых операция е определена, или имеет другой смысл, целесообразно предоставить специализированную реализацию функции getMin(). Например, такая специализация необходима для типа char*:

template <> char* Array<char*>::getMin() { char* min = pa[0]; for (int i = 1; i < size; i++) if (strcmp(min, pa[i]) > 0) min = pa[i]; return min; } В функции main() можно протестировать класс массива на примере нескольких типов. Поскольку класс OutOfBounds является вложенным в шаблонный класс Array, он также инстанцируется для различных типов элементов:

int main(int argc, char* argv[]) { Array<double> a; a.addElem(11); a.addElem(12.5); cout << a << endl; try { a[10] = 35; } catch (Array<double>::OutOfBounds e) { cout << "Bad index: " << e.getIndex() << endl; } cout << a.getMin() << endl; Array<char*> b; b.addElem("First"); b.addElem("Second"); cout << b << endl; cout << endl; try { b[10] = "Bad"; } catch (Array<char*>::OutOfBounds e) { cout << "Bad index: " << e.getIndex() << endl; } cout << b.getMin() << endl; cin.get(); return 0; } 5 Задания на самостоятельную работу Задание 1 Реализовать программу решения уравнения методом дихотомии с использованием шаблонного класса.

Задание 2 Реализовать шаблонную функцию, реализующую вычисление максимального элемента массива и возвращающую его индекс. Протестировать работу в функции main().

Задание 3 Видоизменить созданный ранее класс "Матрица", превратив его в параметризированный класс.

Задание 4* Реализовать класс "Стек" как параметризированный класс.

 

Содержание     Предыдущее занятие     Следующее занятие

 

© 2001 - 2006 Иванов Л.В.

Соседние файлы в папке OOP_C++