1 Одиночное наследование
1.1 Понятие наследования
При большом количестве никак не связанных классов в программе, управлять ими становится практически невозможным (так же, как и большим количеством функций в большом проекте). Наследование позволяет справиться с этой проблемой путем упорядочивания и ранжирования классов, то есть объединения общих для нескольких классов свойств в одном классе и использования его в качестве базового.
Наследование - механизм передачи свойств одних классов другим классам в процессе проектирования иерархии классов. Исходные классы называют базовыми(БК) (родителями), вторые - производными (ПК) (потомками).
Наследование может быть одиночным и множественным, в зависимости от числа родителей.
Классы, находящиеся ближе к началу иерархии, объединяют в себе наиболее общие черты для всех нижележащих классов. По мере продвижения вниз по иерархии классы приобретают все больше конкретных черт. Множественное наследование позволяет одному классу обладать свойствами двух и более родительских классов.
Пример наследования
Пример с наследованием
class Box // тип ``ящик``
{
protected:
int width,height;
public:
void SetWidth(int w) { width=w; }
void SetHeight(int h) { height=h; }
};
class ColoredBox : public Box // ``цветной ящик``
{
int color;
public:
void SetColor(int c) { color=c; }
};
1.2 Ключи наследования
При описании производного класса в его заголовке перечисляются все базовые для него классы. Доступ к элементам этих классов регулируется с помощью ключей доступа: private, protected и public. Если базовых классов несколько, они перечисляются через запятую.
Использование ключей доступа вместе с разделами внутри БК позволяет сформировать правило, по которому будет осуществляться доступ к членам ПК.
Ключ доступа | Раздел БК | Раздел ПК |
private | private | доступ через БК |
protected | private | |
public | private | |
protected | private | доступ через БК |
protected | protected | |
public | protected | |
public | private | доступ через БК |
protected | protected | |
public | public | |
Из таблицы следует, что если, например, в БК некоторая переменная располагалась в разделе public, а ПК был объявлен с ключом private, то в ПК к данной переменной можно будет обращаться только членам ПК или его друзьям (эта переменная перейдет в раздел private ПК).
Когда происходит наследование без явного указания спецификатора, все имена базового класса в производном классе автоматически становятся приватными (или можно указать private).
Если наследовать с ключевым словом public - все общедоступные имена базового класса будут общедоступными в производном классе и все защищенные имена будут защищенными в производном классе.
1.3 Правила
Нельзя наследовать
- конструкторы
- деструкторы
- перегруженные new
- перегруженные =
- отношения дружественности
Правила для специальных методов
- Поскольку конструкторы не наследуются, ПК должен иметь собственные конструкторы.
- Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, автоматически вызывается конструктор БК.
- Для иерархии, состоящей из нескольких классов, конструкторы БК вызываются начиная с самого верхнего уровня. После этого выполняются конструкторы элементов класса, являющихся объектами, а затем - конструктор класса.
- В случае нескольких БК их конструкторы вызываются в порядке объявления.
- Для деструкторов эти правила справедливы, но порядок вызова обратный - сначала ПК, затем БК. Не требуется явно вызывать деструкторы, поскольку они будут вызваны автоматически.
Передача параметра в конструктор БК
Данная ситуация возникает в том случае, когда конструктор БК должен быть вызван с параметром (параметрами). При записи конструктора ПК необходимо указать через двоеточие имя конструктора БК со списком фактических параметров.
class X {
int a,b,c;
public:
X(int x,int y,int z) { a=x; b=y; c=z; }
};
class Y : public Х {
int val;
public:
Y(int d) : X(d,d+1,d+5) { val=d; }
Y (int d, int e);
}
Y::Y(int d, int e) : X(d,e,12) {
val=d+e;
}
1.4 Производные классы
class Employee {
public:
Employee(string _name,
string _surname);
~Employee();
void hire(Date d);
void fire(Date d);
string name() const;
string surname() const;
void print() const;
private:
string name, surname;
Date hire_date, fire_date;
};
class Programmer: public Employee
{
public:
Programmer(string _name,
string _surname,
string _team);
~Programmer();
void set_team (string _team);
string team() const;
void print() const;
private:
string team;
};
Employee::Employee()
Employee::~Employee()
Employee::hire()
Employee::fire()
Employee::name()
Employee::surname()
Employee::print()
Programmer::Programmer()
Programmer::~Programmer()
Programmer::set_team()
Programmer::team()
Programmer::print()
Programmer::Employee::hire()
Programmer::Employee::fire()
Programmer::Employee::name()
Programmer::Employee::surname()
Programmer::Employee::print()
Date start_date(1,1,2004), end_date(31,12,2007);
Employee emp("Vasya", "Pupkin");
emp.hire(start_date);
cout << emp.name();
cout << emp.surname();
emp.print()
emp.fire(end_date);
Programmer prog("Petr", "Petrov", "GM00");
prog.hire(start_date);
prog.set_team("GM12");
cout << prog.name();
cout << prog.surname();
cout << prog.team();
prog.print();
prog.Employee::print()
prog.fire(end_date);
Производные классы и указатели
Programmer *prog1 = new Programmer("Petr", "Petrov", "GM12");
Employee *emp1 = prog1; // хорошо
Employee *emp2 = new Employee("vasya", "Pupkin");
Programmer *prog2 = emp2; // ошибка
prog2->set_team("GM00"); // нет team
void test_function(Employee& emp);
Programmer prog3("Ivan", "Ivanov", "GM00");
test_function (prog3); // хорошо
С объектом производного класса можно обращаться как с объектом базового класса при обращении к нему при помощи указателей и ссылок.
1.5 Функции-члены
class Employee {
string name, surname;
//...
public:
void print() const;
string surname() const;
};
class Programmer: public Employee { //...
public:
void print() const;
//...
};
void Programmer::print() const
{
cout << surname() << endl;
}
void Programmer::print() const
{
cout << surname << endl;
}
void Programmer::print() const
{
Employee::print();
cout << team << endl;
}
int main()
{
Employee emp("Vasya", "Ivanov");
Programmer prog("Petr", "Petrov", "GM12");
emp.print(); // Vasya Ivanov
emp.surname(); // Ivanov
prog.print(); // Petr Petrov GM12
prog.surname(); // Petrov
}
1.6 Конструкторы
class Employee {
string name, surname;
public:
Employee(const string&,
const string&);
};
class Programmer:
public Employee
{
string team;
public:
Programmer(const string&,
const string&,
const string&);
//...
};
Employee::Employee(const string& n,
const string& sn)
: name(n), surname(sn)
{ /*...*/ }
Programmer::Programmer(
const string& _name,
const string& _surname,
const string& _team) :
Employee(_name, _surname),
team(_team)
{
//...
}
class Employee {
string name, surname;
public:
Employee(const string&,
const string&);
};
class Programmer:
public Employee {
string team;
public:
Programmer(const string&,
const string&,
const string&);
//...
};
Programmer::Programmer(
const string& name,
const string& sname,
const string& t) :
name(name),
surname(sname),
team(t)
{
//...
}
1.7 Копирование
class Employee {
//...
Employee(const Employee&);
Employee& operator=(const Employee&)
};
void f(const Programmer& rPrg)
{
Employee emp = rPrg;
emp = rPrg;
};
Копируется только Employee-часть Programmer – срезка.
class Employee {
string name, surname;
public:
Employee(const Employee&);
Employee& operator=(const Employee&)
//...
};
class Programmer: public Employee {
string team;
public:
Programmer(const Programmer &);
Programmer& operator=(const Programmer &)
//...
};
Programmer::Programmer (const Programmer& rp)
: Employee(rp), team(rp.team)
{
}
Programmer& Programmer::operator=(const Programmer &rp)
{
Employee::operator=(rp);
team = rp.team;
}
- operator= не наследуется
- Конструкторы не наследуются
1.8 Иерархия
Иерархия классов

class Employee {/*...*/};
class Programmer: public Employee
{/*...*/};
class Team_leader: public Programmer
{/*...*/};
class Proj_manager: public Employee
{/*...*/};
class Senior_Manager: public Proj_manager
{/*...*/};
class HR_manager: public Employee
{/*...*/};
class Team_leader: public Programmer {
public:
Team_leader(string n, string fn, string t);
bool add_designer(Programmer*);
bool rm_designer(string fn, string n);
Programmer* get_designer(string fn, string n) const;
private:
list<Programmer*> team_list;
};
Team_leader::Team_leader(string n,string fn, string t) :
Programmer(n, fn, t),team_list()
{}
...
Team_leader tm("Igor", "Kotov", "GM12");
tm.hire(Date(20,3,2008));
cout << tm.name();
tm.set_team("GM18");
tm.add_designer(p);
tm.fire(Date());
2 Виртуальные функции
2.1 Введение
Виртуальные функции
class Employee {
public:
Worker(string _name,
string _surname);
// ...
virtual void print() const;
private:
string name, surname;
Date hire_date, fire_date;
};
class Programmer:
public Employee
{
public:
Programmer(string _name,
string _surname,
string _team);
virtual void print() const;
private:
string team;
};
void Employee::print() const
{
cout << first_name << " " << surname << endl;
}
void Programmer::print() const
{
Employee::print();
cout << "team: " << team << endl;
}
void print_emp(const Employee* pEmp)
{
cout << "Employee info:" << endl;
pEmp->print();
}
int main()
{
Employee emp("Vassya", "Pupkin");
Programmer prog("Ivan", "Sidorov", "GM12");
print_emp ( &emp );
print_emp ( &prog );
return 0;
}
2.2 Реализация
Таблица виртуальных функций

2.3 Примеры использования
Когда используется виртуальность
Employee emp("Vasya", "Pupkin");
Programmer prog("Ivan", "Sidorov", "GM12");
emp.print(); // нет, Employee::print()
prog.print(); // нет, Programmer::print()
void fn1(Employee *p)
{
p->print(); // да
p->hire(); // да
}
void fn2(Employee &r)
{
r.print(); // да
r.fire(); // да
}
Механизм виртуальности используется, только когда вирт. функция вызывается через указатель или ссылку на базовый класс.
// массив указателей на Employee, размер
void give_a_bonus(Employee *list[], int size)
{
for(int i=0; (i<size && list[i]); ++i)
list[i]->bonus();
}
void create_lucky_list_and_give_bonus()
{
Employee **list = new (Employee*) [10];
for(int i=0; i<10; ++i)
list[i] = next_lucky_man();
give_a_bonus(list);
}
class Unit {
public:
virtual bool action()
{return false};
};
class Soldier: public Unit {/*...*/};
class Tank : public Unit {/*...*/};
class Mine : public Unit {/*...*/};
void Field::turn()
{
for(int i=0; i<unit_number; ++i)
if ( units[i]->action() != true)
move_to_end(units[i]);
}
class Field
{
public:
Field();
~Field();
void refresh_field();
void turn();
void move_to_end(Unit*);
private:
int unit_number;
Unit **units;
//...
}
2.4 Абстрактные классы
class Cosmetics {
public:
virtual void make_up() = 0;
virtual void touch_up() = 0;
virtual void remove() = 0;
};
class Lipstick :
public Cosmetics {
public:
virtual void make_up();
virtual void touch_up();
virtual void remove();
};
class Mascara :
public Cosmetics {
public:
virtual void make_up();
virtual void touch_up();
};
class WaterRes_Mascara :
public Mascara {
public:
virtual void remove();
};
class Plain_Mascara :
public Mascara {
public:
virtual void remove();
};
Cosmetics cosmo; // ошибка
Lipstick lips;
Mascara masc; // ошибка
WaterRes_Mascara wr_masc;
Plain_Mascara plain_masc;
void complete_touch_up(Cosmetics* todo[], int n)
{
for (int i=0; (i<n && todo[i]); ++i)
{
todo[i]->touch_up();
}
}
class Shaver { // бритва
public:
virtual void shave() = 0;
virtual void reload() = 0;
virtual void clean() = 0;
};
class Electric_Shaver :
public Shaver {
public:
virtual void shave();
virtual void reload() {}
virtual void clean();
};
class Razor : // станок
public Shaver {
public:
virtual void clean();
};
class Safe_Razor :
public Razor {
public:
virtual void shave();
virtual void reload();
};
class Blade : // лезвие
public Razor {
public:
void shave();
virtual void reload() {...}
};
- Абстрактный класс – это чистый интерфейс
- Класс асбтрактный, если есть хотя бы одна чисто виртуальная функция (=0)
- Нельзя создать экземпляр абстрактного класса
- Чисто виртуальная фнкция, которая не определена в производном классе, остается чисто виртуальной
2.5 Виртуальные конструкторы и деструкторы
Виртуальные деструкторы
class Employee
{
public:
Employee(string _name,
string _surname);
virtual ~Employee();
};
class Programmer:
public Employee
{
public:
Programmer(/*...*/);
virtual ~Programmer();
};
void destroy_container(Empolyee **container, int size)
{
for(int i=0; i < size; ++i) {
delete container[i];
container[i] = 0;
}
}
Виртуальные ''конструкторы''
На самом деле, таких не существует!
class Employee
{
public:
virtual Employee* new_employee() {return new Employee();}
virtual Employee* clone() {return new Employee(*this);}
};
class Programmer
{
public:
virtual Programmer* new_employee() {return new Programmer();}
virtual Programmer* clone() {return new Programmer(*this);}
};
3 Стратегии наследования
Защищенные члены
class Employee {
string name, surname;
protected:
void retire();
//...
};
class Programmer:
public Employee {
int team;
public:
~Programmer() { retire(); }
//...
};
void ret(Employee &re,
Programmer &rp) {
re.retire(); // Ошибка!
rp.retire(); // Ошибка!
}
class Proj_Manager:
public Employee
{ /*...*/};
class HR_Assistant:
public Employee {
void fire(Proj_Manager *p) {
p->retire(); // Ошибка!
retire();
}
};
- Производный класс имеет доступ к защищенным членам базового (но только для объектов собственного типа)
- Защищенные данные приводят к проблемам сопровождения
- Защищенные функции - хороший способ задания операций для использования в производных классах
Права доступа Public
class Employee {
public:
string name() const;
protected:
void retire();
};
class Programmer:
public Employee {
/*…*/
};
class Team_Leader:
public Programmer{
/*…*/
};
void Team_Leader::~Team_Leader()
{
retire();
}
void f(Employee *emp,
Programmer *prog,
Team_Leader *tleader)
{
prog->name();
tleader->name();
prog->retire(); // Ошибка!
emp = prog;
prog = tleader;
emp = tleader;
}
Права доступа Private
class Stack {
public:
void push(char);
char pop();
protected:
int max_size();
int cur_size();
private:
int max_size;
//...
};
class Tough_Stack:
private Stack {
public:
void put(char c) {
push(c);
}
char get() {
if (cur_size()>0)
return pop();
}
};
void f(Tough_Stack *p) {
p->put('a');
p->pop(); // Ошибка!
Stack *pbase = p; // !
pbase->pop(); // !
}
class Semi_Tough_Stack :
public Tough_Stack {
public:
char pop(Tough_Stack *p) {
Stack *pbase = p; // !
return pbase->pop(); // !
}
};
Правила доступа Protected
class Unit {
public:
bool move (int x, int y);
bool fire(int x, int y);
bool no_ammo();
bool reload();
void retreat();
void wound(int precent);
private:
int X, Y;
int ammo;
int magazine;
int health;
};
class Soldier: protected Unit {
public:
bool move (int x, int y);
bool shoot(int x, int y);
void defend();
protected:
void wound(int precent);
};
bool Soldier::move(int x,
int y)
{
return Unit::move(x,y);
}
bool Soldier::shoot(int x,
int y)
{
if (no_ammo())
{ if (reload()==false)
return false;
}
return fire(x,y);
}
void Soldier::wound(int precent)
{
Unit::wound(precent);
if (health()<20)
retreat();
}
void madness()
{
Soldier sol;
if (!sol.shoot(x,y))
{
sol.retreat(); // Ошибка!
}
if (sol.no_ammo()) // !
{
s1.wound(100); // Ошибка!
}
}
Доступ к базовым классам
- Открытое наследование делает производный класс подтипом базового
- Защищенное и закрытое наследование используются для выражения деталей реализации
- Защищенные базовые классы полезны в иерархиях с дальнейщим наследованием
- Закрытые базовые классы полезны для “ужесточения интерфейса”
4 Множественное наследование
4.1 Понятие множественного наследования
Множественное наследование
class Storable_Process :
public Process,
public Storable {
//...
};
void f(Storable_Process& rSP)
{
rSP.read(); // Storable::read()
rSP.run(); // Process::run()
rSP.dump(std::cerr);
rSP.stop(); // Process::stop()
rSP.write(); // Storable::write()
}
void start(Process*);
bool check_filename(Storable*);
void susp(Storable_Process* pSP)
{
if ( check_filename(pSP) )
{
start(pSP);
}
}
Виртуальные функции работают как обычно
class Process {
//...
virtual void pending() = 0;
};
class Storable {
//...
virtual void prepare() = 0;
};
class Storable_Process :
public Process,
public Storable {
//…
virtual void pending();
virtual void prepare();
};
Разрешение неоднозначности
class Process {
//...
virtual debug_info*
get_debug();
};
class Storable {
//...
virtual debug_info*
get_debug();
};
void f(Storable_Process* pSP)
{
debug_info* dip=
pSP->get_debug();
dip= pSP->Storable::
get_debug();
dip= pSP->Process::
get_debug();
}
class Storable_Process :
public Process,
public Storable {
//...
virtual debug_info*
get_debug()
{
debug_info* d1 =
Storable::get_debug();
debug_info* d2 =
Process::get_debug();
return d1->merge(d2);
}
};
Пример
class Drink {
public:
virtual Liquid* drink() =0;
};
class Bottle {
public:
void fill(Liquid*);
Liquid* pour(Volume);
void open();
void break();
bool opened() const;
bool empty() const;
};
class BBeer: public Drink,
protected Bottle {
/*...*/ };
Liquid* BBeer::drink()
{
if (!opened()) open();
if (!empty())
return pour(VOL_GULP));
return NULL;
}
void get_drunk(BBeer* beer,
Human *man)
{
beer->break();
man->consume(beer->drink());
Bottle *bottle = &beer;
bottle->break();
}
bool Human::get_drunk(Drink* alc[], int num)
{
for(int i=0; i<num || i_am_drunk() ; ++i)
{
Liquid *p = 0;
while( (p=acl[i]->drink()) !=0 )
{
consume(p);
}
}
return i_am_drunk();
}
4.2 Дублирование
class Link {
//...
Link* next();
};
class Task_Queue: public Link {
//...
};
class Processors: public Link {
//...
};
class Distributor : public Task_Queue,
public Processors {
//...
};

Для устранения дублирования используют виртуальные базовые классы.
class Storable {
public:
Storable(const char*)
virtual void read() =0;
virtual void write() =0;
virtual ~Storable() {
write();
}
private:
const char* store;
};
class Transmitter:
public virtual Storable {
//…
virtual void write();
};
class Receiver:
public virtual Storable {
//…
virtual void write();
};
class Radio :
public Transmitter,
public Receiver {
Radio() : Storable(“radio.stor”)
{}
virtual void write() {
Transmitter::write();
Receiver::write();
}
};
