- •Наследование. Абстрактные классы. Виртуальные функции Контейнерные классы
- •Наследование
- •Простое наследование
- •Спецификаторы доступа базовых классов
- •Порядок вызова конструкторов
- •Порядок вызова деструкторов
- •Разрешение области видимости
- •Теперь вызов функции b.Fin1() приведет к вызову функции fun() класса a. Динамическое связывание в ооп (полиморфизм)
- •Наследование и таблица виртуальных функций
- •Абстрактные базовые классы
- •Виртуальные деструкторы
- •Механизм работы позднего связывания
- •Чистые виртуальные деструкторы
- •Виртуальные функции в деструкторах
- •Перегруженные функции при динамическом связывании
- •Множественное наследование
- •Что необходимо знать
- •Задания
Чистые виртуальные деструкторы
В отличие от чистых виртуальных функций для чистых виртуальных деструкторов необходимо обеспечить тело функции. При реализации деструкторов производных классов их тело задавать не нужно.
//f1.h
#include <iostream.h>
class AbstractBase
{public:
virtual ~AbstractBase()=0;
};
AbstractBase::~AbstractBase()
{cout<<"Destructor Abstract Class"<<endl;}
class Derived: public AbstractBase
{
};
//f1.cpp
#include "f1.h"
void main()
{ Derived d;
}
Виртуальные функции в деструкторах
Механизм позднего связывания не действителен для виртуальных и невиртуальных деструкторов. Вызывается локальная версия виртуальной функции.
//f1.h
#include <iostream.h>
class Base
{public:
virtual ~Base() {cout<<"~Base()"<<endl; f();}
virtual void f() {cout<<"Base::f"<<endl;}
};
class Derived: public Base
{public:
~Derived() {cout<<"~Derived()"<<endl;}
virtual void f() {cout<<"Derived::f"<<endl;}
};
//f1.cpp
#include "f1.h"
void main()
{ Base *bp = new Derived;
delete bp; //"~Derived()","~Base()","Base::f"
}
Перегруженные функции при динамическом связывании
Общее правило перегрузки функций: перегруженная (переопределенная) функция базового класса в производных классах прячет все другие версии этой функции в базовом классе.
//f1.h
#include <iostream.h>
class Base
{public:
virtual int f() {cout<<"Base::f()"<<endl;return 1;}
virtual void f(char *s) {cout<<s<<endl;}
virtual void g() {};
};
class Derived1: public Base
{public:
void g() {cout<<"Derived1::g()"<<endl;}
};
class Derived2: public Base
{public:
int f() {cout<<"Derived2::f()"<<endl;return 2;}
};
/*class Derived3: public Base
{public:
void f() {cout<<"Derived3::f()"<<endl;} //Ошибка, так как компилятор не позволяет изменять тип возвращаемого значения для перегруженной виртуальной функции
};*/
//f1.cpp
#include "f1.h"
void main()
{ char *s;
s="Hello!";
Derived1 d1;
int x=d1.f(); //Base::f()
d1.f(s); //"Helo!"
Derived2 d2;
x=d2.f(); //Direved2:f()
//d2.f(s); //ошибка! функция f не имеет ни одного параметра
Base* b2=&d2;
b2->f();//Direved2:f()
b2->f(s);//Hello!
}
Множественное наследование
Множественное наследование описывает родство между классами, при котором один класс может иметь несколько базовых.
class A {…};
class B {…};
class C {…};
class D: public A, public B, public C {…};
Вызов конструкторов происходит в том порядке, в котором были объявлены базовые классы:
A::A()
B::B()
C::C()
D::D()
//f1.h
class A
{public:
void Display(void) {…}
};
class B
{public:
void Display(void) {…}
};
class C: public A, public B
{public:
void f(void);
};
Если имеем реализацию:
void C::f(void)
{Display();}
то компилятор не будет знать, какую функцию Display() вызывать. Нужно указывать явно.
void C::f(void)
{ A::Display();
B::Display();
}
Конструктор класса, у которого несколько родителей, перед вызовом своего конструктора вызывает все конструкторы базовых классов в порядке их объявления. Их можно вызвать явно:
class C: public A, public B
{public:
C():A(), B() {};
void f(void);
};
Если через объект класса C попытаться обратиться к методам класса W, то компилятор выдаст ошибку. Выходом из этой проблемы будет использование виртуального базового класса
//f1.h
#include <iostream.h>
class W
{public:
virtual void f() {cout<<"W::f()"<<endl;}
virtual void g() {cout<<"W::g()"<<endl;}
virtual void h() {cout<<"W::h()"<<endl;}
};
class A:public virtual W
{public:
void g() {cout<<"A::g()"<<endl;}
};
class B:public virtual W
{public:
void f() {cout<<"B::F()"<<endl;}
};
class C: public A, public B
{public:
void f() {cout<<"C::f()"<<endl;}
};
//f1.cpp
#include "f1.h"
void main()
{ C* pc = new C;
pc->f(); //C::f()
pc->g(); //A::g()
pc->h(); //W::h()
((A*)pc)->f(); //C::f(). pc – указатель на C, насильственно преобразуем его к указателю на A; в описании класса A функции f нет, она описана в базовом классе W, но как виртуальная; далее происходит анализ класса, на который указывает pc, то есть класса C, в нем обнаруживается реализация функции f(), вызов которой и происходит. Если слово virtual перед f() в W убрать, тогда будет вызвана f из W.
((W*)pc)->f(); //C::f()
B* pb = new B;
pb->f(); //B::f()
pb->g(); //W::g();
pb->h(); //W::h()
((W*) pb)->f(); //B:f()
A* pa = new A;
pa->f(); //W::f()
pa->g(); //A::g()
pa->h(); //W::h()
((W*) pa)->g(); //A::g()
}
Для описания иерархий множественного наследования используется прямой ациклический граф. Виртуальные базовые классы инициализируются перед любыми невиртуальными базовыми классами в том порядке, в котором они появляются в прямом ациклическом графе наследования при просмотре его снизу вверх и слева направо. Для приведенного выше примера порядок вызова конструкторов следующий: W(), A(), B(), C().
class A {};
class B: public virtual A {};
class C: public virtual A {};
class D: public A {};
class E: public B, public C, public D {};
Дерево наследования приведенной выше иерархии изображено на рисунке ниже.
Порядок вызова конструкторов для класса E таков: A(), B(), C(), D(), E().
Ключ доступа |
Спецификатор базового доступа |
Доступ в производном классе |
Комментарий |
private |
private protected public |
private private private |
все private |
protected |
private protected public |
private protected protected |
все protected, кроме private |
public |
private protected public |
private protected public |
не изменяется |
