Загрузка...
 
Печать
ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  1.3 Добавляем поддержку связных списков (LinkedList.h)
Программируем 3D-шутер от первого лица (FPS) (Win32, C++, DirectX9)

1.3 Добавляем поддержку связных списков (LinkedList.h)


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

Рис. 1 Сейчас Проект нашего движка выглядит так
Рис. 1 Сейчас Проект нашего движка выглядит так

Сейчас в Проекте нашего движка всего два файла: Engine.h и Engine.cpp, которые мы создали в Главе 1.2 (см. Рис. 1).

Создаём LinkedList.h

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

Закрыть
noteПримечание

Напомним, что директива #include "LinkedList.h" (ссылка на заголовок) уже указана в Engine.h (Проверь!). Таким образом, для того, чтобы воспользоваться функционалом сразу всех заголовочных файлов (в том числе LinkedList.h), достаточно в любом из файлов исходного кода (.cpp) указать всего 1 директиву: #include "Engine.h". Это соответствует концепции единой точки контакта.

ОК, приступаем.

  • Стартуй MSVC++ 2010 и открывай Решение GameProject01 (если не сделал это раньше).
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Заголовочные файлы".
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи "LinkedList.h".
  • Жмём "Добавить".

Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле LinkedList.h набираем следующий код:
LinkedList.h
//-----------------------------------------------------------------------------
// Сборник функций и классов для работы со
// связными списками (LinkedLists).
//
// Original SourceCode:
// Programming a Multiplayer First Person Shooter in DirectX
// Copyright (c) 2004 Vaughan Young
//-----------------------------------------------------------------------------
#ifndef LINKED_LIST_H
#define LINKED_LIST_H

//-----------------------------------------------------------------------------
// Linked List Class
//-----------------------------------------------------------------------------
template< class Type > class LinkedList
{
public:
	//-------------------------------------------------------------------------
	// Element Structure
	//-------------------------------------------------------------------------
	struct Element
	{
		Type *data; //  Указатель на данные, содержащиеся в элементе.
		Element *next; // Указатель на следующий элемент в списке.
		Element *prev; // Указатель на предыдущий элемент в списке.

		//---------------------------------------------------------------------
		// The element structure constructor.
		//---------------------------------------------------------------------
		Element( Type *element )
		{
			data = element;
			next = prev = NULL;
		}

		//---------------------------------------------------------------------
		// The element structure destructor.
		//---------------------------------------------------------------------
		~Element()
		{
			SAFE_DELETE( data );

			if( next )
				next->prev = prev;
			if( prev )
				prev->next = next;
		}
	};

	//-------------------------------------------------------------------------
	// The linked list class constructor.
	//-------------------------------------------------------------------------
	LinkedList()
	{
		m_first = m_last = m_iterate = m_temp = NULL;
		m_totalElements = 0;
	}

	//-------------------------------------------------------------------------
	// The linked list class destructor.
	//-------------------------------------------------------------------------
	~LinkedList()
	{
		Empty();
	}

	//-------------------------------------------------------------------------
	// Добавляет (add) данный элемент в конец списка
	//-------------------------------------------------------------------------
	Type *Add( Type *element )
	{
		if( element == NULL )
			return NULL;

		if( m_first == NULL )
		{
			m_first = new Element( element );
			m_last = m_first;
		}
		else
		{
			m_last->next = new Element( element );
			m_last->next->prev = m_last;
			m_last = m_last->next;
		}

		m_totalElements++;

		return m_last->data;
	}

	//-------------------------------------------------------------------------
	// Вставляет данный элемент в связный список сразу перед nextElement.
	//-------------------------------------------------------------------------
	Type *InsertBefore( Type *element, Element *nextElement )
	{
		m_temp = nextElement->prev;

		m_totalElements++;

		if( m_temp == NULL )
		{
			m_first = new Element( element );
			m_first->next = nextElement;
			nextElement->prev = m_first;

			return m_first->data;
		}
		else
		{
			m_temp->next = new Element( element );
			m_temp->next->prev = m_temp;
			m_temp->next->next = nextElement;
			nextElement->prev = m_temp->next;

			return m_temp->next->data;
		}
	}

	//-------------------------------------------------------------------------
	// Удаляет данный элемент из списка и уничтожает его данные.
	//-------------------------------------------------------------------------
	void Remove( Type **element )
	{
		m_temp = m_first;
		while( m_temp != NULL )
		{
			if( m_temp->data == *element )
			{
				if( m_temp == m_first )
				{
					m_first = m_first->next;
					if( m_first )
						m_first->prev = NULL;
				}
				if( m_temp == m_last )
				{
					m_last = m_last->prev;
					if( m_last )
						m_last->next = NULL;
				}

				SAFE_DELETE( m_temp );

				*element = NULL;

				m_totalElements--;

				return;
			}

			m_temp = m_temp->next;
		}
	}

	//-------------------------------------------------------------------------
	// Убирает (удаляет) все элементы связного списка вместе с их данными
	//-------------------------------------------------------------------------
	void Empty()
	{
		while( m_last != NULL )
		{
			m_temp = m_last;
			m_last = m_last->prev;
			SAFE_DELETE( m_temp );
		}
		m_first = m_last = m_iterate = m_temp = NULL;
		m_totalElements = 0;
	}

	//-------------------------------------------------------------------------
	// Убирает (удаляет) все элементы связного списка вместе с указателями.
	// Внимание: Эта функция не уничтожает данные, содерж. в элементах.
	//-------------------------------------------------------------------------
	void ClearPointers()
	{
		while( m_last != NULL )
		{
			m_temp = m_last;
			m_temp->data = NULL;
			m_last = m_last->prev;
			SAFE_DELETE( m_temp );
		}
		m_first = m_last = m_iterate = m_temp = NULL;
		m_totalElements = 0;
	}

	//-------------------------------------------------------------------------
	// Убирает (удаляет) данный элемент списка и очищает указатель на него.
	// Внимание: Эта функция не уничтожает данные, содерж. в данном элементе.
	//-------------------------------------------------------------------------
	void ClearPointer( Type **element )
	{
		m_temp = m_first;
		while( m_temp != NULL )
		{
			if( m_temp->data == *element )
			{
				if( m_temp == m_first )
				{
					m_first = m_first->next;
					if( m_first )
						m_first->prev = NULL;
				}
				if( m_temp == m_last )
				{
					m_last = m_last->prev;
					if( m_last )
						m_last->next = NULL;
				}

				m_temp->data = NULL;

				SAFE_DELETE( m_temp );

				*element = NULL;

				m_totalElements--;

				return;
			}

			m_temp = m_temp->next;
		}
	}

	//-------------------------------------------------------------------------
	// Итерация (сканирование) каждого элемента в списке
	//-------------------------------------------------------------------------
	Type *Iterate( bool restart = false )
	{
		if( restart )
			m_iterate = NULL;
		else
		{
			if( m_iterate == NULL )
				m_iterate = m_first;
			else
				m_iterate = m_iterate->next;
		}

		if( m_iterate == NULL )
			return NULL;
		else
			return m_iterate->data;
	}

	//-------------------------------------------------------------------------
	// Возвращает элемент связного списка, итерируемый в данный момент.
	//-------------------------------------------------------------------------
	Type *GetCurrent()
	{
		if( m_iterate )
			return m_iterate->data;
		else
			return NULL;
	}

	//-------------------------------------------------------------------------
	// Возвращает первый элемент связного списка.
	//-------------------------------------------------------------------------
	Type *GetFirst()
	{
		if( m_first )
			return m_first->data;
		else
			return NULL;
	}

	//-------------------------------------------------------------------------
	// Возвращает последний элемент связного списка.
	//-------------------------------------------------------------------------
	Type *GetLast()
	{
		if( m_last )
			return m_last->data;
		else
			return NULL;
	}

	//-------------------------------------------------------------------------
	// Возвращает элемент связного списка, следующий за данным.
	//-------------------------------------------------------------------------
	Type *GetNext( Type *element )
	{
		m_temp = m_first;
		while( m_temp != NULL )
		{
			if( m_temp->data == element )
			{
				if( m_temp->next == NULL )
					return NULL;
				else
					return m_temp->next->data;
			}

			m_temp = m_temp->next;
		}

		return NULL;
	}

	//-------------------------------------------------------------------------
	// Возвращает случайный элемент связного списка.
	//-------------------------------------------------------------------------
	Type *GetRandom()
	{
		if( m_totalElements == 0 )
			return NULL;
		else if( m_totalElements == 1 )
			return m_first->data;

		unsigned long element = rand() * m_totalElements / RAND_MAX;

		m_temp = m_first;
		for( unsigned long e = 0; e < element; e++ )
			m_temp = m_temp->next;

		return m_temp->data;
	}

	//-------------------------------------------------------------------------
	// Возвращает полный элемент списка
	//	(включая указатели на предыдущий и след. элементы).
	//-------------------------------------------------------------------------
	Element *GetCompleteElement( Type *element )
	{
		m_temp = m_first;
		while( m_temp != NULL )
		{
			if( m_temp->data == element )
					return m_temp;

			m_temp = m_temp->next;
		}

		return NULL;
	}

	//-------------------------------------------------------------------------
	// Возвращает число элементов в связном списке.
	//-------------------------------------------------------------------------
	unsigned long GetTotalElements()
	{
		return m_totalElements;
	}

private:
	Element *m_first; // Первый элемент связного списка.
	Element *m_last; // Последний элемент связного списка.
	Element *m_iterate; // Используется для итерации связного списка.
	Element *m_temp; // Используется как временное хранилище при различных операциях.

	unsigned long m_totalElements; // Общее количество элементов в связном списке.
};

#endif
// Сборник функций и классов для работы со
// связными списками (LinkedLists).
//
// Original SourceCode:
// Programming a Multiplayer First Person Shooter in DirectX
// Copyright (c) 2004 Vaughan Young
//-----------------------------------------------------------------------------
#ifndef LINKED_LIST_H
#define LINKED_LIST_H

//-----------------------------------------------------------------------------
// Linked List Class
//-----------------------------------------------------------------------------
template< class Type > class LinkedList
{
public:
	//-------------------------------------------------------------------------
	// Element Structure
	//-------------------------------------------------------------------------
	struct Element
	{
		Type *data; //  Указатель на данные, содержащиеся в элементе.
		Element *next; // Указатель на следующий элемент в списке.
		Element *prev; // Указатель на предыдущий элемент в списке.

		//---------------------------------------------------------------------
		// The element structure constructor.
		//---------------------------------------------------------------------
		Element( Type *element )
		{
			data = element;
			next = prev = NULL;
		}

		//---------------------------------------------------------------------
		// The element structure destructor.
		//---------------------------------------------------------------------
		~Element()
		{
			SAFE_DELETE( data );

			if( next )
				next->prev = prev;
			if( prev )
				prev->next = next;
		}
	};

	//-------------------------------------------------------------------------
	// The linked list class constructor.
	//-------------------------------------------------------------------------
	LinkedList()
	{
		m_first = m_last = m_iterate = m_temp = NULL;
		m_totalElements = 0;
	}

	//-------------------------------------------------------------------------
	// The linked list class destructor.
	//-------------------------------------------------------------------------
	~LinkedList()
	{
		Empty();
	}

	//-------------------------------------------------------------------------
	// Добавляет (add) данный элемент в конец списка
	//-------------------------------------------------------------------------
	Type *Add( Type *element )
	{
		if( element == NULL )
			return NULL;

		if( m_first == NULL )
		{
			m_first = new Element( element );
			m_last = m_first;
		}
		else
		{
			m_last->next = new Element( element );
			m_last->next->prev = m_last;
			m_last = m_last->next;
		}

		m_totalElements++;

		return m_last->data;
	}

	//-------------------------------------------------------------------------
	// Вставляет данный элемент в связный список сразу перед nextElement.
	//-------------------------------------------------------------------------
	Type *InsertBefore( Type *element, Element *nextElement )
	{
		m_temp = nextElement->prev;

		m_totalElements++;

		if( m_temp == NULL )
		{
			m_first = new Element( element );
			m_first->next = nextElement;
			nextElement->prev = m_first;

			return m_first->data;
		}
		else
		{
			m_temp->next = new Element( element );
			m_temp->next->prev = m_temp;
			m_temp->next->next = nextElement;
			nextElement->prev = m_temp->next;

			return m_temp->next->data;
		}
	}

	//-------------------------------------------------------------------------
	// Удаляет данный элемент из списка и уничтожает его данные.
	//-------------------------------------------------------------------------
	void Remove( Type **element )
	{
		m_temp = m_first;
		while( m_temp != NULL )
		{
			if( m_temp->data == *element )
			{
				if( m_temp == m_first )
				{
					m_first = m_first->next;
					if( m_first )
						m_first->prev = NULL;
				}
				if( m_temp == m_last )
				{
					m_last = m_last->prev;
					if( m_last )
						m_last->next = NULL;
				}

				SAFE_DELETE( m_temp );

				*element = NULL;

				m_totalElements--;

				return;
			}

			m_temp = m_temp->next;
		}
	}

	//-------------------------------------------------------------------------
	// Убирает (удаляет) все элементы связного списка вместе с их данными
	//-------------------------------------------------------------------------
	void Empty()
	{
		while( m_last != NULL )
		{
			m_temp = m_last;
			m_last = m_last->prev;
			SAFE_DELETE( m_temp );
		}
		m_first = m_last = m_iterate = m_temp = NULL;
		m_totalElements = 0;
	}

	//-------------------------------------------------------------------------
	// Убирает (удаляет) все элементы связного списка вместе с указателями.
	// Внимание: Эта функция не уничтожает данные, содерж. в элементах.
	//-------------------------------------------------------------------------
	void ClearPointers()
	{
		while( m_last != NULL )
		{
			m_temp = m_last;
			m_temp->data = NULL;
			m_last = m_last->prev;
			SAFE_DELETE( m_temp );
		}
		m_first = m_last = m_iterate = m_temp = NULL;
		m_totalElements = 0;
	}

	//-------------------------------------------------------------------------
	// Убирает (удаляет) данный элемент списка и очищает указатель на него.
	// Внимание: Эта функция не уничтожает данные, содерж. в данном элементе.
	//-------------------------------------------------------------------------
	void ClearPointer( Type **element )
	{
		m_temp = m_first;
		while( m_temp != NULL )
		{
			if( m_temp->data == *element )
			{
				if( m_temp == m_first )
				{
					m_first = m_first->next;
					if( m_first )
						m_first->prev = NULL;
				}
				if( m_temp == m_last )
				{
					m_last = m_last->prev;
					if( m_last )
						m_last->next = NULL;
				}

				m_temp->data = NULL;

				SAFE_DELETE( m_temp );

				*element = NULL;

				m_totalElements--;

				return;
			}

			m_temp = m_temp->next;
		}
	}

	//-------------------------------------------------------------------------
	// Итерация (сканирование) каждого элемента в списке
	//-------------------------------------------------------------------------
	Type *Iterate( bool restart = false )
	{
		if( restart )
			m_iterate = NULL;
		else
		{
			if( m_iterate == NULL )
				m_iterate = m_first;
			else
				m_iterate = m_iterate->next;
		}

		if( m_iterate == NULL )
			return NULL;
		else
			return m_iterate->data;
	}

	//-------------------------------------------------------------------------
	// Возвращает элемент связного списка, итерируемый в данный момент.
	//-------------------------------------------------------------------------
	Type *GetCurrent()
	{
		if( m_iterate )
			return m_iterate->data;
		else
			return NULL;
	}

	//-------------------------------------------------------------------------
	// Возвращает первый элемент связного списка.
	//-------------------------------------------------------------------------
	Type *GetFirst()
	{
		if( m_first )
			return m_first->data;
		else
			return NULL;
	}

	//-------------------------------------------------------------------------
	// Возвращает последний элемент связного списка.
	//-------------------------------------------------------------------------
	Type *GetLast()
	{
		if( m_last )
			return m_last->data;
		else
			return NULL;
	}

	//-------------------------------------------------------------------------
	// Возвращает элемент связного списка, следующий за данным.
	//-------------------------------------------------------------------------
	Type *GetNext( Type *element )
	{
		m_temp = m_first;
		while( m_temp != NULL )
		{
			if( m_temp->data == element )
			{
				if( m_temp->next == NULL )
					return NULL;
				else
					return m_temp->next->data;
			}

			m_temp = m_temp->next;
		}

		return NULL;
	}

	//-------------------------------------------------------------------------
	// Возвращает случайный элемент связного списка.
	//-------------------------------------------------------------------------
	Type *GetRandom()
	{
		if( m_totalElements == 0 )
			return NULL;
		else if( m_totalElements == 1 )
			return m_first->data;

		unsigned long element = rand() * m_totalElements / RAND_MAX;

		m_temp = m_first;
		for( unsigned long e = 0; e < element; e++ )
			m_temp = m_temp->next;

		return m_temp->data;
	}

	//-------------------------------------------------------------------------
	// Возвращает полный элемент списка
	//	(включая указатели на предыдущий и след. элементы).
	//-------------------------------------------------------------------------
	Element *GetCompleteElement( Type *element )
	{
		m_temp = m_first;
		while( m_temp != NULL )
		{
			if( m_temp->data == element )
					return m_temp;

			m_temp = m_temp->next;
		}

		return NULL;
	}

	//-------------------------------------------------------------------------
	// Возвращает число элементов в связном списке.
	//-------------------------------------------------------------------------
	unsigned long GetTotalElements()
	{
		return m_totalElements;
	}

private:
	Element *m_first; // First element in the linked list.
	Element *m_last; // Last element in the linked list.
	Element *m_iterate; // Used for iterating the linked list.
	Element *m_temp; // Used for temporary storage in various operations.

	unsigned long m_totalElements; // Total number of elements in the linked list.
};

#endif

  • Сохрани Решение (Файл -> Сохранить все).

Исследуем код LinkedList.h

Самый важный элемент LinkedList.h - это объявление шаблонного класса LinkedList.

Фрагмент файла LinkedList.h
...
//-----------------------------------------------------------------------------
// Linked List Class
//-----------------------------------------------------------------------------
template< class Type > class LinkedList
...

Важно помнить, что этот класс является шаблоном, а значит представленный выше фрагмент кода по большому счёту не является объявлением класса! Это, скорее, некий трафарет или клише для объявления класса. Действительное объявление класса LinkedList не произойдёт до тех пор, пока ты не создашь экземпляр (инстанс) шаблонного класса LinkedList, указав определённый тип данных. Более подробно см. Шаблоны (Templates) функций и классов.

Структура Element

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

Фрагмент файла LinkedList.h
...
public:
	//-------------------------------------------------------------------------
	// Element Structure
	//-------------------------------------------------------------------------
	struct Element
	{
		Type *data; //  Указатель на данные, содержащиеся в элементе.
		Element *next; // Указатель на следующий элемент в списке.
		Element *prev; // Указатель на предыдущий элемент в списке.

		//---------------------------------------------------------------------
		// The element structure constructor.
		//---------------------------------------------------------------------
		Element( Type *element )
		{
			data = element;
			next = prev = NULL;
		}

		//---------------------------------------------------------------------
		// The element structure destructor.
		//---------------------------------------------------------------------
		~Element()
		{
			SAFE_DELETE( data );

			if( next )
				next->prev = prev;
			if( prev )
				prev->next = next;
		}
	};
...

Структура Element расположена внутри шаблонного класса. В её объявлении видим ключевое слово Type, означающее, что тип данных элемента напрямую зависит от типа данных, указанных при создании экземпляра (инстанса) шаблонного класса LinkedList. Таким образом, указатель переменная *data хранит информацию об элементе, используя тип данных, указанный программером при создании экземпляра (инстанса) шаблонного класса LinkedList.

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

Фрагмент файла LinkedList.h
...
		Element *next; // Указатель на следующий элемент в списке.
		Element *prev; // Указатель на предыдущий элемент в списке.
...


Далее следует конструктор структуры Element, который назначает переменную для хранения данных элемента и очищает указатели-ссылки (на предыдущий и следующий элементы). Сам по себе класс LinkedList ответственен за корректное добавление (и назначение ссылок) элемента в связный список.

Рис. 2 Бережное удаление элемента из связного списка
Рис. 2 Бережное удаление элемента из связного списка

В след за конструктором идёт деструктор структуры Element, который специально разработан для:

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

Обрати внимание, как при удалении элемента предыдущий и следующий элементы смыкаются вместе (Рис. 2)

Остальной код

Дальнейший код LinkedList.h рассмотри самостоятельно, изучив комментарии к каждой из функций.

Вот конструктор, который подготавливает связный список, и деструктор, который очищает связный список перед удалением:

Фрагмент файла LinkedList.h
...
	//-------------------------------------------------------------------------
	// The linked list class constructor.
	//-------------------------------------------------------------------------
	LinkedList()
	{
		m_first = m_last = m_iterate = m_temp = NULL;
		m_totalElements = 0;
	}

	//-------------------------------------------------------------------------
	// The linked list class destructor.
	//-------------------------------------------------------------------------
	~LinkedList()
	{
		Empty();
	}
...

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

Чуть ниже в LinkedList.h ты можешь видеть другие функции для:

  • добавления элементов (Add),
  • вставки элементов (InsertBefore),
  • удаления элементов из списка (Remove, Empty),
  • "ручного" удаления данных в каждом элементе,
  • быстрого доступа к любому из элементов связного списка.

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

Также, в классе LinkedList экспонированы две дополнительные функции: ClearPointers и ClearPointer, которые созданы для удаления всех элементов списка или одного элемента соответственно. Что делает эти функции особенными, так это то, что они не уничтожают данные, хранящиеся в элементах. Это пригодится в тех случаях, когда есть связный список указателей, ссылающийся на данные, хранящиеся где-то в другом месте. Таким образом эти функции позволяют уничтожить связный список, не трогая данные.

Закрыть
warningВнимание!

Будь осторожен с функциями ClearPointers и ClearPointer. В случае их применения к связным спискам, действительно хранящим какие-либо данные, возможны утечки памяти (memory leaks).


Несмотря на то обилие возможностей, которое предоставляет класс LinkedList, пользоваться ими очень легко. Основная причина этого заключается в том, что вся подноготная LinkedList.h предельно прозрачна и доступна для понимания.
Чтобы использовать класс LinkedList, тебе необходимо сначала создать на его основе класс с конкретным типом данных (Напомним, что LinkedList - это шаблонный класс!), добавить или удалить элементы и затем уничтожить его. Следующий пример демонстрирует использование класса LinkedList с данными типа float (прочитай и разберись в коде):

Пример использования класса LinkedList
...
LinkedList<float> *list = new LinkedList<float>;

list->Add( new float(5.0f));
List->Add( new float(3.7f));

SAFE_DELETE(list;)
...


Класс LinkedList вовсе не ограничен использованием основных типов данных (например float и int). Он поддерживает любой тип данных, который ты укажешь (или сам создашь). Например, ты можешь создать связный список игроков в игре. Ты увидишь множество примеров использования связных списков по ходу разработки нашего движка. Более того, следующий компонент нашего движка - менеджер ресурсов (resource manager) - сам практически целиком состоит из связных списков.


ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  1.3 Добавляем поддержку связных списков (LinkedList.h)

Contributors to this page: slymentat .
Последнее изменение страницы Вторник 29 / Ноябрь, 2016 13:50:26 MSK автор slymentat.

Последние комментарии

No records to display

Хостинг