310 likes | 466 Views
Производные классы. Определение класса посредством добавления возможностей к уже имеющемуся классу без перепрограммирования или перекомпиляции самого класса. Построение производного класса. struct employee // служащий { char* name; // имя short age; // возраст short
E N D
Производные классы • Определение класса посредством добавления возможностей к уже имеющемуся классу без перепрограммирования илиперекомпиляции самого класса
Построение производного класса struct employee // служащий { char* name; // имя short age; // возраст short department; // подразделение int salary; // жалованье employee* next; // ... };
struct manager // менеджер { employee emp; // запись о менеджере как о служащем employee* group; // подчиненные люди // ... }; • Однако указатель на менеджера (manager*) не является указателем на служащего (employee*)
Корректный подход состоит в том, чтобы установить, что менеджер является служащим с некоторой добавочной информацией: struct manager : employee { employee* group; // ... }; • manager является производным от employee • employee есть базовый класс для manager
Создадим список служащих, некоторые из которых являются менеджерами: void f() { manager m1, m2; employee e1, e2; employee* elist; elist = &m1; // поместить m1, e1, // m2 и e2 в elist m1.next = &e1; e1.next = &m2; m2.next = &e2; e2.next = 0; } • Поскольку менеджер является служащим, manager* может использоваться как employee*. Однако служащий необязательно является менеджером, поэтому использовать employee* как manager* нельзя
Функции члены class employee { char* name; // ... public: employee* next; void print(); // ... }; class manager : public employee { // ... public: void print(); // ... };
Вопросы: • Как может функция-член производного класса manager использовать члены его базового класса employee? • Как члены базового класса employeeмогут использовать функции члены производного класса manager? • Какие члены базового классаemployee может использовать функция не член на объекте типаmanager?
void manager::print() { cout << " имя " << name << "\n"; // ... } • Функция-член производного класса не имеет никакого особого права доступа к закрытым членам его базового класса, поэтому для нее name недоступно • Возможность, позволяющая программисту получать доступ к закрытой части класса с помощью производного класса, лишила бы понятие закрытого члена всякого смысла
void manager::print() { employee::print(); // печатает информацию о служащем // ... // печатает информацию о менеджере } void manager::print() { print(); // последовательность // бесконечных рекурсивных // вызовов // ... // печатает информацию о менеджере }
Каждая секция внутри класса начинается с одного из ключевых слов: private, protected, public сlass class_name { private: protected: public: };
private - приватные имена имеют наиболее ограниченный доступ, разрешенный только методам (функциям-членам) данного класса. Доступ производных классов к приватным методам базовых классов запрещен. Можно определить закрытый (private) класс опустив в описании класса слово public: protected - защищенные имена имеют доступ, разрешенный методам данного и производного от него класса public - общедоступные имена имеют неограниченный доступ, разрешенный методам всех классов и их объектов
Секции могут появляться в любом порядке, а их названия могут встречаться повторно • Если секция не названа, компилятор считает последующие объявления имен класса приватными (private). Структура - класс у которого все элементы общедоступны • По мере возможности не помещайте данные в общедоступную секцию (public), если только вы не хотите разрешить доступ к ним отовсюду. Можно объявляют защищенными (protected), чтобы разрешить доступ только методам производного класса • Используйте методы для выборки, проверки и установки значений свойств и членов данных • Конструкторы и деструкторы являются специальными функциями, которые не возвращают значения и имеют имя своего класса • Функции, которые содержат более одной инструкции C++, рекомендуется объявлять вне класса
Видимость • В производном классе возможен доступ только к открытым (public) и защищенным (protected) членам базовых классов • Доступ к закрытым (private) элементам не возможен, хотя они и становятся частью производного класса
Класс employeeстал открытым (public) базовым классом классаmanagerв результате описания: class manager : public employee { // ... }; • Это означает, что открытый член класса employee является также и открытым членом класса manager. Например: void clear(manager* p) { p->next = 0; };
Альтернатива - можно определить закрытый (private) класс, просто опустив в описании класса слово public: class manager: employee { // ... }; • Это означает, что открытый член класса employee является закрытым членом класса manager. То есть, функции члены класса manager могут как и раньше использовать открытые члены класса employee, но для пользователей класса manager эти члены недоступны. В частности, при таком описании класса manager функция clear() компилироваться не будет
Друзья производного класса имеют к членам базового класса такой же доступ, как и функции члены • Поскольку, как оказывается, описание открытых базовых классов встречается чаще описания закрытых, жалко, что описание открытого базового класса длиннее описания закрытого. Это, кроме того, служит источником запутывающих ошибок у начинающих • Когда описывается производная struct, ее базовый класс по умолчанию является public базовым классом. То есть, struct D : B { ... означает class D : public B { public: ...
Можно также объявить некоторые, но не все, открытые члены базового класса открытыми членами производного класса. Например: class manager : employee { // ... public: // ... employee::name; employee::department; }; • Запись имя_класса::имя_члена; не вводит новый член, а просто делает открытый член базового класса открытым для производного класса. Теперь name иdepartment могут использоваться для manager, а salary и age - нет
Указатели и производные классы • Если производный класс derived имеет открытый базовый класс base, то указатель на derived можно присваивать переменной типа указатель на base не используя явное преобразование типа • Обратное преобразование, указателя на base в указатель на derived, должно быть явным • Если производный класс derived имеет закрытый базовый класс base, то указатель на derived нельзя присваивать переменной типа указатель на base не используя явное преобразование типа
Например: class base { /* ... */ }; class derived : public base { /* ... */ }; derived m; base* pb = &m; // неявное преобразование derived* pd = pb; // ошибка: base* // не является derived* pd = (derived*)pb; // явное преобразование
Иерархия Типов • Производный класс сам может быть базовым классом. Например: class employee { ... }; class secretary : employee { ... }; class manager : employee { ... }; class temporary : employee { ... }; class consultant : temporary { ... }; class director : manager { ... }; class vice_president : manager { ... }; class president : vice_president { ... }; • Такое множество родственных классов принято называть иерархией классов
Конструкторы • Конструкторы не наследуются. Если конструктор базового типа требует спецификации одного или нескольких параметров, конструктор базового класса должен вызывать базовый конструктор, используя список инициализации элементов class base { // ... public: base(int, float); ~base(); }; class derived : public base { public: derived (char* lst, float=1.000); ~derived(); }; derived::derived(char* lst,float amt): base(strlen(lst), amt) { // ... }
Деструкторы • Деструктору производного класса, напротив, не требуется явно вызывать деструктор базового класса. В деструкторе производного класса компилятор автоматически генерирует вызовы базовых деструкторов • Объекты класса конструируются снизу вверх: сначала базовый, потом члены, а потом сам производный класс. Уничтожаются они в обратном порядке: сначала сам производный класс, потом члены а потом базовый
Множественное наследование • В С++ допускается множественное наследование, когда класс является производным от нескольких базовых классов. Это позволяет в одном классе сочетать поведение нескольких классов • Следующий пример показывает это. Класс Сoord отслеживает координаты x,y. Класс Message хранит сообщение. Класс MessageXY, производный от этих двух классов, наследует контроль как над координатами, так и над сообщением
#include <stdio.h> #include <conio.h> #include <string.h> const int MAX_LEN = 10; class Coord { protected: int x,y; // Защищенный возможен // доступ из наследуемых // классов) public: Coord(int _x=0, int _y=0) {SetLoc(_x, _y);} void SetLoc(int _x, int _y) { x=_x; y=_y; } };
class Message { protected: char msg[Max_LEN]; public: void SetMsg(char *_msg) { strcpy(msg,_msg); } }; class MessageXY: public Coord,public Message { public: void Show(); }; // Выводит сообщение в текущей позиции void MessageXY::Show() { gotoxy(x,y); printf(msg); }
int main(void) { MessageXY greeting; greeting.Setloc(10,10); greeting.SetMsg("Hello .."); greeting.Show(); return 0; }
Рассмотрим иерархию классов двух простых геометрических объектов: окружности и цилиндра • Базовый класс Circle моделирует окружность, а производный класс Cylinder моделирует цилиндр
const double pi = 4*atan(1); class Circle { protected: double r; public: Circle(double rVal = 0){ r = rVal; } void setRadius(double r Val){ r = rVal; } double getRadius(){ return r; } double Area(){ return pi*r*r;} // Площадь круга void showData(); };
class Cilinder : public Circle { protected: double h; public: Cylinder(double hVal =0, double rVal =0) { getHeight(hVal), Circle(rVal);} void setHeight(double hVal) { h= hVal; } double getHeight() { return h; } double Area() { return 2*Circle::Area()+2*pi*r*h; } // Площадь поверхности цилиндра void showData(); };
void Circle::showData() { cout << "Радиус окружности = " << getRadius() << endl << "Площадь круга = " << Area() << endl; } void Cylinder::showData() { cout << "Радиус основания = " << getRadius() << endl << "Высота Цилиндра = " << getHeight() << endl << "Площадь поверхности = " << Area() << endl; }
void main() { Circle circle(2); Cylinder cylinder(10,1); circle.showData(); cylinder.showData(); } Радиус окружности = 2 Площадь круга = 12.566 Радиус основания =1 Высота цилиндра = 10 Площадь поверхности = 69.115