Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP_otvety_k_ekzamenu.doc
Скачиваний:
55
Добавлен:
13.04.2015
Размер:
786.94 Кб
Скачать

46. Понятие обобщенной концепции. Статический полиморфизм по сравнению с динамическим полиморфизмом.

Рассмотрим простейший обобщенный алгоритм поиска элемента в массиве:

#include <cassert>

template< typename T >

const T * MyFind ( const T * _pArray, int _nElements, const T & _value )

{

for ( int i = 0; i < _nElements; i++ )

if ( _pArray[ i ] == _value )

return _pArray + i;

return nullptr;

}

int main ()

{

int data[ 5 ] = { 1, 2, 3, 4, 5 };

const int * pResult = MyFind( data, 5, 3 );

assert( pResult && ( ( pResult - data ) == 2 ) );

}

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

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

template< typename T >

class List

{

public:

struct Node

{

T m_value;

Node * m_pNextNode;

Node ( const T& _value )

: m_value( _value ),

m_pNextNode( nullptr )

{

}

};

private:

Node * m_pFirstNode, * m_pLastNode;

List ( const List< T >& );

List< T > & operator = ( const List< T > & );

public:

List ()

{

m_pFirstNode = m_pLastNode = nullptr;

}

~List ()

{

Node * pCurrent = m_pFirstNode;

while ( pCurrent )

{

Node * pTemp = pCurrent->m_pNextNode;

delete pCurrent;

pCurrent = pTemp;

}

}

void push_back ( const T & _value )

{

Node * pNewNode = new Node( _value );

if ( m_pLastNode )

{

m_pLastNode->m_pNextNode = pNewNode;

m_pLastNode = pNewNode;

}

else

m_pFirstNode = m_pLastNode = pNewNode;

}

const Node * first_node () const

{

return m_pFirstNode;

}

};

template< typename T >

const typename List< T >::Node *

MyFind ( const List< T > & _list, const T & _value )

{

const List< T >::Node* pCurrentNode = _list.first_node();

while ( pCurrentNode )

{

if ( pCurrentNode->m_value == _value )

return pCurrentNode;

pCurrentNode = pCurrentNode->m_pNextNode;

}

return nullptr;

}

int main ()

{

List < int > myList;

myList.push_back( 1 );

myList.push_back( 2);

myList.push_back( 3 );

myList.push_back( 4 );

myList.push_back( 5 );

const List< int >::Node * pNode = MyFind( myList, 3 );

assert( pNode && pNode->m_value == 3 );

}

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

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

template< typename T >

const T * MyFind ( const T * _pArrayFirst,

const T * _pArrayLast,

const T & _value )

{

const T * pCurrent = _pArrayFirst;

while ( pCurrent != _pArrayLast )

{

if ( * pCurrent == _value )

return pCurrent;

}

return _pArrayLast;

}

Тестовый код также меняется незначительно:

int main ()

{

int data[ 5 ] = { 1, 2, 3, 4, 5 };

const int * pResult = MyFind( data, data + 5, 3 );

assert( pResult && ( ( pResult - data ) == 2 ) );

}

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

Пара передаваемых указателей задает интервал последовательности, которую необходимо обработать. Когда эти указатели корректны и адресуют существующий цельный набор значений в памяти, такой интервал называют ДЕЙСТВИТЕЛЬНЫМ, в противном случае интервал был бы НЕДЕЙСТВИТЕЛЬНЫМ.

На втором шаге преобразовываем код в форму, избегающую явного использования указателей. Для этой цели вводим дополнительный уровень косвенности - специальный аргумент шаблона функции, скрывающий указатели. При этом требуем от данного аргумента шаблона наличия операций сравнения со значением такого же типа (==, !=), присвоения (=), операции префиксного инкремента (++X), означающего переход к следующему элементу, а также операцию разыменования ( * x ), выбирающую адресуемое в данный момент значение. Во всех прежних контекстах использования указателя на аргумент-тип используем новый аргумент шаблона.

template< typename It, typename T >

It MyFind ( It _first, It _last, const T & _value )

{

It current = _first;

while ( current != _last )

{

if ( * current == _value )

return current;

++ current;

}

return _last;

}

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

Такой обобщенный аргумент, скрывающий указатель на обрабатываемые данные, называется ИТЕРАТОРОМ. Итератор представляет собой пример понятия ОБОБЩЕННОЙ КОНЦЕПЦИИ (generic concept) - именованного набора операций, которые должен поддерживать тип-аргумент для взаимодействия с тем или иным алгоритмом. В случае итератора, речь идет о наборе операций, необходимых для обеспечения последовательного доступа алгоритма к данным, хранящимся в структуре любой сложности. Если в качестве типа для инстанцирования в качестве итератора будет предоставлено нечто, что соответствует требованиям обобщенной концепции итератора, то такой тип и будет являться итератором (принцип “если это похоже на черепаху, движется как черепаха, значит это черепаха”).

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

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

template< typename T >

class List

{

public:

// ...

struct Iterator

{

Node * m_pCurrentNode;

Iterator ( Node * _pCurrentNode )

: m_pCurrentNode( _pCurrentNode )

{

}

bool operator == ( const Iterator & _it ) const

{

return m_pCurrentNode == _it.m_pCurrentNode;

}

bool operator != ( const Iterator & _it ) const

{

return !( * this == _it );

}

Iterator & operator ++ ()

{

assert( m_pCurrentNode );

m_pCurrentNode = m_pCurrentNode->m_pNextNode;

return * this;

}

const T& operator * () const

{

assert( m_pCurrentNode );

return m_pCurrentNode->m_value;

}

};

Iterator begin ()

{

return Iterator( m_pFirstNode );

}

Iterator end ()

{

return Iterator( nullptr );

}

// ...

};

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

const List< int >::Iterator it = MyFind( myList.begin(), myList.end(), 3 );

assert( * it == 3 );

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]