Прежде чем переходить к виртуальным функциям и полиморфизму, следует объяснить один из их важнейших атрибутов. Начнем с указателей. В общем случае указатель одного типа не может указывать на объект другого типа. Из этого правила, однако, есть исключение, которое относится только к производным классам. В С++ указатель на базовый класс может указывать на объект производного класса, полученного из этого базового класса. Предположим, например, что имеется базовый класс B_class и его производный класс D_class. В С++ любой указатель типа B_class* может также указывать на объект типа D_class. Например, если имеются следующие объявления переменных:
B_class *р; // указатель на объект типа B_class
B_class B_ob; // объект типа B_class
D_class D_ob; // объект типа D_class
то следующие присвоения абсолютно законны:
р = &В_оb; // р указывает на объект типа B_class
р = &D_ob; /* р указывает на объект типа D_class, являющийся объектом, порожденным от B_class */
Используя указатель р, можно получить доступ ко всем членам D_ob, которые наследованы от B_ob. Однако специфические члены D_ob не могут быть получены с использованием указателя р (по крайней мере до тех пор, пока не будет осуществлено приведение типов). Это является следствием того, что указатель «знает» только о членах базового типа и не знает ничего о специфических членах производных типов.
В следующей короткой программе иллюстрируется использование указателей на базовый класс. В ней определен класс B_class, от которого порожден класс D_class. Этот производный класс реализует функции простой автоматической телефонной книги.
// использование указателей с объектами порожденного класса
#include <iostream.h>
#include <string.h>
class B_class {
char name[80];
public:
void put_name(char *s) {strcpy(name, s); }
void show_name() {cout << name << " "; }
};
class D_class: public B_class {
char phone_num[80];
public:
void put_phone (char *num) {
strcpy (phone_num, num);
}
void show_phone() {cout << phone_num << "\n"; }
};
int main()
{
B_class *p;
B_class B_ob;
D_class *dp;
D_class D_ob;
p = &B_ob; // адрес базового
// доступ к B_class через указатель
p->put_name ("Thomas Edison");
// доступ к D_class через указатель
р = &D_ob;
p->put_name ("Albert Einstein");
// показать каждое имя соответствующего объекта
B_ob.show-name();
D_ob. showname();
cout << "\n";
/* поскольку put_phone и show_phone не являются частью базового класса, они не доступны через указатель на базовый класс и доступ должен осуществляться или напрямую, или, как показано ниже, через указатель на порожденный класс */
dp = &D_ob;
dp->put_phone("555 555-1234");
p->show_name(); // в данной строке могут использоваться или р, или dp
dp->show_phone();
return 0;
}
В этом примере указатель р определен как указатель на класс B_class. Однако он может указывать также на объект производного класса D_class и может использоваться для доступа к членам производного класса, которые были определены в базовом классе. Вместе с тем следует запомнить, что этот указатель не может использоваться для доступа к членам, специфическим для производного класса, до тех пор, пока не выполнено приведение типов. Именно поэтому доступ к функции show_phone() осуществляется с использованием указателя dp, являющегося указателем на производный класс.
Если необходимо получить доступ к элементам производного класса с помощью указателя, имеющего тип указателя на базовый класс, необходимо воспользоваться приведением типов. Например, в следующей строке кода осуществляется вызов функции show_phone() класса D_ob:
((D_class *)р) ->show_phone();
Внешние скобки необходимы для того, чтобы ассоциировать приведение типа именно с указателем р, а не с возвращаемой величиной функции show_phone(). Хотя ничего неправильного с технической точки зрения в таком приведении типов нет, лучше исключить его использование, так как оно может служить дополнительным источником ошибок в коде.
Хотя указатель, имеющий тип указателя на базовый класс, может использоваться в качестве указателя на производный объект, обратное не имеет места. Это означает, что указатель, имеющий тип указателя на производный класс, не может использоваться для доступа к объектам базового типа. И еще одно замечание. Инкремент и декремент указателя выполняются по отношению к его базовому типу. Таким образом, если указатель на базовый тип указывает на объект производного класса, инкремент или декремент его не даст указатель на следующий объект производного класса. Поэтому нельзя использовать операции инкремента и декремента указателей, когда они указывают на объект производного класса.
Ссылки на производные классы
Ссылки на базовый класс могут быть использованы для ссылок на объект производного типа. Такая техника наиболее употребительна при работе с функциями. Если параметр является ссылкой на базовый класс, то он может принимать значение ссылки как на объект базового класса, так и на объекты производных типов.