Lab04 - Белорусско-Российский университет

advertisement
Министерство образования Республики Беларусь
Министерство образования и науки Российской Федерации
ГУВПО “Белорусско-Российский университет”
Кафедра “Автоматизированные
cистемы управления”
Дисциплина “Объектно-ориентированное программирование”
Лабораторная работа № 4
Шаблоны
Время выполнения работы – 4 часа
2014
2
1 Цель работы
Получение навыков в разработке программ с использованием шаблонов классов.
2 Техническое обеспечение
1.1
1.2
1.3
1.4
Персональная ЭВМ IBM PC/486 и более поздних моделей.
Клавиатура.
Дисплей.
Печатающее устройство.
3 Программное обеспечение
3.1 Операционная система Windows 95 и более поздние версии.
3.2 Система программирования Visual C++ версия 5.0 и более поздние версии.
4 Постановка задачи
Выполнить задание согласно варианта, приведенного в разделе 7 настоящих методических указаний.
5 Содержание отчета
5.1 Тема и цель работы.
5.2 Текст программы.
5.3 Результаты выполнения программы.
6 Общие сведения
Такие контейнеры, как стеки и очереди, характеризуются дисциплиной доступа к элементам и фактически не зависят от типа элементов. Аналогично, контейнер-массив представляет независимый от типа доступ к элементам с помощью операции индексирования.
Создать универсальный контейнер, безразличный к типу элементов, можно двумя способами:
 используя в качестве элемента контейнера указатель void *;
 на основе шаблона класса, в котором тип элементов задается параметром шаблона.
Первый способ  наследие от С, поскольку в этом языке не было других средств. В
С++ обычно используется другой способ  так называемые шаблоны.
6.1 Шаблоны классов
Шаблон класса является заготовкой, из которой создаются конкретные классы в процессе инстанцирования (то есть создания сущности, инстанции). Делается это путем подстановки конкретного типа в качестве аргумента. Шаблон класса еще называют параметризованным типом. Термин «параметризованный тип» эквивалентен терминам «шаблон класса»
и «шаблон».
Синтаксис класса-шаблона выглядит так:
template <параметры>
class имя_класса
{ // определение класса
};
// объявление шаблона
Префикс template <параметры> показывает компилятору, что следующее определение
класса является шаблоном, а не обычным классом. Шаблоном может быть и структура. Как и
3
имя обычного класса, имя класса-шаблона не должно совпадать с другими именами в одной
области видимости. Параметры шаблона могут быть следующих видов:
 параметр-тип;
 параметр целочисленного или перечислимого типа;
 параметр-указатель на объект или указатель на функцию;
 параметр-ссылка на объект или ссылка на функцию;
 параметр-указатель на член класса.
К целочисленным типам относятся все целые, символьные и булевский ( bool) типы.
Нельзя использовать в качестве параметра ни один из встроенных дробных типов  ни float,
ни double, ни long double. Параметры шаблона, если их несколько, записываются через запятую. Все виды параметров, кроме параметра-типа, пишутся обычным образом (как в функциях), например:
long р, int (*f)(double), int *t, const char *s
Имена параметров видимы в пределах всего шаблона, поэтому внутренние для шаблона имена должны быть разными.
Параметр-тип является наиболее часто используемым и объявляется как
class имя
или
typename имя
В качестве имени параметра разрешается применять любой допустимый идентификатор.
Внутри класса такой параметр может появляться на тех местах, где позволено писать конкретный
тип.
Шаблон структуры-пары с двумя полями разного типа можно определить разными способами:
template <class T1, class T2>
struct Pair { Tl first; T2 second; };
template <typename T1, typename T2>
struct Pair { T1 first; T2 second; };
template <class T1, typename T>
struct Pair { T1 first; T second; };
Шаблон, как и обычный класс, можно объявить. Объявление состоит из заголовка шаблона и
не включает в себя тело класса, например:
template <typename Т> class Stack;
template <typename T1, typename T2> struct Pair;
template <class T> class Array;
Определение объектов для некоторого класса-шаблона в общем виде выглядит так:
имя_класса_шаблона <аргументы> имя_объекта;
имя_класса_шаблона <аргументы> имя_объекта[количество];
имя_класса_шаблона <аргументы> *имя_обьекта;
// одиночный объект
//массив
// указатель
Или для случая константной ссылки в качестве параметра:
const имя_класса_шаблона <аргументы> &имя_объекта;
В угловых скобках на месте параметров при объявлении объектов задают конкретные аргументы. Параметры шаблона являются позиционными (как и аргументы функции), поэтому подставляемое фактическое значение должно соответствовать виду параметра шаблона: если на данном месте в определении класса-шаблона находился параметр-тип, то и аргумент должен быть типом или
классом. Например, определение конкретных объектов-пар может быть таким:
Pair <int, int> х;
4
Pair <int, double> у;
Pair <string, date> d;
В качестве аргумента-типа можно использовать и класс-шаблон, например:
Pair <Pair <int, long>, float> pair;
6.1.1 Пример класса-шаблона
Листинг 11.1. Простой массив  шаблон
#include <stdexcept>
template <class T, std::size_t N>
// параметры шаблона
class Array
{
public:
// типы
typedef T
value_type;
typedef T
&reference;
typedef const T
&const_reference;
typedef std::size_t
size_type;
static const size_type static_size = N;
// размер массива
Array(const T &t = T());
// конструктор
size_type size() const
// получение размера
{ return static_size; }
reference operator[](const size_type &i)
// доступ к элементам
{ rangecheck(i); return elem[i]; }
const_reference operator[](const size_type &i) const
{ rangecheck(i); return elem[i]; };
private:
void rangecheck (const size_type &i) const
{
if (i >= size()) { throw std::range_error("Array - range!"); }
}
T elem[N];
};
template <typename Т, std::size_t N>
// реализация конструктора
Array <T,N>::Array(const T &t)
{ for (int i = 0; i<N; i++) elem[i] = t; }
// проверка индекса
// поле-массив
-шаблона определено несколько
типов-синонимов. Проверка индекса в операциях индексирования [] выполняется приватной функцией rangecheck(), которая генерирует стандартное исключение при «вылете» за границу массива.
Конструктор инициализации является и конструктором по умолчанию. Конструктор классашаблона задан внешним образом: определение метода должно начинаться со слова template вместе с
параметрами шаблона:
template <typename Т, std::size_t N>
Тип класса-шаблона Array <T, N>:: задается в качестве префикса. В теле метода для всех
имен класса префикс может отсутствовать. Для методов, определенных внутри шаблона, префиксы
не указываются.
Параметру присвоено значение по умолчанию
const Т &t = Т()
При инстанцировании шаблона (подстановка конкретного типа) эта конструкция означает
инициализацию нулем для элементарных встроенных типов; а для классов влечет вызов конструктора
по умолчанию (то есть класс, используемый в качестве подставляемого аргумента шаблона, должен
иметь конструктор по умолчанию).
5
Данную конструкцию можно использовать не только в списке параметров, но в любом месте
шаблона, где допускается объявить переменную и присвоить ей значение. Например, в цикле инициализации массива можно было бы написать:
elem[i] = Т();
что превращается в корректную конструкцию при подстановке любого типа, как встроенного, так и
реализованного.
В классе-шаблоне разрешено использовать инициализацию нулем и в списке инициализации
конструктора. Если отказаться от присвоения значения по умолчанию и использовать явное обнуление, конструктор класса-шаблона примет вид:
template <typename Т, std::size_t N>
// реализация конструктора
Array <T, N>::Array():elem() { }
Массив будет проинициализирован нулем, если вместо Т подставить встроенный тип. Для
классов тогда будет вызываться конструктор без аргументов.
Array <int, 10> w;
Array <int, 10> t(0);
Array <int, 10> r(t);
Array <double, 50> р(2);
for (int i = 0; i < p.size(); i++)
cout << p[i] << ' ';
for (int i = 0; i < p.size(); i++)
p[i] = i;
//
//
//
//
инициализируем нулем
инициализируем нулем
конструктор копирования
все элементы = 2
// доступ справа
// доступ слева
В качестве типа-аргумента могут подставляться любые возможные типы, определение которых видимо в точке объявления переменной-массива.
Array <void*, 100> рр;
Array<Pair <int, int>, 100> mр;
Array<Pair<long,int>*,10> *mpp;
// массив указателей
// массив объектов-пар
// указатель на массив
// указателей на пары
// массив дат
Array<date, 10> dd;
Класс date должен иметь конструктор без аргументов, вызываемый в конструкторе массива.
Действительно также и присваивание:
w = г;
// присваиваем - типы одинаковы
Копирование и присваивание работают только для массивов с одинаковым типом элементов и
размером массива. Изменяя тип элементов и (или) размер, получаем массивы разных типов. При попытках использовать оператор присваивания или конструктор копирования с такими разнотипными
массивами компилятор выдает сообщение о невозможности преобразования типов. Например, нельзя
написать присвоение
р = t;
Слева массив с типом элементов double
int. Преобразование типов по умолчанию
не выполняется.
Объекты такого класса-шаблона можно передавать как параметры и получать как результат из
функций. Необходимо помнить, что при передаче (и при возврате) массива по значению вызывается
конструктор копирования, поэтому лучше такой массив передавать по ссылке.
Array<double, 10> F(const Array <int, 10> &t);
Эта функция получает ссылку на массив из 10 элементов типа int, а возвращает массив из 10
элементов типа double. Нужно также помнить, что преобразование типов по умолчанию не работает.
6.1.2 Параметры шаблона, задаваемые по умолчанию
Параметрам класса-шаблона можно присваивать значения по умолчанию, в том числе и для
параметров-типов. Для целочисленных параметров допускается задавать в качестве значения константное выражение, которое компилятор способен вычислить на этапе трансляции. Для параметров-
6
типов можно указывать либо встроенный тип, либо любое видимое в точке определения шаблона имя
тина.
Например, для шаблона «умного» массива Array (листинг 11.1) можно присвоить по умолчанию тип double и ограничить массив десятью элементами. Заголовок шаблона пишется привычным
способом
template <typename Т = double, std::size_t N = 10>
В реализации методов ничего изменять не требуется. Тогда допускаются объявления массивов такого вида:
Array <int, 20> t;
Array <date> d;
Аггау <> р;
// полное объявление
// количество по умолчанию
// тип и количество по умолчанию
Пустые угловые скобки указывать обязательно, иначе возникает ошибка времени компиляции, поскольку компилятор будет искать обычный класс Array. Во всех объявлениях инициализация
выполняется неявно. Класс date должен иметь конструктор без аргументов или конструктор инициализации с аргументом по умолчанию.
Как и для функций с аргументами по умолчанию, присваивать значения нужно правым параметрам. Можно написать заголовок шаблона Array так:
template <typename Т, std::size_t N = 100>
и не разрешается
таким способом:
template <typename Т = double, std::size_t N>
Как и для функций с аргументами по умолчанию, при определении объекта нельзя пропускать
левые параметры, например:
Array<10> t;
// ожидается ошибка трансляции!
Подобное объявление приведет к ошибке трансляции  компилятор не обнаружит определения шаблона с одним целочисленным параметром.
6.1.3 Специализация шаблона класса
При программировании шаблонов классов часто бывает нужно наряду с общим шаблоном
иметь специализированные версии. Для этого в С++ имеется механизм специализации. Специализация заключается в том, что на основе исходного первичного шаблона создается его специализированная версия, в которой на место параметров подставлены аргументы (и по-другому реализованы некоторые методы  в соответствии с аргументами). Специализация  это не присвоение параметров по
умолчанию. Шаблон с параметрами по умолчанию является первичным шаблоном, который тоже
можно специализировать. Специализация шаблона называется полной, если конкретизированы все
аргументы. Если задана только часть аргументов, такая специализация называется частичной.
При определении шаблона и его специализированных версий должен соблюдаться порядок
следования: сначала должен быть определен первичный шаблон, и только после него разрешается
определять специализированные версии.
tempiate<class Т>
// первичный шаблон
class Class
{ // определения полей и методов класса
};
template < >
// полная специализация
class Class <void *>
{ // определения полей и методов специализированной версии класса
};
Аргументы шаблона, подлежащие специализации, указаны в угловых скобках после имени
класса. В данном случае шаблон Class специализирован для бестиповых указателей. Объект такого
класса нужно объявлять как объект класса-шаблона с аргументом void *, например:
Class <void *> ddd;
7
При частичной специализации конкретизируется только часть параметров первичного шаблона. Для указателей возможна частичная специализация, даже если параметр шаблона всего один,
например:
template <class Т>
// частичная специализация
class Class<T*>
{ // определения полей и методов специализированной версии класса
};
Обозначение <Т*> после имени подразумевает, что эта специализация должна использоваться
всегда, когда аргументом шаблона является указатель любого типа, отличного от void *, для которого
«реализована более специализированная полная специализация». Определения объектов выглядят
так:
Class <date*> m1;
Class <int*> m2;
Class <double**> m3;
// <T*> - это <date*> -> T = date
// <T*> - это <int*> -> T = int
// <T*> - это <double**> -> T = double*
Чтобы специализировать шаблон, не обязательно иметь полное определение первичного шаблона  достаточно объявления, например:
template <typename T> class Class:
// объявление первичного шаблона
template <typename T> class Class <T*>
// частичная специализация
{ // определения полей и методов специализированной версии класса
};
template <> class Class <void *>
// полная специализация
{ // определения полей и методов специализированной версии класса
};
Все будет корректно работать, если не пытаться инстанцировать первичный шаблон.
6.1.4 Статические элементы в шаблонах
В классе-шаблоне разрешено объявлять и статические методы, и статические поля. В этом
случае при различных вариантах параметров шаблона получаются фактически разные конкретные
классы, и каждый из них обладает собственной копией статических членов, как показано в листинге
11.2.
Листинг 11.2. Класс-шаблон со статическими элементами
template <typename Т>
class StaticClass
{
static T t;
static int count;
public:
StaticClass() { ++count; }
~StaticClass ()_ { --count; }
static void print(T x = t);
template <typename T> int StaticClass <T>::count = 0;
template <typename T> T StaticClass<T>:: t = T();
template <class T>
void StaticClass<T> :: print(T x)
{ std::cout << x << “ “ << count; t = x; }
// зависимое имя
// независимое имя
// конструктор считает объекты
// деструктор
// статический метод
// счетчик объектов
// определение t
// реализация метода
В классе определен счетчик объектов  статическое поле count. Это поле не зависит от параметра шаблона, однако при определении поля нужно соблюдать синтаксис для элементов классашаблона. Определение статического поля t, тип которого зависит от шаблона, можно задать без явного вызова безаргументного конструктора, так как он вызывается по умолчанию:
template <typename Т>
Т StaticClass <T>::t;
// вызов конструктора по умочанию
Реализация статического метода, как обычно, должна сопровождаться префиксом шаблона
template <class Т>.
8
Разрешается специализировать статические поля:
template <> int StaticClass <int>::t = 22;
template <> double StaticClass<double>::t = 15;
// специализация t
// специализация t
Специализация статического поля вызывает инстанцирование шаблона с параметром заданного типа. В данном случае создаются классы StaticClass <int> и StaticC1ass <double>. Для каждого из
них заводятся статические поля-счетчики count, статические поля t, которым присваиваются значения, и создаются статические методы print ().
6.1.5 Шаблоны классов с шаблонами
К шаблонам применимо, как в экономике, отношение «шаблон-класс-шаблон». Нет ограничений на вложенность классов-шаблонов: класс может быть объявлен внутри шаблона и шаблон 
внутри как класса, так и шаблона. Единственное ограничение  класс-шаблон нельзя объявлять внутри функции (а обычный класс можно). Шаблон может наследовать от обычного класса; класс может
наследовать от шаблона; шаблон может наследовать от шаблона. В классе-шаблоне допускаются
вложенные конструкции:
 инстанцированный класс-шаблон в качестве поля в классе и в шаблоне;
 класс-шаблон в качестве параметра-типа по умолчанию;
 метод-шаблон, в том числе конструктор.
Поле-шаблон
В классе допускается объявить поле, инстанцировав некоторый шаблон. Объявление поляшаблона ничем не отличается от объявления обычного поля. Например, на основе класса-шаблона
Array (см. листинг 11.1) можно реализовать стек ограниченного размера.
class Stack
{
Array<double. 100> t;
public:
// методы
};
// поле - инстанцированный шаблон
Объявить объект-стек можно обычным образом:
Stack t;
Поле-шаблон в шаблоне выглядит так:
template <typename Т, std::size_t N = 100>
{
Аггау<Т, N> st;
public:
// методы
};
// параметры стека class Stack
// зависимое имя
Так как количество элементов стека явно не объявлено, определять переменные Stack разрешено так:
Stack<double> st;
Или с явным указанием количества элементов
Stack <double, 500> stack;
Параметр-шаблон
Разрешается использовать в качестве параметра шаблон. Параметру-шаблону можно присвоить значение по умолчанию, например Array (листинг 11.1).
template <typename Т,
// тип элементов
std::size_t N = 100,
// количество
9
template <class, std::size_t>
// параметр-шаблон
class Container = Array
>
class Stack
{
Container <T, N> s;
public:
// методы
};
// конец списка
// поле-контейнер
Параметр-шаблон объявлен последним:
template <class, std::size_t> class Container = Array>
Объявление переменных-стеков в программе может выглядеть так:
Stack <double> s1;
Stack <int, 500> s2;
Stack <long, 1000, Array> s3;
// размер и контейнер по умолчанию
// контейнер по умолчанию
// все задано явно
В третьем случае задается только имя контейнера без аргументов. Вместо шаблона Array разрешается указывать любой контейнер, имеющий два параметра: тип элементов и беззнаковое целое.
Метод-шаблон
В любом классе, как в обычном, так и в шаблоне, можно объявить метод-шаблон. Классшаблон Array (листинг 11.1) не позволяет присваивать массивы разной длины и (или) массивы с разными типами элементов. Этот недостаток можно устранить, если определить в классе Array шаблон
операции присваивания, как показано в листинге 11.3.
Листинг 11.3. Метод-шаблон в классе-шаблоне
#include <cstdlib>
// для size_t
#include <stdexcept>
template <typename T = double, std::size_t N = 10>
// параметры шаблона
class Array
{
public:
// типы и методы (см. листинг 11.1)
template <typename Tl, std::size_t NI>
Array <T, N> &operator=(const Array <TI, NI> &r);
private:
// . . .
T elem[N];
// поле-массив
};
// реализация конструкторов и методов
// Определение метода-шаблона
template <class Т, std::size_t N>
// внешний шаблон
template <class Tl, std::size_t NI>
// внутренний шаблон
Array<T, N> &Array <T, N>::operator=(const Array <TI, NI> &rhs)
{
if ((void *) this != (void *) &rhs)
// проверка самоприсваивания
{
if (N >= NI)
// проверка размера
for(int i = 0; i<NI; ++i)
elem[i] = static_cast <T>(rhs[i]);
// преобразование типа
}
return *this;
}
Прототип метода выглядит следующим образом:
template <typename Tl, std::size_t NI>
Array <T,N> &operator=(const Array <TI, NI> &rhs);
10
Определение метода-шаблона начинается с заголовка template шаблона-класса, а затем задается его собственный заголовок template. Методы-шаблоны не могут быть виртуальными; для обычных методов класса-шаблона такого ограничения нет.
Шаблон операции присваивания не замещает оператор присваивания, генерируемый по умолчанию. Для присвоения массивов одного типа будет вызываться стандартный оператор присваивания. Аналогично, шаблон конструктора копирования никогда не замещает собой генерируемый конструктор.
С помощью шаблона-присваивания выполняется присвоение «меньшего» массива «большему»  как по размеру, так и по типу.
Array <int, 10> mn;
Array <int, 10> mm;
mm = mn;
Array <int, 5> х;
mn = х;
Array<double, 15> у;
// тип и размер отличаются
у = х;
// тип и размер mm = m
// встроенная операция
// размер х < размер mn
// операция-шаблон
// операция-шаблон
Шаблоны и наследование
Шаблон может наследовать от простого класса такой базовый класс называется независимым базовым классом.
class Base { };
template <class T>
class Template: public Base
{
// . . .
};
Это может понадобиться, например, для того, чтобы сделать статические поля общими для
любых классов, инстанцированных из шаблона-наследника.
class CommonVariable
{
public:
static const double Exp;
static const double Pi;
};
const double CommonVariable::Exp = 2.718281828;
const double CommonVariable::Pi = 3.141592653;
template <typename T>
class DeriveTemplate: public CommonVariable
{
// . . .
};
Здесь статические поля Exp и Pi будут общими для всех классов, инстанцированных из шаблона DeriveTemplate.
Шаблон может наследовать от шаблона.
template <typename T1, typename T2>
struct Pair { T1 first; T2 second; };
template <class T>
class Derive: public Pair <T,T>
{ };
// базовый класс-шаблон
// шаблон-наследник
Простой класс также может наследовать от шаблона. Простой класс не предполагает параметров шаблона, наследование выполняется фактически не от шаблона, а от конкретного класса, инстанцированного из шаблона.
template <class Т>
class Base { };
class Derave: public Base<date>
// базовый класс-шаблон
// наследник - простой класс
11
{ };
Последнее используется для отслеживания количества объектов класса, как это сделано в листинге 11.4.
Листинг 11.4. Подсчет объектов класса
template <class Т>
class Count
{
static unsigned int counter;
public:
// функциональность подсчета объектов
Count() { ++counter; }
Count(const Count <T> &t) { ++counter; }
~Count() { --counter; }
static unsigned int getCounter() { return counter; }
};
template <class T>
unsigned int CountedObject <T>::counter = 0;
// наследование от шаблона
class Object01: public Count <Object01>
{
// дополнительная функциональность
}:
class 0bject02: public Count <Object02>
{
// дополнительная функциональность
};
// шаблон класса со счетчиком
// счетчик
// инициализация счетчика
В таком варианте каждый из классов-наследников будет иметь собственную копию счетчика
объектов.
Шаблоны и дружественные отношения
У шаблона могут быть друзья, и шаблоны тоже могут дружить. Дружественная функция вывода для класса-шаблона должна быть такой:
template <typename Т>
class TempiateClass
{
T x;
public:
TemplateClass(const T &t) : x(t) { }
// конструктор инициализации
friend
std: :ostream &operator<< <>(std::ostream &os, const TempiateClass <T> &t);
};
// внешнее определение
template <class T>
std: :ostream &operator<<(std: :ostream &os. const TempiateClass<T>&t)
{ return os << '(' << t.x << ')'; }
Пустые скобки <>, указанные в прототипе после имени функции, свидетельствуют, что дружественная функция является шаблоном. Для дружественных функций-шаблонов не выполняются по
умолчанию преобразования типов аргументов. При определении дружественной функции внутри
класса-шаблона ее аргументы обязаны зависеть от параметров шаблона. Определенные внутри класса-шаблона функции-друзья не являются функциями-шаблонами  это обычные функции, несмотря
на то что аргументы у них зависят от параметра шаблона.
template <class Т>
class Template
{
Т х;
public:
Template(const Т &t) : x(t) { }
12
friend std::ostream &operator<<(std::ostream &os, const Template <T> &t)
{ return os << '(' << t.x << ')'; }
};
6. 2 Шаблоны функций
Помимо определения шаблонов классов, С++ разрешает писать шаблоны функций. Синтаксис
шаблона функции следующий:
<параметры>
заголовок
{ тело }
template
Параметры шаблона функции могут быть такими же, как и параметры шаблона-класса. В
шаблонах функций тоже разрешено использовать параметры, не являющиеся типами; параметр-тип
можно задавать как в качестве типа аргументов функции, так и в качестве типа возвращаемого значения. Например, шаблон функции поиска минимального значения в массиве может быть таким:
template <typename Т>
Т Min(T const *begin, Т const *end)
{
T min = *begin;
while (begin != end) if (min > *(++begin)) min = *begin;
return min;
}
Вызов этой функции-шаблона выглядит как вызов обычной функции:
int а[10]= {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
cout << Min(a, а+10) << endl;
double b[10]= {1.1, 2, 3, 4, 5, 6, 7, 8, 9,10.1};
cout << Min(b, b+10) << endl;
// выбор минимума из массива int
// выбор минимума из массива double
При вызове шаблона-функции не заданы аргументы шаблона в угловых скобках о. Компилятор самостоятельно подставляет тип на основании информации о типе фактического аргумента при
вызове. Для шаблонов классов аргументы нужно задавать всегда явно. Явное указание аргументов
можно делать и при вызове функции-шаблона, например:
cout << Min<int>(a, а+10) << endl;
cout << Min<double>(b, b+10) << endl;
// выбор минимума из массива int
Вывод типов не работает для типа возвращаемого значения  такой аргумент при вызове
функции-шаблона всегда нужно указывать явно. Поэтому, если требуется, чтобы тип возвращаемого
значения тоже был параметром шаблона, следует ставить его на первую позицию списка.
template <class RT, class Т>
RT Multiply(Т const *begin, T const *end)
{
RT total = 1:
while (begin != end)
{
total *= *begin;
++begin;
}
return total;
}
Теперь вызовы нужно писать с явным указанием аргумента для типа возвращаемого значения,
например:
int а[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
cout << Multiply<long>(a, а+10) << endl;
double b[10] = {1.1, 2, 3, 4, 5, 6, 7, 8, 9,10.1};
cout << Multiply<double>(b, b+10) << endl;
Типы параметров функции компилятор выясняет самостоятельно. Попытка вызвать функциюшаблон без аргумента, наподобие
13
cout << Multiply(b, b+10) << endl;
приводит к ошибке трансляции.
Параметры, не являющиеся типами, компилятор обычно не может идентифицировать сам, поэтому их, как правило, надо указывать явно. Например, шаблон функции инициализации числового
массива может быть таким:
template <std::size_t N, class Т, Т t>
void Init(T array[N])
{
for(std::size_t i =0; i<N; ++i)
array[i] = t;
}
// передача массива
Вызов этой функции требует задания всех аргументов шаблона:
int а[100];
Init<100, int, 3>(а);
Перегрузка и специализация шаблонов функций
Допускается перегрузка функций-шаблонов шаблонами и обычными функциями. Как и при
перегрузке обычных функций, шаблоны (и функции) должны отличаться списками параметров. Компилятор при обработке конкретного вызова старается подобрать наиболее подходящий вариант.
Помимо перегрузки, для шаблонов функций реализован механизм полной специализации. Частичная специализация для функций-шаблонов запрещена. Например, функция max() с параметрамиуказателями на символы, шаблон и специализация шаблона с параметрами-указателями выглядят
так:
char *max(const char *х, const char *y)
{ return (strcmp(x, y) > 0) ? x : y; }
template <typename T>
T const &max(T const &x, T const &y)
{ return (x > y) ? x : y; }
template <>
char* const &max(char *const &x, char* const &y)
{ return (strcmp(x, y) > 0) ? x : y; }
// функция
// шаблон
// специализация шаблона
Специализация начинается с ключевого слова template с пустыми угловыми скобками. Вообще говоря, для полной специализации требуется задавать специализирующие аргументы после имени
шаблона:
template<>
// специализация шаблона
char* const &max<char*>(char* const &x, char* const &y)
{ return (strcmp(x, y) > 0) ? x : y; }
Но для шаблонов функций это практически всегда необязательно, поскольку компилятор способен выяснить тип самостоятельно.
Параметры по умолчанию
В шаблонах функций нельзя задавать параметры по умолчанию. Однако это ограничение легко обойти, используя класс-обертку. В листинге 11.5 такая функция является статическим методом
класса.
Листинг 11.5. Класс-обертка шаблона функции
template <class RT = double, class T = float>
class Function
{
public:
static RT Multiply(T const *begin, T const *end)
{
RT total = 1;
while (begin != end)
{
total *= *begin;
14
++begin;
}
return total;
}
};
Вызов функции должен сопровождаться префиксом класса-шаблона.
float f[10] = {1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10.1};
cout << Function<>::Multiply(f, f+10) << endl;
int L[10]= {1, 2, 3, 4, 5,6, 7, 8, 9, 10};
cout << Functional<float, int>: :Multiply(L, L+10) << endl;
6.3 Обобщенные алгоритмы и функторы
Обобщенным алгоритмом называется шаблон функции, который способен выполнять заданную операцию с последовательным контейнером любого вида. Параметрами обобщенного алгоритма,
как правило, являются итераторы входного (и выходного) контейнера.
Примером обобщенного алгоритма, обрабатывающего входной контейнер «по месту», может
служить функция сортировки, которая может иметь следующий прототип
template <class Iterator>
void sort(Iterator first, Iterator last);
Примером алгоритма, перебирающего входной контейнер и записывающего результаты в выходной, может служить алгоритм копирования. Прототип алгоритма примерно следующий:
template <class Inputlterator, class Outputlterator>
Output Iterator copy(
Inputlterator first,
Inputlterator last,
Outputlterator result);
Итераторы с типом Inputlterator определяют интервал обрабатываемых элементов входного
контейнера, а итератор типа Outputlterator является начальным итератором выходного контейнера.
Как правило, выходной контейнер должен существовать к моменту вызова функции и иметь достаточный размер, чтобы вместить все элементы входного контейнера.
Часто параметром алгоритма является функтор. Функтор представляет собой объект, ведущий
себя как функция. Класс, в котором перегружена операция operator() вызова функции, называется
классом-функтором. Объект такого класса называют объектом-функцией, или функтором.
Например, функтор, определяющий нечетность своего аргумента, пишется так:
class Odd
{
public:
int operator()(const int &d)
{ return (d % 2); }
};
Вызов данного функтора выглядит как вызов обычной функции:
Odd f;
f(5);
Функтор, возвращающий булевский результат, называется функтором-предикатом. Функтор
с одним аргументом называется унарным функтором; функтор с двумя аргументами  бинарным.
Класс-функтор является обычным классом, поэтому в нем могут быть определены любые поля, конструкторы, методы. Любые методы, в том числе и перегруженные операции вызова функции
(их может быть несколько), могут быть объявлены виртуальными. Класс-функтор вправе участвовать
в иерархии наследования, вправе запрашивать динамическую память, может быть абстрактным.
Пример класса-функтора с полями и конструктором:
class GreaterEqual
{
int value;
public:
15
GreaterEqual(const int &v): value(v) { }
bool operator()(const int &d)
{ return (d >= value); }
};
При наличии конструктора инициализации вызов функтора можно представить как вызов
конструктора, например:
Greater(3);
Обычно функтор оформляют в виде шаблона класса, например:
template <class Т>
class GreaterEqual
{
Т value;
public:
GreaterEqual(const T &v): value(v) { }
bool operator()(const T &d)
{ return (d >= value);
};
Шаблон класса-функтора  это еще один способ реализации шаблона функции, к которой
разрешается применять все «прелести» шаблонов классов.
Примером обобщенного алгоритма, использующего функтор, может служить алгоритм
foreach() со следующим прототипом:
template <class Iterator, class Function>
void for_each(Iterator first, Iterator last, Function f);
Алгоритм применяет функтор f к каждому элементу контейнера из заданного интервала. Реализация обобщенного алгоритма с функтором показана в листинге 11.6.
Листинг 11.6. Обобщенный алгоритм copy_if() с функтором
template < class Inputlterator,
class Outputlterator,
class Predicate
>
void copy_if( Inputlterator first,
Inputlterator last,
Outputlterator result,
Predicate Functor )
{
for ( ; first != last: ++first)
if (Functor(*first))
// вызов функтора
{ *result = *first; ++result; }
return;
}
- *-
Данный алгоритм копирует элементы, удовлетворяющие предикату Functor, из заданного интервала входного контейнера в выходной. Этот алгоритм с приведенными выше функторами может
пригодиться для следующих задач:
int а[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
int b[10] = { 0 };
copy_if(a, а+10, b, Odd());
Odd f;
copy_if(a, a+10, b, f);
GreaterEqual five(5);
copy_if(a, a+10, b, five);
copy_if(a, a+10, b, GreaterEqual(6));
copy_if(a, a+10, b, GreaterEqual<int>(4));
16
7 Варианты заданий
Далее предлагается реализовать шаблоны классов и шаблоны функций. Для демонстрации работы с шаблонами во всех заданиях требуется написать главную функцию.
1
Создать шаблон класса «стек». Написать программу, использующую этот шаблон класса
для моделирования Т-образного сортировочного узла на железной дороге. Программа
должна разделять на два направления состав, состоящий из вагонов двух типов (на каждое направление формируется состав из вагонов одного типа). Предусмотреть возможность формирования состава из файла и с клавиатуры.
2
Создать шаблон класса «стек».Написать программу, использующую этот шаблон для
отыскания прохода по лабиринту с использованием данного шаблона класса.
Лабиринт представляется в виде матрицы, состоящей из квадратов. Каждый квадрат либо
открыт, либо закрыт. Вход в закрытый квадрат запрещен. Если квадрат открыт, то вход в
него возможен со стороны, но не с угла. Каждый квадрат определяется его координатами
в матрице. После отыскания прохода программа печатает найденный путь в виде координат квадратов.
3
Создать шаблон класса «стек». Написать программу, моделирующую процесс прибытия
и отъезда машин с использованием этого шаблона класса.
Гаражная стоянка имеет одну стояночную полосу, причем въезд и выезд находятся в одном конце полосы. Если владелец автомашины приходит забрать свой автомобиль, который не является ближайшим к выходу, то все автомашины, загораживающие проезд,
удаляются, машина данного владельца выводится со стоянки, а другие машины возвращаются на стоянку в исходном порядке.
Прибытие или отъезд автомашины задается командной строкой, которая содержит признак прибытия или отъезда и номер машины. Программа должна выводить сообщение
при прибытии или выезде любой машины. При выезде автомашины со стоянки сообщение должно содержать число раз, которое машина удалялась со стоянки для обеспечения
выезда других автомобилей.
4
Создать шаблон класса «однонаправленный линейный список». Написать программу,
которая содержит динамическую информацию о наличии автобусов в автобусном парке с
использованием данного шаблона класса.
Сведения о каждом автобусе содержат:
 номер автобуса;
 фамилию и инициалы водителя;
 номер маршрута.
Программа должна обеспечивать:
 начальное формирование данных о всех автобусах в парке в виде списка;
 при выезде каждого автобуса из парка вводится номер автобуса, и программа удаляет
данные об этом автобусе из списка автобусов, находящихся в парке, и записывает эти
данные в список автобусов, находящихся на маршруте;
 при въезде каждого автобуса в парк вводится номер автобуса, и программа удаляет
данные об этом автобусе из списка автобусов, находящихся на маршруте, и записывает эти данные в список автобусов, находящихся в парке;
17
 по запросу выдаются сведения об автобусах, находящихся в парке, или об автобусах,
находящихся на маршруте.
5
Создать шаблон класса «однонаправленный линейный список». Написать программу,
которая содержит текущую информацию о заявках на авиабилеты, с использованием
данного шаблона класса.
Каждая заявка содержит:
 пункт назначения;
 номер рейса;
 фамилию и инициалы пассажира;
 желаемую дату вылета.
Программа должна обеспечивать:
 хранение всех заявок в виде списка;
 добавление заявок в список;
 удаление заявок из списка;
 вывод заявок по заданному номеру рейса и дате вылета;
 вывод всех заявок.
6
Создать шаблон класса «бинарное дерево». Написать программу, которая содержит текущую информацию о книгах в библиотеке, с использованием данного шаблона класса.
Сведения о книгах содержат:
 номер УДК;
 фамилию и инициалы автора;
 название;
 год издания;
 количество экземпляров данной книги в библиотеке.
Программа должна обеспечивать:
 начальное формирование данных о всех книгах в библиотеке в виде двоичного дерева;
 добавление данных о книгах, вновь поступающих в библиотеку;
 удаление данных о списываемых книгах;
 по запросу выдаются сведения о наличии книг в библиотеке, упорядоченные по годам
издания.
7
Создать шаблон класса «бинарное дерево». Написать программу, которая содержит текущую информацию о заявках на авиабилеты, с использованием данного шаблона класса.
Каждая заявка содержит:
18
 пункт назначения;
 номер рейса;
 фамилию и инициалы пассажира;
 желаемую дату вылета.
Программа должна обеспечивать:
 хранение всех заявок в виде двоичного дерева;
 добавление и удаление заявок;
 удаление заявок из списка;
 вывод заявок по заданному номеру рейса и дате вылета с их последующим удалением;
 вывод всех заявок.
8
Создать шаблон класса «бинарное дерево». Использовать его для сортировки целых чисел и строк, задаваемых с клавиатуры или из файла.
9
Создать шаблон класса «очередь». Написать программу, демонстрирующую работу с
этим шаблоном для различных типов параметров шаблона. Программа должна содержать
меню, позволяющее осуществить проверку всех методов шаблона.
10 Создать шаблон класса «очередь с приоритетами». При добавлении элемента в такую
очередь его номер определяется его приоритетом. Написать программу, демонстрирующую работу с этим шаблоном для различных типов параметров шаблона. Программа
должна содержать меню, позволяющее осуществить проверку всех методов шаблона.
11 Создать шаблон класса для работы с комплексными числами, обеспечивающий выполнение операций сложения, вычитания и умножения комплексных чисел. Написать программу, использующую этот шаблон, задавая вещественную и мнимую части как числами типа double, так и целыми числами.
12 Создать шаблон класса одномерных массивов чисел (векторов). Предусмотреть возможность обращения к отдельному элементу массива с контролем выхода за пределы массива, возможность задания произвольных границ индексов при создании объекта и выполнения операций поэлементного сложения и вычитания массивов с одинаковыми границами индексов, умножении и деления всех элементов массива на число, вывода на экран
элемента массива по заданному индексу и всего массива.
Написать программу, использующую созданный шаблон для создания массивов различного типа.
13 Создать шаблон класса, обеспечивающего представление матрицы произвольного размера с возможностью изменения числа строк и столбцов, вывода на экран подматрицы любого размера и всей матрицы.
Написать программу, использующую созданный шаблон для создания матриц различного типа.
14 Описать шаблон класса «множество», позволяющий выполнять основные операции – добавление и удаление элемента, пересечение, объединение и разность множеств.
Написать программу, демонстрирующую работу с этим шаблоном для различных типов
данных.
19
15 Создать шаблон класса «однонаправленный кольцевой список». Написать программу,
которая содержит текущую информацию о книгах в библиотеке, с использованием данного шаблона класса.
Сведения о книгах содержат:
 номер УДК;
 фамилию и инициалы автора;
 название;
 год издания;
 количество экземпляров данной книги в библиотеке.
Программа должна обеспечивать:
 начальное формирование данных о всех книгах в библиотеке в виде однонаправленного кольцевого списка;
 при взятии каждой книги вводится номер УДК, и программа уменьшает значение количество книг на единицу или выдает сообщение о том, что требуемой книги в библиотеке нет, или требуемая книга находится на руках ;
 при возвращении каждой книги вводится номер УДК, и программа увеличивает значение количества книг на единицу;
 по запросу выдаются сведения о наличии книг в библиотеке.
16 Создать шаблон класса «двунаправленный кольцевой список». Написать программу, которая содержит динамическую информацию о наличии автобусов в автобусном парке с
использованием данного шаблона класса.
Сведения о каждом автобусе содержат:
 номер автобуса;
 фамилию и инициалы водителя;
 номер маршрута;
 признак того, где находится автобус – на маршруте или в парке.
Программа должна обеспечивать:
 начальное формирование данных о всех автобусах в парке в виде списка;
 при выезде каждого автобуса из парка вводится номер автобуса, и программа устанавливает значение признака «автобус на маршруте»;
 при въезде каждого автобуса в парк вводится номер автобуса, и программа устанавливает значение признака «автобус в парке»;
 по запросу выдаются сведения об автобусах, находящихся в парке, или об автобусах,
находящихся на маршруте.
17 Создать шаблон класса «однонаправленный линейный список указателей». Использовать
данный шаблон для организации предметного указателя».
20
Каждая компонента указателя содержит слово и номера страниц, на которых это слово
встречается. Количество номеров страниц, относящихся к одному слову, от одного до
десяти.
Составить программу, которая обеспечивает:
 начальное формирование предметного указателя;
 вывод предметного указателя;
 вывод номеров страниц для заданного слова.
Программа должна обеспечивать диалог с помощью меню и контроль ошибок при вводе.
18 Создать шаблон класса «бинарное дерево». Использовать данный шаблон для создания
картотеки абонентов, содержащей сведения о телефонах и их владельцах.
Составить программу, которая:
 обеспечивает начальное формирование картотеки в виде двоичного дерева;
 производит вывод всей картотеки;
 вводит номер телефона и время разговора;
 выводит извещение на оплату телефонного разговора.
Программа должна обеспечивать диалог с помощью меню и контроль ошибок при вводе.
19 Создать шаблон класса «бинарное дерево», содержащее указатели на элементы данных.
Использовать данный шаблон для создания автоматизированной информационной системы на железнодорожном вокзале, которая содержит сведения об отправлении поездов
дальнего следования.
Для каждого поезда указывается:
 номер поезда;
 станция назначения;
 время отправления.
Составить программу, которая:
 обеспечивает первоначальный
 ввод данных в информационную систему и формирование двоичного дерева;
 производит вывод всего дерева;
 вводит номер поезда и выводит все данные об этом поезде;
 вводит название станции назначения и выводит данные о всех поездах, следующих до
этой станции.
Программа должна обеспечивать диалог с помощью меню и контроль ошибок при вводе.
20 Создать шаблон класса «бинарное дерево», обладающее возможностью добавления новых элементов, удаления существующих, поиска элемента по ключу, а также последовательного доступа по всем элементам.
Написать программу, использующую этот шаблон для представления англо-русского
словаря. Программа должна содержать меню, позволяющее осуществить проверку всех
21
методов класса. Предусмотреть возможность формирования словаря из файла и с клавиатуры.
Download