Расширенные типы данных Delphi

C++Builder не позволяет посредством известного ключевого словаtypedefпросто переопределить некоторые сложные типы данных Объектного Паскаля. C++Builder реализует такие расширенные типы в виде обычных или шаблонных классов (template class). Каждый такой класс содержит все необходимые конструкторы, деструкторы, свойства и объектные методы. Многие компоненты VCL используют реализацию расширенных типов, а кроме того, они требуются при разработке новых компонент на базе оригиналов из Delphi.

Ниже приводится сводная таблица встроенных типов Delphi и соответствующих им типов C++Builder:

Delphi Длина и значения C++Builder Реализация
Shortint 8-битовое целое char typedef
Smallint 16-битовое целое short typedef
Longint 32-битовое целое long typedef
Byte 8-битовое целое без знака unsigned char typedef
Word 16-битовое целое без знака unsigned short typedef
Integer 32-битовое целое int typedef
Cardinal 32-битовое целое без знака unsigned long typedef
Boolean true/false bool typedef
ByteBool true/false или 8-битовое целое без знака unsigned char typedef
WordBool true/false или 16-битовое целое без знака unsigned short typedef
LongBool true/false или 32-битовое целое без знака unsigned long typedef
AnsiChar 8-битовый символ без знака unsigned char typedef
WideChar Слово - символ Unicode wchar t typedef
Char 8-битовый символ char typedef
String Текстовая строка Delphi AnsiString typedef
Single 32-битовое плавающее число float typedef
Double 64-битовое плавающее число double typedef
Extended 80-битовое плавающее число long double typedef
Real 32-битовое плавающее число float typedef
Comp 64-битовое плавающее число double typedef
Pointer 32-битовый указатель void * typedef
PChar 32-битовый указатель на символы без знака unsigned char * typedef
PansiChar 32-битовый указатель на ANSI символы без знака unsigned char * typedef
Set Множество 1..32 байт Set<type, minval, maxval> template class
AnsiString Текстовая строка Delphi AnsiString class
Variant Вариантное значение, 16 байт Variant class
TdateTime Значение даты и времени, 64-битовое плавающее число TDateTime class
Currency Валюта, 64-битовое плавающее число, 4 цифры после точки Currency class

Set (множество) служит для спецификации типа параметров объектных методов VCL или типа значений, возвращаемых этими методами. C++Builder реализует этот встроенный тип Delphi с помощью одноименного шаблонного класса Set<type, minval, maxval> со следующими параметрами:

type тип элементов множества (обычно,int. char илиenum):

minval минимальное (положительное) значение, которое могут принимать элементы множества;

maxval максимальное (не более 255) значение, которое могут принимать элементы множества.

Подстановка разных значений параметров приводит к созданию экземпляров шаблонного класса Set различных типов, поэтому оператор сравнения if (si == s 2) объектов, описанных как

Set<char,'A', 'C'>si;

Set<char, 'X', 'Z'> s2;

вызовет ошибку компиляции. Для создания множественных экземпляров типаSetнеобходимо использовать ключевое слово typedef. Например, объявив typedef Set<char, 'A','Z'> UpperCaseSet; можно создать множества UpperCaseSet si; и UpperCaseSet s2; а затем инициализировать эти объекты:

s1 “ 'А' “ 'В' “ 'С' ;

s2 “ 'X' “ 'Y' “'?.' ;

AnsiString используется для спецификации типа текстовых строк произвольной длины, имеющих следующую характерную внутреннюю структуру:

счетчик длина строки данные терминатор \0

C++Builder реализует этот встроенный тип Delphi как одноименный класс. Если при создании экземпляров данного класса не указано начальное значение строки, конструктор AnsiString автоматически присваивает всем переменным нулевые значения. Среди методов данного класса отметим наиболее часто вызываемый метод с str (), который возвращает указатель на символьный массив, оканчивающийся 0 и содержащий копию символов, заключенных в исходном объекте типа AnsiString. Листинг 3.18 иллюстрирует "применение методов чтения и записи значения члена данных FNames свойства Names типа AnsiString в экземпляре MyFamily объявленного компонентного класса Family". Предыдущее предложение кажется полной абракадаброй, если не проникнуться терминологией объектно-ориентированного программирования. Рассматривайте его как своеобразное словесное упражнение по краткому курсу ООП.

#include <vcl/dstring.h> #include <stdio.h> class Family // объявление класса

{

private:

AnsiString FNames[10]; // массив имен AnsiString GetName(int Index); // метод чтения void SetName(int, AnsiString); // метод записи public:

_property AnsiString Names[int Index] =

{read=GetName, write=SetName} ;

Family(){} // .конструктор -Family(){) // деструктор

};

AnsiString Family::GetName(int i)

{

return FNames[i]; // GetName возвращает значение }

void Family::SetName(int i,const AnsiString s) { FNames[i]=s; // SetName присваивает значение

}

void main()

{

Family My Family; // создание объекта MyFamily // Инициализация 4-х строк массива имен методом SetName() MyFamily.Names[0]="Иван" ;

MyFamily.Names[1]="Анна" ;

MyFamily.Names[2]="Марья";

MyFami ly. Names [ 3 ] = " Андрей " ;

// Вывод 4-х строк массива имен методом GetName() for (int i=0; i<=3; i++)

puts(MyFamily.Names[i].c_str()) ;

}

Листинг 3.18. Пример использования типа AnsiString в C++ программе с компонентным классом Family (Семья).

Variant служит для спецификации значений, меняющих тип динамически. Переменная вариантного типа, в отличие от обычных статически типизированных переменных, способна менять свой тип во время исполнения программы. C++Builder объявляет этот тип Delphi как class __declspec(delphireturn) Variant: public TVarData. Заметим, чтс синтаксис вариантов, принятый в Delphi, например:

V: Variant;

V := VarArrayCreate([0,Hi9hVal,0,HighVal],varlnteger) ;

отличается от способа записи вариантного массива в C++Builder:

Variant V(OPENARRAY(int,(0,HighVal,0,HighVal)),varlnteger);

Вариант может быть сконструирован из следующих типов данных:short, int, float, double. Currency, TDateTime,bool, WordBool, Byte, AnsiString&,char *, wchar_t * const. 01e2::lDispatch* const или 01e2::IUnknown* const. Компилятор автоматически выполнит необходимые преобразования типа. При создании вариантных переменных они всегда инициализируются специальным значением Unassigned (не определено). Специальное значение Null указывает, что данные отсутствуют.

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

TDateTime используется для спецификации переменных даты и времени. C++Builder реализует этот встроенный тип Delphi как одноименный класс, который инкапсулирует член данных типаdouble, содержащий значение даты в целой части числа, а значение времени в мантиссе (считая от полудня 30 декабря 1899 года). В следующей таблице приведены значения переменной типа TDateTime и их эквиваленты в выражениях даты и времени:

Значение Дата Время Примечания
12/30/1899 12:00 +0 дней, +0 часов
2.75 01/01/1900 18:00 +2 дня, +6 часов
-1.25 12/29/1899 06:00 -1 день, -б часов
01/01/1996 12:00 +35065 дней, +0 часов

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

3.6.2 Расширения стандарта ANSI C++

Рассматриваемые расширения стандарта ANSI C++, в основном, представляют интерес для разработчиков новых классов и компонент, а также для программистов, которые работают в большом коллективе над созданием сложного проекта.

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

3.6.2.1 Шаблоны

Шаблоны (параметризованные типы)позволяют конструировать семейство связанных функций или классов. Обобщенный синтаксис определения шаблона имеет вид

template <список шаблонных типов> { объявление } ;

Различают шаблоны функций и шаблоны классов.

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

template <class Т> Т тах(Т х. Ту) {return (x > у) ? х : у; } ;

причем тип данных, представленный аргументом шаблона<class T>, может быть любым. При его использовании в программе компилятор генерирует код функции тах в соответствии с фактическим типом передаваемых ей параметров:

int i;

Myclassa, b;

int j = max(i, 0); // тип аргументов int Myclass m = max(a, b); // тип аргументов Myclass

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

#define max(x,y) ((х > у) ? х : у) из-за блокировки механизма проверки типов, который дает такие преимущества языку C++ над обычным С. Очевидно, задача функции тах(х, у) - сравнить совместимые типы. К сожалению, использование макроса допускает сравнение несовместимых типов, например,int и struct.

Шаблон классов задает образец определений семейства классов. Рассмотрим пример шаблона Vector - генератора классов одномерного массива данных:

template <class T> class Vector

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

Как и в случае шаблонов функций, разрешается явно переопределять тип шаблон классов:

classVector<char *> ( ... };

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

3.6.2.2 Пространства имен

Большинство нетривиальных приложений состоят из нескольких файлов с исходным текстом программы. Эти файлы могут создаваться и обслуживаться группой программистов. В конце концов, все файлы собираются вместе и проходят через финальную процедуру сборки готового приложения. Традиционно принято, чтобы все имена, не заключенные в некоторой локальной области (функции, теле класса или модуле трансляции), разделяли общие глобальные имена. Поэтому повторное определения имен, обнаруженное в процессе сборки отдельных модулей, приводит к необходимости каким-то образом различать каждое имя. Решение этой проблемы в C++ возложено на механизм пространства имен (namespace).

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

namespace <идентификатор> { [<объявления>] } Существует три способа доступа к элементам идентифицированного пространства имен:

• Явная квалификация доступа к конкретному элементу:

ALPHA:: varl; // доступ к переменной из ALPHA BETA:: Fl; // доступ к функции из BETA

• Доступ ко всем элементам:

using namespace :: ALPHA; // доступ ко всем именам из ALPHA

• Объявление нового идентификатора в локальном пространстве имен:

using :: new_name; // добавление идентификатора

3.6.2.3 Явные объявления

Обычно объектам класса, в котором объявлен конструктор с одним параметром, можно присвоить значения, тип которых автоматически (неявно) преобразуется к своему классовому типу. При объявлении конструктора можно использовать модификаторexplicit:

explicit <объявление конструктора> Тогда при объявлении конструкторов данного класса с ключевым словомexplicitвсем объектам класса можно присвоить только те значения, тип которых явно преобразуется к классовому типу (Листинг 3.19). Другие присваивания приведут к ошибке компиляции.

class X

public:

explicit X(int);

explicit X(const char*, int = 0);

};

voidf(X arg)

(

X a = X (1) ;

X b = Х("строка",0);

a = Х(2);

} :(

Листинг 3.19. Явные объявления конструкторов.

Явные объявления конструкторов требуют, чтобы значения в операторах присваивания были преобразованы к тому классовому типу, объектам которого эти значения присваиваются.

3.6.2.4 Непостоянные объявления

При объявлении переменной, которая может быть изменена фоновой задачей, обработчиком прерывания или портом ввода-вывода, используется модификатор volatile:

volatile <тип> <имя объекта>;

В C++ применение ключевого словаvolatile распространяется на классы и функции-члены. Это ключевое слово запрещает компилятору делать предположения относительно значения указанного объекта, поскольку при вычислении выражений, включающих этот объект, его значение может измениться в любой момент. Кроме того. непостоянная переменная не может быть объявлена с модификатором register. Листинг 3.20 показывает пример реализации таймера, в котором переменная ticks модифицируется обработчиком временных прерываний.

volatile intticks;

void timer( ) // Объявление функции таймера

ticks++;

voidwait (int interval)

ticks = 0;

while (ticks < interval); // Цикл ожидания

}

Листччг 3.20. Изменение непостоянной переменной volatile.

Положим, что обработчик прерывания timer был надлежащим образом ассоциирован с аппаратным прерыванием от часов реального времени. Процедура wait реализует цикл ожидания, пока значение переменной ticks не станет равным интервалу времени, заданному ее параметром. Компилятор C++ обязан перезагружать значение переменнойvolatile ticks перед каждым сравнением внутри цикла - несмотря на то, что внутри цикла значение переменной не изменяется. Некоторые оптимизирующие компиляторы могли бы допустить эту "роковую" ошибку.

Другой вид непостоянной переменной, которая может быть изменена даже если она входит в константное выражение, объявляется с помощью модификатора mutable:

mutable <имя переменной>;

Назначение ключевого словаmutable состоит в спецификации членов данных некоторого класса, которые могут быть модифицированы константными функциями этого класса. Листинг 3.21 показывает пример, в котором член данных count модифицируется константной функцией F1.

class A {

public: mutable int count; int F1(int p = 0)const // Объявление функции F1

count = p++ return count; //PI возвращает count

) I

voidmain() {

A a;

cout “ a.Fl(3) “ end.1; // main выдает значение 4 )

Листинг 3.21. Изменение непостоянной переменной mutable.

3.6.2.5 Идентификация типов RTTI

Идентификация типов при выполнении программы RTTI (Run-Time Туре Identification) позволяет вам написать переносимую программу, которая способна определять фактический тип объекта в момент выполнения даже в том случае, если программе доступен только указатель на этот объект. Это дает возможность, например, преобразовывать тип указателя на виртуальный базовый класс в указатель на производный тип фактического объекта данного класса. Таким образом, преобразование типов может происходить не только статически - на фазе компиляции, но и динамически - в процессе выполнения. Динамическое преобразование указателя в заданный тип осуществляется с помощью оператора dynamic_cast.

Механизм RTTI также позволяет проверять, имеет ли объект некоторый определенный тип, или принадлежат ли два объекта одному и тому же типу. Оператор typeid определяет фактический тип аргумента и возвращает указатель на объект класса typeinfo, который этот тип описывает.

Передавая RTTI Инспектору объектов во время выполнения, C++Builder информирует его о типах свойств и членов данного класса.

3.6.2.6 Исключения

Язык C++ определяет стандарт обслуживания исключений в рамках ООП. C++Builder предусматривает специальные механизмы для обработки исключений (ошибок), которые могут возникнуть при использовании Библиотеки Визуальных Компонент. C++Builder также поддерживает обработку исключений самой операционной системы и модель завершения работы приложения.

Когда программа встречает ненормальную ситуацию, на которую она не была рассчитана, можно передать управление другой части программы, способной справиться с этой проблемой, и либо продолжить выполнение программы, либо завершить работу. Переброс исключений (exception throwing) позволяет собрать в точке переброса информацию, которая может оказаться полезной для диагностики причин, приведших к нарушению нормального хода выполнения программы. Вы можете определить обработчик исключения (exception handler), выполняющий необходимые действия перед завершением программы. Обслуживаются только так называемые синхронные исключения, которые возникают внутри программы. Такие внешние события, как нажатие клавишCtrl+C, не считаются исключениями.

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

1. Программа ищет подходящий обработчик исключения.

2. Если обработчик найден, стек очищается и управление передается обработчику исключения.

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

Блок кода, который обрабатывает возникшее исключение, начинается ключевым словомcatch и заключается в фигурные скобки. По меньшей мере один кодовый блок обработчика исключения должен следовать непосредственно за блоком try. Для каждого исключения, которое может сгенерировать программа, должен быть предусмотрен свой обработчик. Обработчики исключений просматриваются по порядку и выбирается обработчик исключения, тип которого соответствует типу аргумента в оператореcatch. При отсутствии в теле обработчика операторов goto, выполнение программы будет возобновлено, начиная с точки, следующей за последним обработчиком исключений данного блокаtry. Листинг 3.22 демонстрирует обобщенную схему обработки исключений.

try

i

// Любой код, который может сгенерировать исключение

} 1

catch (Т X) |

{ I

// Обработчик исключения Х типа Т, которое могло быть |

// ранее сгенерировано внутри предыдущего блокаtry Я // Обработчики других исключений предыдущего блокаtry catch (...)

// Обработчик любого исключения предыдущего блокаtry

Листинг 3.22. Обработка исключении.

Когда возникает исключение, выражение присваивания в оператореthrow<выражение> инициализирует временный объект, тип которого соответствует типу аргумента выражения. Другие копии этого объекта могут быть сгенерированы, например, при помощи конструктора копирования объекта исключения.

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

3.7Итоги

Мы вкратце ознакомились с терминологией объектно-ориентированного программирования на языке C++ и с некоторыми расширениями языка. ООП оперирует абстрактными понятиями классов, объектов, методов, инкапсуляции, наследования и полиморфизма. Читатель быстро научится находить аналоги терминов ООП в реальном мире, что облегчит освоение в общем-то простых понятий с довольно туманными и, зачастую, неудачными определениями.

Мы по достоинству оценили введение новых компонентных классов и подготовились к манипулированию с ними в рамках интегрированной визуальной среды быстрой разработки приложений.