Расширенные типы данных 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++ и с некоторыми расширениями языка. ООП оперирует абстрактными понятиями классов, объектов, методов, инкапсуляции, наследования и полиморфизма. Читатель быстро научится находить аналоги терминов ООП в реальном мире, что облегчит освоение в общем-то простых понятий с довольно туманными и, зачастую, неудачными определениями.
Мы по достоинству оценили введение новых компонентных классов и подготовились к манипулированию с ними в рамках интегрированной визуальной среды быстрой разработки приложений.