| Задача на функции |
Наследование. Шаблоны классов
Шаблоны классов
Шаблон класса позволяет задать класс, параметризованный типом данных. Передача классу различных типов данных в качестве параметра создает семейство родственных классов. Наиболее широкое применение шаблоны находят при создании контейнерных классов. Контейнерным называется класс, который предназначен для хранения каким-либо образом организованных данных и работы с ними. Преимущество использования шаблонов состоит в том, что как только алгоритм работы с данными определен и отлажен, он может применяться к любым типам данных без переписывания кода.
Создание шаблонов классов
Рассмотрим процесс создания шаблона класса на примере двусвязного списка. Поскольку списки часто применяются для организации данных, удобно описать список в виде класса, а так как может потребоваться хранить данные различных типов, этот класс должен быть параметризованным.
Сначала рассмотрим непараметризованную версию класса. Список состоит из узлов, связанных между собой с помощью указателей. Каждый узел хранит целое число, являющееся ключом списка. Опишем вспомогательный класс для представления одного узла списка:
class Node
{
public:
int d; // Данные
Node *next, *prev; //Указатели на предыдущий и последующий узлы
Node(int dat = 0)
{ d = dat; next = 0; prev = 0; } // Конструктор
};Поскольку этот класс будет описан внутри класса, представляющего список, поля для простоты доступа из внешнего класса сделаны доступными ( public ). Это позволяет обойтись без функций доступа и изменения полей. Назовем класс списка List:
class List
{
class Node{ ... };
Node *pbeg, *pend; // Указатели на начало и конец списка
public:
List() { pbeg = 0; pend = 0; } // Конструктор
~List(); // Деструктор
void add(int d); // Добавление узла в конец списка
Node * find(int i); // Поиск узла по ключу
Node * insert(int key, int d); /* Вставка узла d после узла с ключом key */
bool remove(int key); // Удаление узла
void print(); // Печать списка в прямом направлении
void print_back(); // Печать списка в обратном направлении
};Рассмотрим реализацию методов класса. Метод add выделяет память под новый объект типа Node и присоединяет его к списку, обновляя указатели на его начало и конец:
void List::add(int d)
{
Node *pv = new Node(d); // Выделение памяти под новый узел
if (pbeg == 0)pbeg = pend = pv; // Первый узел списка
else
{
pv->prev = pend; // Связывание нового узла с предыдущим
pend->next = pv; pend = pv;
} // Обновление указателя на конец списка
}Метод find выполняет поиск узла с заданным ключом и возвращает указатель на него в случае успешного поиска и 0 в случае отсутствия такого узла в списке:
Node * List::find( int d )
{
Node *pv = pbeg;
while (pv)
{
if(pv->d == d)break;
pv=pv->next;
}
return pv;
}Метод insert вставляет в список узел после узла с ключом key и возвращает указатель на вставленный узел. Если такого узла в списке нет, вставка не выполняется и возвращается значение 0:
Node * List::insert(int key, int d)
{
if(Node *pkey = find(key))
{
// Поиск узла с ключом key
/* Выделение памяти под новый узел и его инициализация */
Node *pv = new Node(d);
/* Установление связи нового узла с последующим */
pv->next = pkey->next;
// Установление связи нового узла с предыдущим
pv->prev = pkey;
// Установление связи предыдущего узла с новым
pkey->next = pv;
if( pkey != pend)
(pv->next)->prev = pv; /* Установление связи последующего узла с новым */
/* Обновление указателя на конец списка, если узел вставляется в конец */
else pend = pv;
return pv;
}
return 0;
}Метод remove удаляет узел с заданным ключом из списка и возвращает значение true в случае успешного удаления и false, если узел с таким ключом в списке не найден:
bool List::remove(int key)
{
if(Node *pkey = find(key))
{
if (pkey == pbeg)
{ // Удаление из начала списка
pbeg = pbeg->next; pbeg->prev = 0;
}
else if (pkey == pend)
{ // Удаление из конца списка
pend = pend->prev; pend->next = 0;
}
else
{ // Удаление из середины списка
(pkey->prev)->next = pkey->next;
(pkey->next)->prev = pkey->prev;
}
delete pkey; return true;}
return false;}Методы печати списка в прямом и обратном направлении поэлементно просматривают список, переходя по соответствующим ссылкам:
void List::print(){
Node *pv = pbeg;
cout << endl << "list: ";
while (pv){
cout << pv->d << ' ';
pv=pv->next;}
cout << endl;
}
void List::print_back(){
Node *pv = pend;
cout << endl << " list back: ";
while (pv){
cout << pv->d << ' ';
pv=pv->prev;}
cout << endl;}Деструктор списка освобождает память из-под всех его элементов:
List::~List(){
if (pbeg != 0){
Node *pv = pbeg;
while (pv)
{pv = pv->next; delete pbeg; pbeg = pv;}
}}Ниже приведен пример программы, использующей класс List. Программа формирует список из 5 чисел, выводит его на экран, добавляет число в список, удаляет число из списка и снова выводит его на экран:
int main()
{
List L;
for (int i = 2; i<6; i++) L.add(i);
L.print(); L.print_back(); L.insert(2,200);
if (!L.remove(5))cout << "not found";
L.print(); L.print_back();}Класс List предназначен для хранения целых чисел. Чтобы хранить в нем данные любого типа, требуется описать этот класс как шаблон и передать тип в качестве параметра.
Синтаксис описания шаблона:
template <описание_параметров_шаблона> class имя { /* определение класса */ };Шаблон класса начинается с ключевого слова template. В угловых скобках записывают параметры шаблона. При использовании шаблона на место этих параметров шаблону передаются аргументы: типы и константы, перечисленные через запятую.
Типы могут быть как стандартными, так и определенными пользователем. Для их описания в списке параметров используется ключевое слово class. В простейшем случае одного параметра это выглядит как <class T>. Здесь T является параметром-типом. Имя параметра может быть любым, но принято начинать его с префикса T. Внутри класса-шаблона параметр может появляться в тех местах, где разрешается указывать конкретный тип, например:
template <class TData> class List
{
class Node{
public:
TData d;
Node *next;
Node *prev;
Node(TData dat = 0){d = dat; next = 0; prev = 0;}
}; ... }Класс TData можно рассматривать как параметр, на место которого при компиляции будет подставлен конкретный тип данных. Получившийся шаблонный класс имеет тип List<TData>.
Методы шаблона класса автоматически становятся шаблонами функций. Если метод описывается вне шаблона, его заголовок должен иметь следующие элементы:
template <описание_параметров_шаблона>
возвр_тип имя_класса <параметры_шаблона >:: имя_функции (список_параметров функции)
Проще рассмотреть синтаксис описания методов шаблона на примере:
template <class Data> void List <Data>::print()
{ /* тело функции */ }- Описание параметров шаблона в заголовке функции должно соответствовать шаблону класса.
- Локальные классы не могут иметь шаблоны в качестве своих элементов.
- Шаблоны методов не могут быть виртуальными.
- Шаблоны классов могут содержать статические элементы, дружественные функции и классы.
- Шаблоны могут быть производными как от шаблонов, так и от обычных классов, а также являться базовыми и для шаблонов, и для обычных классов.
- Внутри шаблона нельзя определять friend -шаблоны.
Если у шаблона несколько параметров, они перечисляются через запятую. Ключевое слово class требуется записывать перед каждым параметром, например:
template <class T1, class T2>
struct Pair { T1 first; T2 second; };Параметрам шаблонного класса можно присваивать значения по умолчанию, они записываются после знака "=". Как и для обычных функций, задавать значения по умолчанию следует, начиная с правых параметров.
Ниже приведено полное описание параметризованного класса двусвязного списка List.
template <class TData> class List
{
class Node
{
public:
TData d;
Node *next, *prev;
Node(TData dat = 0){d = dat; next = 0; prev = 0;}
};
Node *pbeg, *pend;
public:
List(){pbeg = 0; pend = 0;}
~List();
void add(TData d);
Node * find(TData i);
Node * insert(TData key, TData d);
bool remove(TData key);
void print();
void print_back();};
//-------------------------
template <class TData> List <TData>::~List()
{
if (pbeg !=0)
{
Node *pv = pbeg;
while (pv)
{pv = pv->next; delete pbeg; pbeg = pv;}
}
}
//-------------------------
template <class TData> void List <TData>::print()
{
Node *pv = pbeg;
cout << endl << "list: ";
while (pv)
{
cout << pv->d << ' ';
pv = pv->next;
}
cout << endl;
}
//-------------------------
template <class TData> void List <TData>::print_back()
{
Node *pv = pend;
cout << endl << " list back: ";
while (pv)
{
cout << pv->d << ' ';
pv = pv->prev;
}
cout << endl;
}
//-------------------------
template <class TData> void List <TData>::add(TData d)
{
Node *pv = new Node(d);
if (pbeg == 0)pbeg = pend = pv;
else
{
pv->prev = pend;
pend->next = pv;
pend = pv;
}
}
//-------------------------
template <class TData> Node * List <TData>::find( TData d)
{
Node *pv = pbeg;
while (pv)
{
if(pv->d == d)break;
pv = pv->next;
}
return pv;
}
//-------------------------
template <class TData> Node * List <TData>::insert(TData key, TData d)
{
if(Node *pkey = find(key))
{
Node *pv = new Node(d);
pv->next = pkey->next;
pv->prev = pkey;
pkey->next = pv;
if( pkey != pend)(pv->next)->prev = pv;
else pend = pv;
return pv;
}
return 0;
}
//-------------------------
template <class TData> bool List <TData>::remove(TData key)
{
if(Node *pkey = find(key))
{
if (pkey == pbeg)
{
pbeg = pbeg->next; pbeg->prev = 0;
}
else if (pkey == pend)
{
pend = pend->prev; pend->next = 0;
}
else
{
(pkey->prev)->next = pkey->next;
(pkey->next)->prev = pkey->prev;
}
delete pkey; return true;
}
return false;
}Если требуется использовать шаблон List для хранения данных не встроенного, а определенного пользователем типа, в его описание необходимо добавить перегрузку операции вывода в поток и сравнения на равенство, а если для его полей используется динамическое выделение памяти, то и операцию присваивания.
При определении синтаксиса шаблона было сказано, что в него, кроме типов, могут передаваться константы. Соответствующим параметром шаблона может быть:
- переменная целого, символьного, булевского или перечислимого типа;
- указатель на объект или указатель на функцию;
- ссылка на объект или ссылка на функцию;
- указатель на элемент класса.
В теле шаблона такие параметры могут применяться в любом месте, где допустимо использовать константное выражение.
В качестве примера создадим шаблон класса, содержащего блок памяти определенной длины и типа:
template <class Type, int kol> class Block
{
public:
Block(){p = new Type [kol];}
~Block(){delete [] p;}
operator Type *();
protected:
Type * p;
};
template <class Type, int kol>
Block <Type, kol>:: operator Type *()
{
return p;
}У класса-шаблона могут быть друзья, и шаблоны тоже могут быть друзьями. Класс может быть объявлен внутри шаблона, а шаблон - внутри как класса, так и шаблона. Единственным ограничением является то, что шаблонный класс нельзя объявлять внутри функции. В любом классе, как в обычном, так и в шаблоне, можно объявить метод-шаблон. После создания и отладки шаблоны классов удобно помещать в заголовочные файлы.