КАТЕГОРИИ: Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748) |
МетодРисование_квадрата интерфейс 4 страница
<вид наследования><имя базового класса n>{...}; Вид наследования определяет режим доступа к компонентам каждого из базовых классов. Базовые классы создаются в том порядке, в котором они перечислены в списке базовых классов при объявлении производного класса. Если конструкторы базовых классов не имеют аргументов, то производный класс может не иметь конструктора. При наличии у конструктора базового класса одного или нескольких аргументов, каждый производный класс обязан иметь конструктор. Чтобы передать аргументы в базовый класс, нужно определить их после объявления конструктора производного класса следующим образом: <имя конструктора производного класса>(<список аргументов>): <имя конструктора базового класса 1>(<список аргументов>), <имя конструктора базового класса n>(<список аргументов>) {<тело конструктора производного класса>} Список аргументов, ассоциированный с базовым классом, может состоять из констант, глобальных параметров и/или параметров для конструктора производного класса. Последовательность активизации конструкторов такая же, как и для случая единственного базового класса: сначала активизируются конструкторы всех базовых классов в порядке их перечисления в объявлении производного класса, затем конструкторы объектных полей и в конце конструктор производного класса. Пример 3.17 Наследование от двух базовых классов. В данном примере класс twonasl наследуется от классов integral и rational и включает объект класса fixed (рис. 3.3). Объект класса twonasl состоит из следующих полей: • поля numfx, унаследованного от класса integral (описанного public, наследованного private, следовательно, private) и инициализированного случайным числом в диапазоне от -50 до 49; • полей num и с, унаследованных от класса rational (описанных public, наследованных public, следовательно, public) и инициализированных числами 20 и символом «R», причем инициализация поля с в конструкторе класса rational не предусмотрена, поэтому она выполняется в теле конструктора класса twonasl; Рис. 3.3. Диаграмма классов с наследованием от двух классов • объекта класса fixed с именем numfix, включающего внутреннее поле numfx, которое недоступно в классе twonasl и инициализируется числом 50.
#include <iostream.h> #include <conio.h> #include <stdlib.h> class fixed { int numfx; public: fixed(int v):numfx(v) {cout<<" вызов конструктора fixed\n";} }; class integral { public: int numfx; integral(int va):numfx(va) {cout<<" вызов конструктора integ\n"; } }; class rational {public: char c; int num; rational (int vn):num(vn) { cout<< " вызов конструктора rational\n ";} }; class twonasl:private integral, // наследование в защищенном режиме public rational // наследование в открытом режиме { private: fixed numfix; // объектное поле public: twonasl (int nfx,int nm,char vc,int pnfx): integral(nfx), rational(nm), numfix(pnfx) { cout<< " вызов конструктора twonasl\n "; c=vc; } /* инициализация поля базового класса в теле конструктора производного класса */ int get(void) // метод, возвращающий значение внутреннего поля, { return numfx;} // унаследованного от класса integral по private }; void main() { clrscr(); randomize; int r=random(100)-50; twonasl aa(r,20, 'R',50); cout<<aa.get()<<" "<<aa.c<<" "<<aa.num<<endl; getch();}
В результате выполнения программы мы получаем следующую цепочку обращений к конструкторам: вызов конструктора integ вызов конструктора rational вызов конструктора fixed вызов конструктора twonasl и содержимое полей numfx (класса integral), с и num, доступных в классе twonasl: -49 R 20 Виртуальное наследование. При множественном наследовании базовый класс не может быть указан в производном классе более одного раза. Однако возможна ситуация, когда производный класс при наследовании от потомков одного базового класса многократно наследует одни и те же компоненты базового класса (рис. 3.4). Иными словами, производный класс будет содержать несколько копий полей одного базового класса. Рис. 3.4. Иерархия с многократным наследованием Чтобы избежать многократного включения в производный класс компонентов базового класса, используется виртуальное наследование. При виртуальном наследовании производный класс описывают следующим образом: class <имя производного класса>: virtual <вид наследования><имя базового класса>{...}; В этом случае включение в производный класс полей базового класса осуществляется один раз, а их инициализация происходит в производном классе, который не является прямым потомком базового класса. Вызов конструкторов при этом производится в следующем порядке: сначала конструктор виртуально наследуемого базового класса, затем конструкторы базовых классов в порядке их перечисления при объявлении производного класса, за ними - конструкторы объектных полей и конструктор производного класса. Деструкторы соответственно вызываются в обратном порядке. Виртуально наследуемый класс обязательно должен содержать конструктор без параметров, который активизируется при выполнении конструкторов классов - прямых потомков виртуально наследуемого класса. Пример 3.18 Виртуальное наследование. Реализуем иерархию классов, представленную на рис. 3.5. Класс derived наследуется от двух наследников класса fixed. Чтобы исключить удваивание полей, описанных в классе fixed, необходимо использовать виртуальное наследование.
Рис. 3.5. Виртуальное наследование #tinclude <iostream.h> class fixed {protected: int Fix; public: fixed(void) // конструктор без параметров {cout<<"вызов конструктораfixed\n";} fixed(int fix):Fix(fix) /* конструктор с параметром */ {cout<< "вызов конструктора fixed int\n ";} class derived_1: virtual public fixed // виртуальное наследование {public: int One; derived_1 (void) { cout<< "вызов конструктора l\n";} }; class derived_2: virtual private fixed // виртуальное наследование {public: int Two; derived_2(void) { cout<< " вызов конструктора 2\n";} }; class derived: public derived_1, public derived_2 /* объявление производного класса - непрямого потомка */ { public: derived(void){ cout<<" вызов конструктора derived \n";} derived(int fix):fixed(fix) { cout<<" вызов конструктора derived (int) \n";} void Out() { cout<< " поле Fix = "<< Fix;} }; main() { derived Var(10); derived Var(10); Var.Out(); } В результате работы программы получаем вызов конструктора fixed int вызов конструктора 1 вызов конструктора 2 вызов конструктора derived.int поле Fix=10 В том случае, если бы наследование не было виртуальным, поле Fix было бы включено в объект класса derived дважды: derived l::Fix и derived 2::Fix. 4. 5. 6. ОБЪЕКТНАЯ МОДЕЛЬ C++ BUILDER Объектная модель C+ + Builder несколько отличается от первоначальной объектной модели C++, описанной в гл. 3. Прежде всего эта среда базируется на современной усложненной модели, используемой в последних версиях языка C++, т. е. поддерживает пространства имен, исключения и специальные средства преобразования типов. Кроме того, C++ Builder использует библиотеку классов VCL, разработанную для среды Delphi и основывающуюся на объектной модели Delphi Pascal. Следовательно, при создании C++ Builder необходимо было согласовать конструкции C++ и Pascal Delphi, а также механизмы реализации этих конструкций, обращая особое внимание на те возможности, которые есть в Delphi Pascal, но отсутствуют в C++. В результате в объектную модель C++ были добавлены: - возможность создания специальных секций для описания опубликованных элементов класса и элементов, реализующих OLE-механизм; - средства объявления свойств; - определения специальных классов, моделирующих стандартные типы данных Delphi Pascal (множества, строки и т. д.); - возможность определения указателей на методы (__closure); - специальный модификатор (__declspec), посредством которого реализуются, например, динамические методы Delphi Pascal. Различие объектных моделей и их реализаций в C++ и Delphi Pascal не позволяет обеспечить полную совместимость классов, разработанных в этих языках. Поэтому разработчики C++Builder обеспечили возможность создания двух типов классов: обычные классы C++ с расширенными возможностями и VCL-совместимые классы - для работы с библиотекой визуальных компонент VCL. 6.1. Расширение базовой объектной модели языка C++ Как уже говорилось выше, объектная модель C++ Builder включает ряд новых (по сравнению с Borland C++ 3.1) средств. Это средства: - определения пространств имен; - описания указателей на методы; - определения и переопределения типа объекта; - описания свойств.
Пространство имен. Большинство сколько-нибудь сложных приложений состоит более чем из одного исходного файла. При этом возникает вероятность дублирования имен, что препятствует сборке программы из частей. Для снятия проблемы дублирования имен в C++ был введен механизм логического разделения области глобальных имен программы, который получил название пространства имен. Пространство имен описывается следующим образом: namespace [<имя>] {<объявления и определения>} Имя пространства имен должно быть уникальным или может быть опущено. Примечание. Если имя пространства опущено, то считается, что определено неименованное пространство имен, локальное внутри единицы трансляции. Для доступа к его ресурсам используется внутреннее имя $$$. Имена, определенные в пространстве имен, становятся локальными внутри него и могут использоваться независимо от имен, определенных в других пространствах. Таким образом, снимается требование уникальности имен программы. Например: namespace ALPHA // ALPHA - имя пространства имен { long double LD; // объявление переменной float f(float у) { return у; } // описание функции }
Имя пространства имен должно быть известно во всех модулях программы, которые используют его элементы. Пространство имен определяет область видимости, следовательно, функции, определенные в одном пространстве имен, могут без ограничений вызывать друг друга и использовать другие ресурсы, объявленные там же (переменные, типы и т. д.). Доступ к элементам других пространств имен может осуществляться тремя способами: - с использованием имени области в качестве квалификатора доступа, например: ALPHA:: LD ALPHA::f() - с использованием объявления using, которое указывает, что некоторое имя доступно в другом пространстве имен: namespace BETA { using ALPHA::LD; /* имя ALPHA::LD доступно в BETA*/} - с использованием директивы using, которая объявляет все имена одного пространства имен доступными в другом пространстве: namespace BETA { using ALPHA; /* все имена ALPHA доступны в BETA*/ }
Каждое объявление класса в C++ образует пространство имен, куда входят все общедоступные компоненты класса. Для доступа к ним принято использовать квалификаторы доступа <имя класса>::. Директиву using внутри класса использовать не разрешается. Применение же объявления using допустимо и может оказаться весьма полезным. Пример 6.1. Переопределение метода потомка перегруженным методом базового класса (с использованием объявления using). Описание базового и производного классов в соответствии с правилами модульного программирования в C++ выполним в файле-заголовке Object.h: #ifndef ObjectH #define ObjectH class A { public: void func(char ch,TEdit *Edit); }; class В: public A { public: void func (char *str,TEdit *Edit); using A::func; // перегрузить B::func }; #endif
Реализацию методов классов поместим в файле Object.cpp:
#include <vcl.h> #pragma hdrstop #include "Object.h" void A::func (char ch, TEdit *Edit) // метод базового класса {Edit->Text=AnsiString("символ"); } void B::func (char *str, TEdit *Edit) // метод производного класса { Edit->Text=AnsiString("cmpoка");} #pragma package(smart_init)
Вызов нужного метода, как это принято для перегруженных функций, определяется типом фактического параметра: B b; b.func('c', Edit); // вызов A::func(), так как параметр - символ b.func("c",Edit); //вызов В::func(), так как параметр – строка
Указатель на метод. Делегирование. В стандартном C++ существует возможность объявления указателей на функции. Аналогично можно объявлять указатели на методы как компонентные функции определенного класса. Такое объявление должно содержать квалификатор доступа вида <имя класса>::. Вызов метода по адресу осуществляется с указанием объекта, для которого вызывается метод. Например, если описан класс base: class base { public: void func(int x, TEdit *Edit); }; то можно определить указатель на метод этого класса и обратиться к этому методу, используя указатель: base A; void (base::*bptr)(int,TEdit *); // указатель на метод класса bptr = & base::func; // инициализация указателя ( A.*bptr )(l,ResultEdit); // вызов метода через указатель
Причем существенно, что указатель определен именно для методов данного класса и не может быть инициализирован адресом метода даже производного класса, не говоря уж о методах других классов: class derived: public base { public: void new _func(int i, TEdit *Edit); }; ….. bptr =&derived:.new_func; // ошибка при компиляции!!!
С помощью описателя __ closure в C++ Builder объявляется специальный тип указателя -указатель на метод. Такой указатель, помимо собственно адреса функции, содержит адрес объекта, для которого вызывается метод. В отличие от обычных указателей на функции, указатель на метод не приписан к определенному классу и потому может быть инициализирован адреса ми методов различных классов. Объявление указателя на метод выполняется следующим образом: <тип> (__ closure * <идентификатор>) (<список параметров>); Например, для классов, описанных выше, можно выполнить следующие объявления: base A; derived В; void (__ closure *bptr)(int,TEdit *); // указатель на метод bptr = &A.func; /* инициализация указателя адресом метода базового класса и адресом объекта А */ bptr(l,ResultEdit); // вызов метода по указателю bptr = &B.new_func; /* инициализация указателя адресом метода производного класса и адресом объекта В*/ bptr(l,ResultEdit); // вызов метода по указателю
Указатели на методы поддерживают сложный полиморфизм. Так, если базовый и производный классы содержат виртуальные методы, то при вызове метода по указателю определение класса объекта и, соответственно, аспекта полиморфного метода будет происходить во время выполнения программы. Например: class base { public: virtual void func_poly(TEdit *Edit); }; class derived: public base { public: virtual void func_poly(TEdit *Edit); }; …. base *pB; pB=new derived; void (__ closure *bptr)(TEdit *); // указатель на метод bptr = &pB->func_poly; /* инициализация указателя адресом полиморфного метода и адресом объекта производного класса, нужный аспект определяется во время выполнения программы */ bptr(ResultEdit); // вызов метода по указателю
Указатели на метод в основном используются для подключения обработчиков событий, но могут применяться и для реализации делегирования методов. Пример 6.2. Делегирование методов (графический редактор «Окружности и квадраты»). Делегирование методов проиллюстрируем на примере разработки графического редактора «Окружности и квадраты», рассмотренного в § 5.5 (см. пример 5.6). Сначала опишем класс TFigure в файле Object.h:
#iifhdef FigureH #define FigureH typedef void (__ closure *type_pMetod) (TImage *); class TFigure { private: int x,y,r; type_pMetod fDraw; public: _property type _pMetod Draw = {read=fDraw, write =fDraw}; TFigure(int, int, int, TImage *, TRadioGroup *); void DrawCircle(TImage *); void DrawSquare (TImage *); void Clear(TImage *); }; #endif
Описание методов класса поместим в файл Object.cpp:
#include <vcl.h> #pragma hdrstop #include "Figure.h" TFigure::TFigure(int X, int Y, int R, TImage *Image, TRadioGroup *RadioGroup) { x=X; y=Y; r=R; switch (RadioGroup->ItemIndex) // определить метод рисования {case 0: Draw=DrawCircle; break; case 1: Draw=DrawSquare;} Draw(Image); // нарисовать фигуру } void TFigure::DrawCircle(TImage *Image) {Image->Canvas->Ellipse(x-r, y-r, x+r, y+r); } void TFigure:.DrawSquare (TImage *Image) {Image->Canvas->Rectangle(x-r, y-r, x+r, y+r);} void TFigure::Clear(TImage *Image) { Image->Canvas->Pen->Color=clWhite; Draw(Image); // вызов метода по адресу, указанному в свойстве Image->Canvas->Pen->Color=clBlack; } #pragma package (smart_init) Объекты класса Figure будем создавать при нажатии клавиши мыши:
void __fastcall TMainForm:: ImageMouseDown (TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { if (Figure!= NULL) delete Figure; // если объект создан, то уничтожить Figure=new TFigure(X,Y, 10,Image,RadioGroup); // создать объект }
При переключении типа фигуры будем стирать уже нарисованную фигуру и рисовать фигуру другого типа:
void __fastcall TMainForm:: RadioGroupClick (TObject *Sender) { if (Figure!= NULL) / если фигура нарисована, то { Figure- > Clear (Image); // стереть ее switch (RadioGroup->ItemIndex) // делегировать метод { case 0: Figure->Draw=Figure->DrawCircle; break; case 1: Figure->Draw=Figure->DrawSquare; } Figure-> Draw (Image); } II нарисовать фигуру другого типа }
Операторы определения и переопределения типа объекта. Эти операторы были включены в C++, чтобы обезопасить операцию переопределения (приведения) типов, которая программировалась следующим образом: (<имя типа>)<имя переменной> В теории ООП различают нисходящее и восходящее приведения типов для объектов иерархии классов. Приведение типов называют нисходящим, если в его результате указатель или ссылка на базовый класс преобразуется в указатель или ссылку на производный, и восходящим, если указатель или ссылка на производный класс преобразуется в указатель или ссылку на базовый класс. Восходящее приведение типов никакой сложности не представляет и возможно во всех случаях. Оно используется сравнительно редко, практически только в тех случаях, когда необходимо объекту производного класса обеспечить доступ к переопределенным функциям базового класса, например: class A { public: void func(char ch); }; class В: public A { public: void func (char *str); }; ….. B b; b.func("c"); //вызвать B::func() (A)b .func('c'); // вызвать A::func(); (A)b - восходящее приведение типа
При выполнении нисходящего приведения типов необходима проверка, гак как никакой гарантии, что указатель ссылается на адрес объекта именно данного производного класса, у нас нет. Используется же это преобразование при работе с полиморфными объектами постоянно, в связи с тем, что это единственный способ обеспечить видимость полей производного класса при работе с объектом через указатель на базовый класс (см. § 1.6). В последних версиях C++ приведение типов выполняется с использованием специальных операторов. Рассмотрим эти операторы. Динамическое приведение типа: dynamic_cast <T>(t). Операнды: Т - указатель или ссылка на класс или void*, t - выражения типа указателя, причем оба операнда либо указатели, либо ссылки. Приведение типа осуществляется во время выполнения программы. Предусмотрена проверка возможности преобразования, использующая RTTI (информацию о типе времени выполнения), которая строится в C++ только для полиморфных объектов. Применяется для нисходящего приведения типов полиморфных объектов, например: class A {virtual ~А(){}};/*класс обязательно должен включать виртуальный метод, так как для выполнения приведения требуется RTTI*/ class В: public A { virtual -В(){}}: void func(A& a) /* функция, работающая с полиморфным объектом*/ { В& b=dynamic_cast<B&>(a); II нисходящее приведение типов } void somefunc() { B b: func(b): // вызов функции с полиморфным объектом }
Если вызов dynamic_cast осуществляется в условной конструкции, то ошибка преобразования, обнаруженная на этапе выполнения программы, приводит к установке значения указателя равным NULL (0), в результате чего активизируется ветвь «иначе». Например: if (Derived* q=dynamic_cast<Derived* p>) {<если преобразование успешно, то...>} else {<если преобразование неуспешно, то...>}
В данном случае осуществляется преобразование указателя на объекты базового класса в указатель на объекты производного класса с проверкой правильности на этапе выполнения программы (с использованием RTTI). Если преобразование невозможно, то оператор возвращает NULL, и устанавливается q=NULL, в результате чего управление передается на ветвь else. Если вызов осуществляется в операторе присваивания, то при неудаче генерируется исключение bad_cast. Например: Derived* q=dynamic_cast<Derived* p>;
Статическое приведение типа: static_cast<T>(t) Операнды: Т - указатель, ссылка, арифметический тип или перечисление; t - аргумент типа, соответствующего Т. Оба операнда должны быть определены на этапе компиляции. Операция выполняется на этапе компиляции без проверки правильности преобразования. Может преобразовывать: 1) целое число в целое другого типа или в вещественное и обратно: int i; float f=static_cast<float>(i); /* осуществляет преобразование без проверки на этапе компиляции программы */ 2) указатели различных типов, например: int *q=static_cast<int>(malloc(100)); /* осуществляет преобразование без проверки на этапе компиляции программы */ 3) указатели и ссылки на объекты иерархии в указатели и ссылки на другие объекты той же иерархии, если выполняемое приведение однозначно. Например, восходящее приведение типов или нисходящее приведение типов неполиморфных объектов, иерархии классов которых не используют виртуального наследования:
class A {...}; // класс не включает виртуальных функций class В: public A {}; // не используется виртуальное наследование void somefunc() {А а; В b; В& ab=static_cast<B&>(a); // нисходящее приведение А& ba=static_cast<A&>(b); // восходящее приведение } Примечание. Кроме описанных выше были добавлены еще два оператора приведения, которые напрямую с объектами обычно не используются. Это оператор const_cast<T>(t) -для отмены действия модификаторов const или volatile и оператор reinterpret<T>(t) - для преобразований, ответственность за которые полностью ложится на программиста. Используя в каждом случае свою операцию преобразования типов, мы получаем возможность лучше контролировать результат.
Свойства. Механизм свойств был заимствован C++ Builder из Delphi Pascal и распространен на все создаваемые классы. В Delphi Pascal свойства использовались для определения интерфейса к отдельным полям классов (см. § 5.3). Синтаксис и семантика свойств C++ Builder полностью аналогичны синтаксису и семантике свойств Delphi Pascal. Так же, как в Delphi Pascal, различают: простые свойства, свойства-массивы и индексируемые свойства.
Простые свойства определяются следующим образом: __property <тип свойства> <имя> = {<список спецификаций>};
Список спецификаций может включать следующие значения, перечисляемые через запятую: read = <переменная или имя функции> - определяет имя поля, откуда читается значение свойства, или метода чтения, которая возвращает это значение; если данный атрибут опущен, то свойство не доступно для чтения из программы; write = <константа или имя функции> - определяет имя поля, куда записывается значение свойства, или метода записи, используемой для записи значения в поле; если данный атрибут опущен, то значение свойства в программе менять нельзя; stored = <константа или имя функции логического типа> - определяют, должно ли сохраняться значение свойства в файле формы, этот атрибут используется для визуальных и невизуальных компонентов; default = <константа> или nodefault - определяет значение по умолчанию или его отсутствие. Пример 6.3. Простые свойства (класс Целое число). Пусть требуется разработать класс для хранения целого числа. Этот класс должен обеспечивать возможность чтения и записи значения числа. Опишем доступ к полю, используемому для хранения значения, с помощью свойства Number целого типа. Поместим это описание в файле Object.h: class TNumber { private: int fNum; int GetNum(); // метод чтения свойства void SetNum(aNum); // метод записи свойства public: TNumber(int aNum); // конструктор __property int Number={read=GetNum,write=SetNum}; // свойство };
Соответственно, реализацию методов этого класса поместим в файл Object.cpp:
#include "Object.h" #pragma package (smart_init) TNumber::TNumber(int aNum) { SetNum(aNum);}
Дата добавления: 2014-12-08; Просмотров: 536; Нарушение авторских прав?; Мы поможем в написании вашей работы! Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет |