Модуль для работы с ассоциативными массивами в C++ Builder

Источник: codingclub

Вступление

Мой любимый язык - PHP. Он изящен и прост, но, к сожалению, предназначен только для программирования сайтов. «Обычную» программу на нём не напишешь.

К счастью, некоторые технологии, реализованные в PHP можно перенести и в другие языки программирования: например, в C++.

Одна из таких технологий - ассоциативные массивы.

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

ass_arr array;
array[0] = 123;
array["name"] = "John Silver";

Здесь в массиве array создаются два элемента, один из которых имеет ключ «0» и числовое значение «123», другой - ключ «name» и строковое значение «John Silver». «ass_arr» - не массив задниц, как подумало большинство читателей, а возможное имя типа (класса) ассоциативного массива.

Удобно? Удобно! Не нужно описывать входящие в массив элементы и их типы. Не нужно думать о размере массива - он динамичен. Не нужно заботится ни о чём, кроме свободной памяти.

Подробнее об удобствах

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

Рассмотрим простой пример. Возьмём структуру, в которой хранятся настройки некоей программы. Опишем её так:

struct preferences
{
int WindowWidth;
int WindowHeight;
int WindowX;
int WindowY;
char documentPath[128];

};

Для сохранения данных этой структуры где-либо, потребуется специальная функция, которая будет «знать» все поля, которые присутствуют в этой структуре. Например, такая:

bool savePreferences(struct preferences* pref)
{
saveInteger(pref->WindowWidth);
saveInteger(pref->WindowHeight);
...
saveString(pref->documentPath);
}

При добавлении в структуру нового поля, придётся дополнять эту функцию.

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

Это могло бы выглядеть так:

bool savePreferences(ass_arr* pref)
{
int i;
Variant v;

// цикл по всем элементам

for (i = 0; i < pref->Count(); i++)
{

// извлекаем очередной элемент

v = (*pref)[pref->key(i)].v()

// если элемент числового типа,
// сохраняем его числовое значение
if (VarType(v) == varInteger)
{
saveInteger((*pref)[pref->key(i)].asInteger());
}
// далее для других типов
...

}
}

Как быть, если нужно заполнить данными настроек Builder'овскую форму? Потребуется ещё одна функция. При использовании ассоциативных массивов эту процедуру можно автоматизировать.

А главное: при добавлении в массив настроек нового поля - не нужно ничего менять.

Существует ещё много подобных задач. Ассоциативные массивы - универсальное средство. Но как реализовать их в C++?

Реализация ассоциативных массивов в C++ Builder

Для реализации класса ассоциативного массива, я использовал несколько стандартных классов: во-первых, Variant - мультитип. В переменной типа Variant может хранится значение любого из стандартных типов. Во-вторых, CList - для создания внутренних списков. Поэтому, вне Builder'а - например, в MSVC++, этот класс работать не будет. Однако, при большом желании, его можно портировать (использовав list из stl и написав свою реализацию Variant).

Моя библиотека содержит три класса: ass_arrEl - класс элемента массива, ass_arr - класс простого ассоциативного массива, и его наследник - prop_ass_arr, предназначенный для работы с окнами настройки. Он «умеет» сохранять и загружать своё содержимое из реестра, заполнять им формы и заполняться содержимым формы сам.

Как работать с моими классами
Несколько наглядных примеров:

Простой массив. Работа со значениями.

#include "ass_arr.h";

ass_arr a;

// так можно создать элементы

a["name"] = "Сажин";
a["surname"] = "Бесноватый";

// а так - обратиться к их значениям

ShowMessage(a["name"].v());
ShowMessage(a["name"].v());

a["name"].v() возвращает значение типа Variant.

Работа с ключами

#include "ass_arr.h";

ass_arr a;
int i;

// Создаём два значения

a["name"] = "Сажин";
a["surname"] = "Бесноватый";

// Выводим их в цикле

for (i = 0; i < a.Count(); i++)
{
// a.key(i) возвращает ключ i-го по счёту элемента.
// Ключ тоже типа Variant. Заметьте, что при выводе я напрямую
// не указываю ключей: они определяются автоматически

ShowMessage(a[a.key(i)].v());

}

В ключах не существует недопустимых символов. Вы можете использовать в качестве ключей даже имена файлов с полными путями!

Вложенные массивы. Простейшее дерево.

#include "ass_arr.h";

ass_arr a;
ass_arr* inner;
int i;

// создаём новый ассоциативный массив

inner = new ass_arr;

// заполняем его данными. (*inner)[] - обращение к оператору
// обьекта по указателю.

(*inner)["name"] = "Фёдор";
(*inner)["surname"] = "Сумкин";

// вносим его в нулевой элемент массива a

a[0] = inner;

inner = new ass_arr;
(*inner)["name"] = "Фёдор";
(*inner)["surname"] = "Чистяков";
a[1] = inner;

inner = new ass_arr;
(*inner)["name"] = "Фёдор";
(*inner)["surname"] = "Беззвестный";

// присваивать можно ссылку на массив, либо же сам массив

a[2] = *inner;

// теперь выведем поле surname второго элемента

inner = a[1].sub(); // заносим в inner ссылку на вложенный массив второго элемента
ShowMessage((*inner)["surname"]);

// выведем поле name третьего элемента (можно писать так)

ShowMessage((*(a[2]))["name"]);

Вложенные массивы так же могут иметь вложенные массивы. Подобные структуры, по сути, представляют из себя деревья с узлами произвольной структуры.

Заполнение формы значениями массива. Загрузка значений ассоциативного массива. Сохранение ассоциативного массива в реестре и загрузка его из реестра.
Допустим, на форме mainForm два поля: TEdit login и TEdit password. Кроме того, в массиве конфигурации необходимо хранить число запусков программы (numStarts).

#include "ass_arr.h";

prop_ass_arr config;

... mainForm::onCreate(...)
{
// загружаем конфигурацию из реестра

if (!config.loadSection(HKEY_CURRENT_USER, "Software/Kuu/Passworder"))
ShowMessage("Не удалось загрузить конфигурацию из реестра");

config["numStarts"].v()=config["numStarts"].v()+1;

}

... mainForm::onShow(...)
{
// заполняем форму значениями конфигурации

config.toForm(this);
}

... mainForm::onDestroy(...)
{
// заполняем конфигурацию значениями из формы

config.fromForm(this);
if (!config.saveSection(HKEY_CURRENT_USER, "Software/Kuu/Passworder"))
ShowMessage("Не удалось сохранить конфигурацию в реестр");
}

Так просто? Да!

saveSection и loadSection поддерживают вложенные массивы неограниченного уровня вложенности.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=2562