
- •Структуры и алгоритмы обработки данных: план курса
- •Лабораторные работы
- •Литература
- •Ста: Лекция №1 - Введение. Данные в памяти программ
- •Введение
- •Модель памяти в прикладных программах
- •Int main ()
- •Int main ()
- •Int main ()
- •Int main ()
- •Char buf[ 2000000 ];
- •Return f(); // бесконечно долго вызываем сами себя // вызываем переполнение стека через время
- •Int main ()
- •Return f();
- •Int main ()
- •Delete[] p; // Освобождаем память
- •Int main ()
- •Int a[ n ]; // ошибка, размер нельзя вычислить во время компиляции
- •Delete[] p;
- •Int main ()
- •Проблема фиксированного размера массивов
- •Int main ()
- •Int main ()
- •Int main ()
- •Динамически растущие массивы (векторы)
- •Int main ()
- •Struct IntegerVector
- •Int * m_pData;
- •Int m_nUsed;
- •Int m_nAllocated;
- •Void IntegerVectorDestroy ( IntegerVector & _vector )
- •Int main ()
- •#Ifndef _integer_vector_hpp_
- •#Include "integer_vector.Hpp"
- •Void IntegerVectorRead ( IntegerVector & _vector, std::istream & _stream );
- •Ста: Лекция №2 - Связные списки.
- •Всегда ли хорош вектор?
- •Связные списки
- •Ста: Лекция №3 - Реализация и использование простейших атд
- •Абстрактные типы данных (атд)
- •Атд “Список” ( “Последовательность” )
- •Атд “Стек”
- •Атд “Очередь”
- •Ста: Лекция №7 - Деревья
- •Основные сведения о деревьях
- •Обход деревьев
- •Атд “Дерево”
- •Типичные структуры данных для n-арных деревьев
- •1. Массив меток и массив родительских индексов.
- •2. Массив меток и заголовок со списками дочерних узлов.
- •3. Динамическая структура с указателями
- •В результате ее выполнения в динамической памяти формируется структура объектов, в существенной степени напоминающая оригинальный пример из описания понятия деревьев:
- •Бинарные деревья
- •Глава 3 “Элементарные структуры данных”
- •Глава 4 “Абстрактные типы данных”
- •Глава 10 “Элементарные структуры данных” (подразделы 10.1-10.3)
- •Глава 2 “Основные абстрактные типы данных” (подразделы 2.1-2.4)
- •Глава 6 “Элементарные методы сортировки”.
- •Глава 5 “Рекурсия и деревья”.
Struct IntegerVector
{
Int * m_pData;
Int m_nUsed;
Int m_nAllocated;
};
Следует отметить, что все переменные, входящие в состав структуры получили префикс “m_”, что означает членство (membership). Это не является обязательным требованием языка С++, а всего лишь рекомендуемой многими профессиональными программистами стилистической конвенцией. Такие идентификаторы легко отличить от других видов переменных благодаря префиксу, что повышает читабельность кода.
Типичные манипуляции со структурой-вектором следует выделить в отдельные функции с интуитивно понятным интерфейсом. Во-первых, необходима функция инициализации вектора, т.е., приведения его в начальное пригодное к использованию состояние:
void IntegerVectorInit ( IntegerVector & _vector, int _allocatedSize = 10 )
{
_vector.m_pData = new int[ _allocatedSize ];
_vector.m_nAllocated = _allocatedSize;
_vector.m_nUsed = 0;
}
Во-вторых, вектор должен освобождать выделенные им ресурсы памяти:
Void IntegerVectorDestroy ( IntegerVector & _vector )
{
delete[] _vector.m_pData;
}
В-третьих, необходима функция для добавления очередного числа в конец вектора, которая автоматически увеличивает размер вектора в случае необходимости:
void IntegerVectorPushBack ( IntegerVector & _vector, int _data )
{
// Закончилось ли место в векторе?
if ( _vector.m_nUsed == _vector.m_nAllocated )
{
// Выделяем новый блок, в 2 раза больше прежнего
int nAllocatedNew = _vector.m_nAllocated * 2;
int * pNewData = new int[ nAllocatedNew ];
// Копируем данные из прежнего блока в новый
memcpy(
pNewData,
_vector.m_pData,
sizeof( int ) * _vector.m_nAllocated
);
// Освобождаем старый блок и подменяем адрес на новый блок
delete[] _vector.m_pData;
_vector.m_pData = pNewData;
// Обновляем количество выделенных ячеек
_vector.m_nAllocated = nAllocatedNew;
}
// Записываем данное в очередную ячейку
_vector.m_pData[ _vector.m_nUsed++ ] = _data;
}
Аргументы функций получили префикс в виде символа “_”, что также является одной из популярных стилистических конвенций.
Имея под рукой такие вспомогательные функции, выделенные из предыдущей версии программы, преобразуем основную программу в простую и очень читабельную:
Int main ()
{
// Приглашение пользователя ко вводу
std::cout << "Input integers, stop with Ctrl+Z: ";
// Создаем вектор и инициализируем его одним вызовом
IntegerVector v;
IntegerVectorInit( v );
// Пытаемся вводить числа с консоли одно за другим
while ( true )
{
// Попытка ввода числа
int temp;
std::cin >> temp;
if ( std::cin )
// Просто просим вектор добавить новое данное в конец
IntegerVectorPushBack( v, temp );
else
// Конец ввода
break;
}
// Количество введенных данных сохранено в переменной m_nUsed в векторе
// Выводим данные в обратном порядке
for ( int i = v.m_nUsed - 1; i >= 0; i-- )
std::cout << v.m_pData[ i ] << ' ';
std::cout << std::endl;
// Просим вектор освободить выделенные им ресурсы
IntegerVectorDestroy( v );
}
Как видим, все сложные манипуляции со структурой данных были полностью скрыты внутри обеспечивающих функций, а тело функции main теперь сосредоточено на решении задачи.
Повторное использование решения
Предположим, возникла более сложная задача. Пользователь вводит последовательность целых чисел произвольной длины, программа создает последовательность частичных сумм и выводит ее на экран. Если на вход подана последовательность “1 2 3 4 5”, программа должна выдавать последовательность “1 3 6 10 15”, где каждое следующее число формируется как сумма текущего числа и всех предыдущих обработанных.
Чтобы не реализовывать логику работы с динамически растущим массивом целых чисел снова и снова, сначала следует применить еще один этап рефакторинга, выделив логику структуры данных в отдельные файлы исходного кода. Следует сформировать заголовочный файл с описанием структуры и прототипами функций, а тела функций разместить в отдельный файл. Заголовочный файл следует включать во всех местах использования данной функциональности.
integer_vector.hpp