Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
DSD_Spiskovye_struktury_dannykh_na_baze_massivo...doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.59 Mб
Скачать

1.1.11Продолжение. Динамические массивы нетипизированных указателей

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

Возможным альтернативным решением является хранение служебной информации о массиве указателей в самом массиве. Предпосылками к этому является тот факт, что массив, отводимый под указатели, является динамическим, т.е. он выделяется программе уже во время ее работы. Фактически же выделяется не массив указателей, а соответствующий блок байт. А уже затем указатель на этот блок, по мере необходимости, типизируется (маскируется) соответствующим образом. Но тогда маски (типы) могут быть разными. Т.е. по мере необходимости указатель на один и тот же динамический массив может маскироваться (типизироваться) разными типами данных. В частности, такая техника работы с динамическим массивом позволяет по-разному рассматривать одни и те же его элементы. Например, один и тот же массив в C++ можно рассматривать как массив чисел типа int или массив указателей int*. Или иначе, часть массива можно рассматривать, как массив чисел типа integer, а другую часть массива можно рассматривать как массив указателей типа int*.

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

индекс

M-1

Незанятая часть массива

N+2

Указатель N ->

N+1



Указатель 2 ->

+3

Указатель 1 ->

+2

Количество элементов в списке N

+1

PtrBase ->

Размер массива М

0

Рисунок 1.17 – Структура массива указателей, содержащего служебную информацию

Следует подчеркнуть, что приводимое ниже решение основано на том, что элементы служебной информации и указатели имеют один и тот же размер – 4-ре байта. Более общее решение, когда элементы служебной информации и указатели имеют разный размер, рассматривается далее.

Для доступа к первой части массива вводится дополнительный тип для указателя int* – указатель на int.

Тогда, получение размера массива будет выглядеть таким образом:

int SizeAr = ((int*)PtrBase)[0]

Поскольку после операции приведения типа указатель PtrBase воспринимается как указатель на число типа int, то индексация указателя нулем означает задачу – взять значение типа int, записанное по адресу PtrBase.

Для доступа ко второй части массива используется тип для указателя void** – указатель на указатель void*.

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

void* PFirstEl = ((void**)PtrBase)[2]

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

Для наглядности индексации элементов массива, отведенных для хранения общего размера массива и фактического количества элементов списка, вводятся соответственно переменные PosSize и PosCnt. В действительности это элементы массива с индексами 0 и 1.

Основные типы и переменные имеют следующий вид:

const SizeArrH = 4; /*начальный размер массива указателей*/

const Delta = 3; /*приращение размера массива указателей*/

const PosCnt = 1; /*индекс служебного элемента в массиве,

содержащего количество элементов списка*/

const PosSize = 0; /*индекс служебного элемента в массиве,

содержащего текущий размер массива*/

typedef struct ElmList

{

char Name[30];

int Age;

char Address[30];

} TElmList;

// ---- Объявление переменных ------- //

void* PtrDynArrPtr ; /*указатель на начало массива указателей*/

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

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

//------- расширение массива указателей ----- //

void ExpendArrPtr (void* &PtrArPtr)

/*увеличение размера массива указателей*/

{

int SizeW;

void* PtrArPtrW; /*вспомогательный бестиповый указатель на начало

массива указателей*/

SizeW = ((int*)PtrArPtr)[PosSize]; /*сохранение старого размера массива*/

PtrArPtrW = PtrArPtr; /*сохранение указателя на старый массив*/

/*выделение места для нового массива*/

PtrArPtr = new void*[SizeW+Delta+2];

/*копирование старого массива указателей в новый массив*/

for (int J = 2; J <= ((int*)PtrArPtrW)[PosCnt] + 1; J++)

{

((void**) PtrArPtr )[J] = ((void**)PtrArPtrW )[J];

}

/*Перенос значения количества элементов из старого массива в но-

вый*/

((int*)PtrArPtr) [PosCnt] = ((int*)PtrArPtrW)[PosCnt];

((int*)PtrArPtr)[PosSize] = SizeW+Delta+2;/*Запись в новый массив размера

массива, который увеличен на величину Delta, 2 – количество служебных элементов*/

delete [ ] PtrArPtrW; /*удаление старого массива*/

} /*ExpendArrPtr*/

//------------ Быстрый поиск -------------------//

void FindElList2(void* PtrArrPtr, char *Key, int &PosFndEl, bool &FindOK)

/*Поиск выполняется методом половинного деления*/

/*процедура имеет двойное назначение - поиск месторасположения искомого элемента и поиск места вставки добавляемого элемента*/

{

int Middl; /*Middl выступает как средняя граница обрабатываемой части

массива*/

int Hi, Low; /*текущие границы - верхняя, нижняя*/

FindOK = false; /*элемент не найден*/

if (((int*)PtrArrPtr)[PosCnt] == 0)

/*Если список пуст, то позиция вставки нового элемента – начало

массива указателей – индекс 2*/

{

PosFndEl = 2;

return;

}

Low = 2; /*установка начальных значений для верхней и нижней границы

диапазона поиска*/

Hi = ((int*)PtrArrPtr)[PosCnt] + 1;

do

{

/*Вычисление среднего индекса в диапазоне поиска*/

Middl = (Hi - Low) / 2 + Low; /* Middl = (Hi + Low) / 2 */

/*Сравнение ключа и поля Name среднего элемента в диапазоне поиска*/

switch (strcmp(((TElmList*)(((void**)PtrArrPtr)[Middl])) ->Name,Key))

{

case 0: PosFndEl = Middl; /*Элемент найден*/

FindOK = true; /*элемент найден*/

return;

case 1: Hi = Middl - 1; /*Если средний элемент >Key, то граница поиска сужается ниже среднего*/

break;

case -1:Low = Middl + 1; /*Если средний элемент >Key, то грани-

ца поиска сужается ниже среднего*/

break;

}

} while (Low <= Hi); /*Поиск осуществляется до тех пор, пока не будет най

ден элемент или нижняя граница не зашкалит за

верхнюю*/

PosFndEl = Low; /*Перебран весь массив. Искомый элемент не найден.

Определена позиция возможной вставки нового эле-

мента*/

} /*FindElList2*/

//-------- Удаление списка --------//

void DelList (void* &PtrArrPtr)

/*Удаление списка сводится к поочередному удалению всех эле-

ментов списка. По окончанию удаления списка счетчик его элементов

равен 0. Таким образом, удаляются только элементы списка, а массив

указателей на элементы списка сохраняется для возможного после-

дующего наращивания списка. Размер SizeArr массива указателей со-

кращается до начального размера SizeArrH. Для полной очистки па-

мяти от списка вслед за вызовом этой процедуры необходимо уда-

лить массив указателей*/

{

if ( ((int*)PtrArrPtr) [PosCnt] != 0) /*список не пуст*/

{

/*удаление всех элементов внутри списка*/

for (int K = 2; K <= ((int*)PtrArrPtr) [PosCnt] + 1; K++)

{

delete ((void**) PtrArrPtr) [K];

}

/*сокращение размера массива указателей от SizeArr до

SizeArrH*/

/*освобождение места, занимаемого старым массивом*/

delete [] PtrArrPtr;

/*выделение места для нового массива*/

PtrArrPtr = new void*[SizeArrH+2];

((int*)PtrArrPtr)[PosCnt] = 0; /*колич. элементов в новом массиве 0*/

((int*)PtrArrPtr)[PosSize] = SizeArrH; /*установка начального размера

массива в массиве указателей*/

}

} /*DelList*/