Семинар 3. Наследование и полиморфизм
Для эффективной разработки программ удобно использовать иерархическое упорядочение понятий и объектов. Такое упорядочение позволяет легче справляться со сложностью разрабатываемых программ, сделать логику их работы более простой и понятной. Иерархия понятий реализуется в виде древовидной структуры, в основе которой лежит наиболее общее понятие. В языке С++ подобная структура реализуется через механизм производных классов, наследующих свойства базовых классов.
Производные классы – развитие классов, определенных ранее, имеющие доступ к protected и public части базового класса. Производные классы наследуют все свойства базовых классов, при этом некоторые свойства могут быть запрещены к наследованию, другие – можно изменить при наследовании, а третьи – могут быть добавлены к свойствам базового класса.
Права доступа к членам базового класса
Права доступа к членам базового класса из производного класса определяются модификатором доступа, задаваемом при описании производного класса.
class Base {
private:
..............
protected:
..............
public:
..............
};
class Derived: public Base { // здесь может применяться private и protected
public:
..............
};
Естественно, нет никаких ограничений на состав функций и полей данных, определяемых в производном классе дополнительно по отношению к базовому.
Таблица прав доступа к членам базового класса в производном классе
Модификатор доступа, указанный при наследовании | Право доступа в базовом классе | Наследуемое право доступа в производном классе |
private | private protected public | не доступен private private |
protected | private protected public | не доступен protected protected |
public | private protected public | не доступен protected public |
Дружественные функции и классы
В ряде случаев бывает удобно получить доступ к приватным и защищенным (private, protected) членам класса из функций, не являющихся членами этого класса. Для того в тело класса нужно вставить прототип такой функции, и перед ним поставить ключевое слово friend.
В тех случаях, когда классы тесно «взаимодействуют» друг с другом (то есть, когда объекты одного класса являются аргументами членов функции другого класса) бывает удобно разрешить доступ таким классам к приватным и защищенным (private, protected) членам этих классов. Для этого в теле класса, к членам которого открывается доступ, нужно описать класс, получающий доступ, с ключевым словом friend.
Отметим, что помимо воли автора этого класса получить доступ к его защищенным полям и методам – нельзя, то есть, чтобы этот доступ получить – в нем самом нужно сделать дополнительное объявление дружественных классов или функций.
#include <ostream>
class CVector {
private:
int len; // размерность вектора
float *ptr; // указатель на область памяти, содержащую элементы вектора.
private:
...
// дружественный оператор вывода:
friend ostream& operator<< (ostream& os, const CVector &v);
friend class CMatrix; // дружественный класс
...
};
ostream& operator<< (ostream& os, const CVector &v)
{
for (int n=0; n < v.len; n++) {
os << "[" << n << "] " << v.ptr[n] << endl;
}
return os;
}
class CMatrix {
private:
int cx, cy; //размерности матрицы
float *ptr; // указатель на память, содержащую элементы матрицы
public:
...
// оператор умножения матрицы на вектор:
CVector& operator * (CVector &v);
...
};
Перегрузка при наследовании
В производных классах могут существовать функции-члены базового класса с именем, совпадающим с именем какой-либо функции базового класса.
class Base {
...
public:
void mf(void) { cout << 1; }
};
class Derived: public Base {
...
public:
void mf(void) { cout << 2; }
};
Base x;
Derived y;
...
x.mf(); // 1, вызывается функция базового класса Base
y.mf(); // 2, вызывается функция производного класса Derived;
Полиморфизм: виртуальные методы класса
Позволяют выбирать методы с одним и тем же именем через указатель функции в зависимости от типа реального объекта, на который указывает указатель, а не в зависимости от типа указателя.
class Base {
...
public:
virtual void mf(void) { cout << 1; }
void mfstd(void) { cout << 10; }
};
class Derived: public Base {
...
public:
void mf(void) { cout << 2; }
void mfstd(void) { cout << 20; }
};
Base x;
Derived y;
Base *px = &x;
Derived *py = &y;
Base *pxy = &y;
px->mf(); // 1, вызывается функция базового класса Base
py->mf(); // 2, вызывается функция производного класса Derived;
pxy->mf(); // 2, полиморфный вызов: мы не знаем, что работаем с Derived,
// так как располагаем указателем на Base, но метод вызывается
// из Derived
pxy->mfstd(); // 10, так как вызов не полиморфный – в базовом классе функция
// не виртуальная.
Абстрактные классы
Абстрактные классы содержат, по крайней мере, одну чистую виртуальную функцию. В программе не могут быть определены объекты абстрактных классов или ссылки на них, но можно определить и использовать указатели на объекты абстрактных классов.
class CPoint {
public:
int x,y;
public:
CPoint (int nx=0, int ny=0): x(nx), y(ny) {} // конструктор
// конструктор копирования
CPoint (const CPoint& src): x(src.x), y(src.y) {}
};
class CShape { // абстрактный класс, так как в нем есть чистая функция
protected:
CPoint center; // центр объекта
public:
CShape (const CPoint &nс): center(nc) {} // конструктор
// перемещение фигуры, невиртуальная функция:
void MoveTo(int nx=0, int ny=0) { center.x = nx; center.y = ny; }
// чистая функция (pure function) для подсчета площади:
virtual double Square() = 0;
};
class CCircle: public CShape {
public:
int radius; // радиус круга
CCircle(int nx=0, int ny=0, int rad=0):
CShape(CPoint(nx,ny)), radius(rad) {}
public:
double Square() { return double(3.14159) * radius * radius; }
};
class CQuadrat: public CShape {
public:
int side; // сторона квадрата
CQuadrat (int nx=0, int ny=0, int ns=0):
CShape(CPoint(nx,ny)), side(ns) {}
public:
double Square() { return side * side; }
};
CCircle c1(1,2,1), c2(2,3,8);
CQuadrat q1(-2,0,3), q2(-2,0,5);
CShape* shapes[4] = { &c1, &q1, &c2, &q2 };
// подсчет площади всех фигур:
double s = 0;
for (int i=0; i < 4; i++) {
s += shapes[i]->Square();
}