Опубликован: 25.08.2010 | Уровень: для всех | Доступ: свободно
Лекция 6:

Наследование. Шаблоны классов

< Лекция 5 || Лекция 6: 1234

Шаблоны классов

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

Создание шаблонов классов

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

Сначала рассмотрим непараметризованную версию класса. Список состоит из узлов, связанных между собой с помощью указателей. Каждый узел хранит целое число, являющееся ключом списка. Опишем вспомогательный класс для представления одного узла списка:

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;
}

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

< Лекция 5 || Лекция 6: 1234
Dana Kanatkyzi
Dana Kanatkyzi
Здравствуйте.Помогите решить задачу минимум 4 чисел.Условие такое:"Напишите функцию int min (int a, int b, int c, int d) (C/C++)"находящую наименьшее из четырех данных чисел."Заранее спасибо!