Производные классы

advertisement
Производные классы

Определение класса посредством добавления возможностей к уже
имеющемуся
классу
без
перекомпиляции самого класса
перепрограммирования
или
Построение производного класса
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
Download