
- •Лекция №1 «Современные средства разработки по. Терминология. Основные понятия.»
- •Проектирование приложения.
- •Реализация программного кода приложения.
- •Тестирование приложения.
- •Лекция 2. История развития ооп и сред rad
- •Преимущества ооп
- •Среды разработки, использующие принципы rad
- •Лекция №3 ооп. Понятия объекта, свойства, метода, события.
- •Лекция №4 Классы
- •4.1 Объявление класса
- •4.2. Перегрузка функций.
- •4.3. Конструкторы.
- •4.4. Деструкторы.
- •4.5. Конструкторы с параметрами.
- •4.6. Указатели на объекты.
- •Лекция №5 Данные-элементы, статические данные, константные данные
- •6. Функции-элементы, дружественные функции, константные функции
- •Inline void SetA(int); // функция записи
- •Встраиваемые функции inline
- •3.3. Введение в наследование.
- •Виртуальные функции.
- •Производные классы: множественное наследование
Производные классы: множественное наследование
Иерархию простого наследования можно описать, используя структуру дерева, где каждый узел представляет подкласс, который может порождать любое количество дополнительных подклассов. Как и в случае простого наследования, определители private, protected, public в родительском классе можно использовать для управления доступом к экземплярам переменных и методам, которые унаследованы производным классом (подклассом) от базового (родительского) класса. Кроме того, спецификатор public или private-производного класса, как и при одиночном наследовании, определяют производные классы, объекты которых имеют простой доступ к открытым данным или функциям-членам базового класса.
Для описания иерархии множественного наследования можно использовать прямой ациклический граф (ПАГ) (Ориентированный граф наследования без петель). Подкласс может унаследовать данные и методы одного или более родительских классов. При этом помимо спецификаторов public и private-производных классов, используется дополнительная опция virtual.
Преимущества и недостатки каждого из видов наследования видны из этих сравнительных примеров:
В случае наследования классов библиотеки VCL накладывается ограничение на множественное наследование, т.е. у любого класса этой библиотеки может быть один единственный предок на соседнем верхнем уровне иерархии. Несмотря на то, что в этом случае нет путаницы относительно дублирования наследуемых свойств и методов, видно, что появляется избыточность кода и большее количество предков в иерархии. Т.к. некоторые компоненты имеют ближайших аналогов в нескольких параллельных ветвях графа, а поскольку множественное наследование запрещено, приходится вместо наследования необходимых свойств и методов у аналогов вносить дополнительный программный код и включать в число предков классы, относящиеся к данной иерархии, даже если их свойства нет необходимости наследовать. Например, при создании кнопки с изображением нужны свойства 2-х классов TButton и TGraphicControl. В случае множественного наследования можно было бы сделать проектируемый класс наследником этих 2-х. Однако, при запрещении множественного наследования приходится выбирать от какого класса каких свойств и методов наследуется больше и сделать наследование от него, а остальную часть кода вносить в класс дополнительно.
Использование множественного наследования имеет свои преимущества и недостатки. Например, имеется задача создать следующую иерархию классов:
Свойства и методы необходимые для создания проектируемого класса задаются в соответствии с логикой развития и появления необходимых качеств класса. Однако в случае дублирования этих свойств и методов происходит их дублирование в наследовании, т.е. в дочернем классе всегда будут присутствовать одни и те же копии свойств и методов родительских классов, что может порождать неоднозначность их трактовки. Например, свойства скорость и грузоподъемность будут унаследованы классами гидроплан и его потомками в двух экземплярах, причем отличить их в случае одинаковых имен компилятору будет невозможно.
Первый пример
Класс Derived является производным от обоих базовых классов Base1 и Base2.
_______________________________________________________
class Base1 {
int id;
public:
Base1(void) {
cout << “ Constructor Base1”;
id=0;
}
Base1(int anid) {
cout << “ Constructor Base1”;
id=anid;
}
void assignid(int anid) {
id=anid;
}
int accessid(void) {
return id;
}
};
________________________________________________________
class Base2 {
char name[20];
public:
Base2(void) {
cout << “ Constructor Base2”;
strcpy(name,”void”);
}
Base2(char *str) {
cout << “ Constructor Base2”;
strcpy(name,str);
}
void assignName(char *str){
strcpy(name,str);
}
char *accessName(void) { return name; }
};
__
_____________________________________________________
class Derived: public Base1,public Base2 {
char ch;
public:
Derived(void) {
cout << “ Construct Derived”;
ch=‘a’;
}
Derived(char c,int anid,char *str):Base1(anid),Base2(str) {
cout << “ Construct Derived”;
ch=c;
}
void assign(char c) {
ch=c;
}
friend ostream& operator<<(osream& o,Derived& d);
};
ostream& operator<<(ostream& o,Derived& d) {
o<<“id”<<d.accessid()<<“name”<<d.accessName() <<“ ch”<<c;
return o;
}
_________________________________________________
main() {
Derived object1;
cout << object1;
Derived object2(‘e’,26,”Robert”);
cout << object2;
}
Результаты работы программы
Constructor Base1 Constructor Base2 Construct Derived id 0 name void ch a
Constructor Base1 Constructor Base2 Construct Derived
id 26 name Robert ch e
Сначала создается класс Base1, содержащий закрытую целую переменную, два инициализирующих конструктора (по умолчанию и с параметром), показывающих свой вызов выводом соответствующей строки, и две интерфейсные функции (чтения и записи). Класс Base2 имеет аналогичную функциональность для закрытого символьного массива.
В объявлении производного класса после двоеточия перечисляются все предки этого класса через запятую. Конструктор по умолчанию инициализирует скрытый символ ch, а конструктор с параметрами сопровождается вызовом конструкторов с параметрами классов предков (они перечисляются после двоеточия). Вызов конструктора так же сопровождается выводом надписи на экран. Кроме этого, определяется дружественный оператор вставки в поток <<, который осуществляет вставку в поток ostream данных класса Derived.
В функции main последовательно создается один объект и вывод данных объекта по умолчанию object1 и один объект и вывод данных объекта с параметрами object2.
Когда object1 объявлен при помощи выражения Derived object1, вызываются void-конструкторы двух базовых классов в той последовательности, в которой определено наследование ( Base1-конструктор первый, Base2-конструктор второй).
Во втором случае при создании экземпляра object2 вызов конструкторов с параметрами родительского класса так же определяется порядком наследования, т.е. сначала Base1 (anid)затем Base2(str) и Derived(char c,int anid,char *str)
________________________________________________________
Возможны варианты
1. Что, если переписать конструктор Derived c тремя параметрами, как приведено ниже:
Derived(char c,int anint,char *str): Base2(str),Base1(anint) { ch=c; }
Результат работы программы будет таким же.
2. Что, если переписать перегруженный оператор <<, как показано:
ostream & operator <<(ostream&o,Derived& d) {
o<<“id”<<d.id<<“name”<<d.name<<“ ch”<<d.c; return o;
}
Компилятор выдаст сообщения об ошибке. Производный класс в иерархии множественного наследования не имеет доступа к защищенным экземплярам переменных любых его базовых классов. Оператор вывода << является дружественным классу Derived, но не Base1 и Base2. Поэтому он не имеет прямого доступа к защищенным членам Base1 и Base2.
Для корректной работы программы необходимо экземпляры переменных в классах Base1 и Base2 перенести в защищенные секции и изменить перегруженный оператор <<, как предложено выше. Т.к. защищенные экземпляры переменных одного или более базовых классов полностью доступны производным классам.