
- •Структуры и алгоритмы обработки данных: план курса
- •Лабораторные работы
- •Литература
- •Ста: Лекция №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 “Рекурсия и деревья”.
Int main ()
{
// Приглашение пользователя ко вводу
std::cout << "Input integers, stop with Ctrl+Z: ";
// Инициализация динамически растущего массива:
// - выделение 10 начальных ячеек
// - счетчик занятых ячеек приравнивается к 0
// - счетчик выделенных ячеек равен 10
const int INITIAL_ALLOCATION_SIZE = 10;
int * pData = new int[ INITIAL_ALLOCATION_SIZE ];
int nUsed = 0, nAllocated = INITIAL_ALLOCATION_SIZE;
// Пытаемся вводить числа с консоли одно за другим
while ( true )
{
// Попытка ввода числа
int temp;
std::cin >> temp;
if ( std::cin )
{
// Проверяем достаточно ли места в массиве
if ( nUsed == nAllocated )
{
// Места недостаточно, необходимо расширить массив.
// Выделяем новый блок вдвое больше существующего
int nAllocatedNew = nAllocated * 2;
int * pNewData = new int[ nAllocatedNew ];
// Переносим данные и существующего блока в новый
memcpy( pNewData, pData, sizeof( int ) * nAllocated );
// Освобождаем существующий блок и подменяем его адрес
delete[] pData;
pData = pNewData;
// Обновляем количество выделенных ячеек на новое
nAllocated = nAllocatedNew;
}
// Записываем данные в массив, точно зная, что место есть
pData[ nUsed++ ] = temp;
}
else
// Конец ввода
break;
}
// Количество введенных данных сохранено в переменной nUsed
// Выводим данные в обратном порядке
for ( int i = nUsed - 1; i >= 0; i-- )
std::cout << pData[ i ] << ' ';
std::cout << std::endl;
// Освобождаем последний выделенный блок данных
delete[] pData;
}
Новая версия программы заметно сложнее, тем не менее, она справляется с любым реальным объемом входных данных, при этом корректно завершается и рационально расходует память. Для 51 числа процедура расширения выполнится 3 раза (10->20, 20->40, 40->80), в последнем блоке останется 29 свободных ячеек.
Рефакторинг решения
Предложенное выше решение задачи удовлетворило выдвинутым требованиям, однако количество и сложность внесенного программного когда, необходимого для поддержания структуры динамически растущего массива, заметно превысили размеры самой задачи. Фактически, разглядеть контуры решения основной изначальной задачи в таком громоздком коде стало затруднительно. Желательно отделять код обеспечения структур данных от кода основной задачи для улучшения восприятия при чтении. Важен ли данный фактор? Несомненно, ведь профессиональный программист в реальных проектах читает существующий код в 5-10 раз чаще, чем пишет новый. а значит чтение должно даваться легко.
Еще одной побудительной причиной отделения может стать необходимость повторного использования структуры данных в других задачах. Ведь необходимость хранения последовательности чисел заранее неизвестной длины - типичная потребность для многих программ. Применим методы РЕФАКТОРИНГА - процесса изменения исходного кода программы с целью улучшения его внутренней структуры без влияния на внешне наблюдаемую функциональность.
Данные динамически растущего массива следует выделить в отдельную структуру, поскольку вместе они образуют целостное понятие и всегда будут существовать рядом друг с другом: