Производные классы Определение класса посредством добавления возможностей к уже имеющемуся классу без перекомпиляции самого класса перепрограммирования или Построение производного класса struct employee { char* name; short age; department; int salary; // ... }; // служащий // // // // имя возраст short подразделение жалованье 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 - общедоступные имена имеют неограниченный доступ, разрешенный методам всех классов и их объектов 1. 2. 3. Секции могут появляться в любом порядке, а их названия могут встречаться повторно Если секция не названа, компилятор считает последующие объявления имен класса приватными (private). Структура - класс у которого все элементы общедоступны По мере возможности не помещайте данные в общедоступную секцию (public), если только вы не хотите разрешить доступ к ним отовсюду. Можно объявляют защищенными (protected), чтобы разрешить доступ только методам производного класса 4. 5. Используйте методы для выборки, проверки и установки значений свойств и членов данных 6. Конструкторы и деструкторы являются специальными функциями, которые не возвращают значения и имеют имя своего класса 7. Функции, которые содержат более одной инструкции 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 class class class class class class class employee { ... }; secretary : employee { ... }; manager : employee { ... }; temporary : employee { ... }; consultant : temporary { ... }; director : manager { ... }; vice_president : manager { ... }; 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 void SetLoc(int _x, }; Защищенный возможен доступ из наследуемых классов) _y=0) {SetLoc(_x, _y);} 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