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

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

Заев А.А. 

Вступление

Мой любимый язык – 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://kuu.spb.ru

http://www.realcoding.net/

Исключения в Borland С++ Builder 6.0

В статье рассматриваются проблемы, возникающие при работе с исключениями в среде Borland C++ Builder 6.0

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

Итак, краткий инструктаж по применению исключений, согласно книгам, статьям и официальным исходникам.

Конструкция исключений имеет следующий вид.

Пример №1

try // try - указывает на то, что дальше пойдет блок исключений

{

throw 1; // throw – ключевое слово, собственно и создающее исключение

}

catch(int a) // catch – указывает на то, что дальше пойдет блок отлова исключений

{

MessageDlg("Exception - 1",mtError, TMsgDlgButtons() << mbOK, 0); // Сообщение о // поимке исключения

};

Теперь, разберем эту конструкцию поподробнее. 

try

Ключевое слово, указывающее на начало блока исключений. Используется только в С++, а в С придется использовать __try. Однако, __try можно так же использовать в C++.

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

throw

Ключевое слово, создающее исключение. Иными словами, при создании исключения, throw инициализирует временный объект того типа, с помощью которого мы хотим создать исключение. В приведенном примере, мы используем тип int, соответственно, создается временный объект типа int, содержащий данные – 1. 

catch

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

И вроде бы все! Создаем новый проект, помещаем на форму кнопочку, и вставляем в обработчик нажатия этой кнопки наш пример. Потом запускаем. Компилятор уверенно считывает строчки. Линкер не видит ничего подозрительного. Программа запускается, и радостно ожидает начала работы. Мы нажимаем на кнопочку… 

Бах!!!

И хорошо, если вам скажут, что «Project XXX.exe raised exception class int with message 'Exception Object Address: 0x8E4C6E'. process stopped. use Step or Run to Continue.»

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

Итак, что же неправильно в этом коде?

Во-первых, давайте разберемся, что такое «Project XXX.exe raised exception…»

Это сообщение появляется тогда, когда программа, над которой трудится дебаггер, создает исключение. Проблема в том, что С++ Builder (равно как и Delphi) почему-то все исключения, которые находит, по умолчанию скидывает на стандартный обработчик. 

Однако, подобное сообщение мы можем увидеть не только при обработке своего исключения. Это сообщение возникает так же в случаях, если это Operating System Exception (Исключение операционной Системы) или Language Exception (Исключение Языка). 

В данном примере, мы работаем с помощью средств языка, нисколько не трогая (ну разве что самую малость) операционную систему. 

Так что стоит сходить в гости к дебаггеру, и проверить – чем он там занимается.

Найти его можно по адресу: Tools->Debugger Options 

Здесь нас интересует вкладка Language Exceptions. Заходим внутрь, и что мы видим? Налицо суровое нарушение правил безопасности! Оказывается, дебаггер успешно игнорирует исключения, принадлежащие Delphi, Microsoft, VisiBroker, CORBA! Но зачем? Ответ очевиден – каждый из этих шедевров мысли имеет свои собственные обработчики исключений, которые заточены под специфические нужды. И они вовсе не собираются пользоваться стандартным обработчиком. 

Мы, конечно, можем вписать сюда тип int, но в этом случае дебаггер будет игнорировать все, что связано с этим типом данных. Это не есть правильно. 

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

Назовем его TCustomException. Кликаем по Add, и вписываем это в строчку диалога. Потом щелкаем OK. Все, поздравляю дамы и господа! Мы успешно зарегистрировали тип данных. То есть теперь дебаггер не будет обижаться на то, что мы используем для генерации исключений что-то, о чем он не знает. 

Итак, уходим из опций дебаггера, и возвращаемся в программу. 

Изменим код.

Пример №2

сlass TCustomException{}; // Наш тип данных

TCustomException NewEx; // Объект класса (типа) TCustomException

try // Начало блока создания исключений

{

throw NewEx; //Создание исключения

}

catch(TCustomException) // Начало блока отлова исключений

{

MessageDlg("Exception!!!",mtError, TMsgDlgButtons() << mbOK, 0); //Сообщение

};

Поскольку классы являются типами данных, мы без труда создаем свой TCustomException. Однако, неудобно создавать под каждое новое исключение – новый класс. Так что лучше создать объект класса TCustomException, что мы и делаем. Создаем объект NewEx, и используем его для создания исключения, которое потом с успехом отлавливается. В остальном конструкция работает так же, как вышеописанная, с той разницей, что она работает! 

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

Пример №3

//Класс для исключений с параметрами. 

//Не забывать регистрировать его в опциях дебаггера!

class TEx

{

public:

int fCode;

TEx(int eCode){fCode=eCode;};

};

// Код, встроенный в клавишу Button1

try

{

throw TEx(1301);

}

catch(TEx Ex)

{

if(Ex.fCode==1301){

MessageDlg("Exception!", mtError, mbOKCancel, 0);

};

}

Иными словами, можно внутри рабочего алгоритма создавать исключения в любой непонравившейся вам ситуации, а затем в обработчике исключений, решать – что делать в каждом конкретном случае. Просто, удобно и надежно. Какие милые люди работают в Borland! 

Надеюсь, эта статья поможет интересующимся в их неравном бою с замечательным (да, я на самом деле так считаю!) инструментом – Borland C++ Builder 6.0 

Заев А.А.