Реализация с помощью классов динамических структур данных

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

Опишем вспомогательный класс для представления одного узла списка:

class Node{

public:

int d; // Данные

Node *next; // Указатель на последующий узел

Node *prev; // Указатель на предыдущий узел

Node (int dat = 0){ // Конструктор

d = dat; next = 0; prev = 0;

}

};

Этот класс будет описан внутри класса, представляющего список (класс List), поэтому для простоты доступа из внешнего класса поля сделаны доступными (public). Это позволяет обойтись без функций доступа и изменения полей.

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; } // Обновление указателя на конец списка

}

Можно получить отсортированный список, заменив данный метод на метод, аналогичный функции формирования отсортированного списка add_sort (лекция «Линейные списки»).

Метод 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 = рkеу;

// Установление связи предыдущего узла с новым:

pkey->next = pv;

// Установление связи последующего узла с новым:

if ( рkеу != 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 << "1ist: ";

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

}

}

В функции main, используется класс 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(); }

Тема 2.11

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

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

Наиболее широкое применение шаблоны находят при создании контейнерных классов. Такие классы предназначены для хранения каким-либо образом организованных данных и работы с ними. Стандартная библиотека C++ содержит множество контейнерных классов для организации структур данных различного вида.

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

Пример:

Определить в виде класса параметризованного класса двухсвязный список (список может потребоваться хранить данные различных типов).

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

Синтаксис описания шаблона:

template <описание_параметров_шаблона> определение_класса;

Параметры шаблона перечисляются через запятую. В качестве параметров могут использоваться типы, шаблоны и переменные. Типы могут быть стандартными или определенными пользователем. Для их описания используется ключевое слово class. Внутри шаблона параметр типа может применяться в любом месте, где допустимо использовать спецификацию типа:

template <class Data> class List{

class Node{

public:

Data d;

Node *next;

Node *prev;

Node (Data dat = 0){d = dat; next = 0; prev = 0;}

};

}

Класс Data является формальным параметром. На его место при компиляции будет подставлен конкретный тип данных.

Для любых параметров шаблона могут задаваться значения по умолчанию:

template <class Т> class myarray {/*..*/};

template <class K, class V, template <class T> class С = myarray>

class Map{

C<K> key;

C<V> value;

};

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

template <class Т, Т* p, class U = Т> class X { /* ... */ };

Методы шаблона класса автоматически становятся шаблонами функций. Если метод описывается вне шаблона, его заголовок должен иметь следующие элементы:

template <описание_параметров_шаблона>

возвр_тип имя_класса <параметры_шаблона>::

имя_функции (список_параметров функции)

Описание параметров шаблона в заголовке функции должно соответствовать шаблону класса. При этом имена параметров могут не совпадать.

template <class Data> void List<Data>::print()

{ / * тело функции */ }

<class Data> - описание параметра шаблона, void – тип возвращаемого функцией значения, List – имя класса, <Data> - параметр шаблона, print — имя функции без параметров.

В случае нескольких параметров порядок их следования в списках <описание_параметров_шаблона> и <параметры_шаблона> должен совпадать:

template<class T1, class T2> struct A{

void f1 ( );

};

template <class T2, class T1> void A<T2, T1>::f1() { ... }

Правила описания шаблонов:

· Локальные классы не могут содержать шаблоны в качестве элементов,

· Шаблоны методов не могут быть виртуальными,

· Шаблоны классов могут содержать статические элементы, дружественные функции и классы,

· Шаблоны могут быть производными от шаблонов и от обычных классов, а также являться базовыми для шаблонов и обычных классов,

· Внутри шаблона нельзя определять friend-шаблоны.

Пример: полное описание параметризованного класса двусвязного списка List.

template <class Data> class List{

class Node{

public:

Data d;

Node *next, *prev;

Node (Data dat = 0){d = dat; next = 0; prev = 0;}

};

Node *pbeg, *pend;

public:

List( ){pbeg = 0; pend = 0;}

~List ( );

void add(Data d);

Node * find(Data i);

Node * insert(Data key, Data d);

bool remove(Data key);

void print();

void print_back();

};

//--------------------------

template <class Data>

List <Data>::~List(){

if (pbeg !=0){

Node *pv = pbeg;

while (pv){

pv = pv->next;

delete pbeg;

pbeg = pv;

}

}

}

//-------------------------

template <class Data>

void List <Data>::print( ){

Node *pv = pbeg;

cout << endl << "list: ";

while (pv){

cout << pv->d << ' ';

pv = pv->next;

}

cout << endl;

}

//---------------------------------

template <class Data>

void List <Data>::print_back(){

Node *pv = pend;

cout << endl << " list back: ";

while (pv){

cout << pv->d << ' ';

pv = pv->prev;

}

cout << endl;

}

//--------------------------

template <class Data>

void List <Data>::add(Data d){

Node *pv = new Node(d);

if (pbeg == 0) pbeg = pend = pv;

else{

pv->prev = pend;

pend->next = pv;

pend = pv;

}

}

//------------------------------

template <class Data>

Node * List <Data>::find(Data d){

Node *pv = pbeg;

while (pv){

if(pv->d == d) break;

pv = pv->next;

}

return pv;

}

//-----------------------------------

template <class Data>

Node * List <Data>::insert(Data key, Data d){

if(Node *pkey = find(key)){

Node *pv = new Node(d);

pv->next = pkey->next;

pv->prev = рkеу;

pkey->next = pv;

if ( рkеу != pend) (pv->next)->prev = pv;

else pend = pv;

return pv;

}

return 0;

}

// ---------------------------------

template <class Data>

bool List <Data>::remove(Data 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;

}

После создания и отладки шаблоны классов удобно помещать в заголовочные файлы.

Наши рекомендации