C++. Бархатный путь. Часть 2 - Указатели на объекты

ОГЛАВЛЕНИЕ

Указатели на объекты

Рассмотрим простой пример.

#include <iostream.h>
class A
{
};
class AB: public A
{
};
class AC: public A
{
};
void main ()
{
A *pObj;
A MyA;
pObj = &MyA;
cout << "OK A" << endl;
AB MyAB;
AC MyAC;
pObj = &MyAB;
cout << "OK AB" << endl;
pObj = &MyAC;
cout << "OK AC" << endl;
}

Это очень простой пример. Пустые классы, простое наследование… Единственно, что важно в объявлении этих классов - спецификаторы доступа в описании баз производных классов. Базовый класс (его будущие члены) должен быть абсолютно доступен в производном классе. Первый оператор функции main() - объявление указателя на объект класса A. Затем следует определение объекта-представителя класса A, следом - настройка указателя на этот объект. Естественно, при этом используется операция взятия адреса. Всё это давно известно и очень просто. Следующие две строки являются определениями пары объектов, которые являются представителями двух разных производных классов…

За объявлениями объектов в программе располагаются строки, которые позволяют настроить указатель на базовый класс на объект производного класса. Для настройки указателя на объект производного класса нам не потребовалось никаких дополнительных преобразований. Здесь важно только одно обстоятельство. Между классами должно существовать отношение наследования. Таким образом, проявляется важное свойство объектно-ориентированного программирования: УКАЗАТЕЛЬ НА БАЗОВЫЙ КЛАСС МОЖЕТ ССЫЛАТЬСЯ НА ОБЪЕКТЫ - ПРОИЗВОДНЫХ КЛАССОВ. Подобное, на первый взгляд, странное обстоятельство имеет своё объяснение.

Рассмотрим схемы объектов MyA, MyAB, MyAC:

MyA::=
A
MyAB::=
A
AB
MyAC::=
A
AC

Все три объекта имеют общий элемент (объекты производных классов - фрагмент) - представитель базового класса A. Исключительно благодаря этому общему элементу указатель на объект класса A можно настроить на объекты производных классов. Указателю просто присваивается адрес базового фрагмента объекта производного типа. В этом и состоит секрет подобной настройки. Как мы увидим, для указателя pObj, настроенного на объект производного класса, вообще не существует фрагмента объекта, представленного производным классом.

pObj
A
AC

Ниже пунктирной линии - пустота. Для того чтобы убедиться в этом, мы усложним структуру класса A, определив в нём функцию Fun1. Конечно же, эта функция ничего не будет делать. Но у неё будет спецификация возвращаемого значения и непустой список параметров. Нам от неё большего и не требуется. Лишь бы сообщала о собственном вызове…

class A
{
public:
int Fun1(int);
};
int A::Fun1(int key)
{
cout << " Fun1( " << key << " ) from A " << endl;
return 0;
}

Аналогичной модификации подвергнем производные классы AB и AC (здесь предполагаются вызовы функций-членов непосредственно из функции main(), а потому надо помнить о спецификаторе public), а затем продолжим опыты.

class AB: public A
{
public:
int Fun1(int key);
};
int AB::Fun1(int key)
{
cout << " Fun1( " << key << " ) from AB " << endl;
return 0;
}
class AC: public A
{
public:
int Fun1(int key);
int Fun2(int key);// В этом классе мы объявим вторую функцию.
};
int AC::Fun1(int key)
{
cout << " Fun1( " << key << " ) from AC " << endl;
return 0;
}
int AC::Fun2(int key)
{
cout << " Fun2( " << key << " ) from AC " << endl;
return 0;
}