
- •1.2 Философские замечания
- •1.3 Процедурное программирование
- •1.4 Модульное программирование
- •1.5 Абстракция данных
- •1.6 Пределы абстракции данных
- •1.7 Объектно-ориентированное программирование
- •1.8 Концепции объектно-ориентированного программирования
- •1.8.1 Инкапсуляция
- •1.8.2 Полиморфизм
- •1.8.3 Наследование
- •1.10 Несколько полезных советов
- •2.2 Перегрузка функций
- •2.3 Перегрузка операторов
- •2.4 Наследование
- •2.5 Конструкторы и деструкторы
- •2.7 Два новых типа данных
- •Глава 3. Классы и объекты
- •3.1 Параметризованные конструкторы
- •3.2 Дружественные функции
- •3.3 Значения аргументов функции по умолчанию
- •3.3.1 Корректное использование аргументов по умолчанию
- •3.4 Взаимосвязь классов и структур
- •3.5 Связь объединений и классов
- •3.6 Анонимные объединения
- •3.7 Inline-функции
- •3.7.1 Создание inline-функций внутри класса
- •3.8 Передача объектов в функции
- •3.9 Возвращение объектов функциями
- •3.10 Присваивание объектов
- •3.11 Конструктор копирования
- •3.12 Массивы объектов
- •3.12.1 Инициализация массивов объектов
- •3.12.2 Создание инициализированных и неинициализированных массивов
- •3.13 Указатели на объекты
- •3.14 Статические члены класса
- •Глава 4. Перегрузка функций и операторов
- •4.1 Перегрузка конструкторов
- •4.2 Локализация переменных
- •4.3 Локализация создания объектов
- •4.4 Перегрузка функций и неопределенность
- •4.5 Определение адреса перегруженной функции
- •4.6 Указатель this
- •4.7 Перегрузка операторов
- •4.8 Дружественная функция-оператор
- •4.9 Ссылки
- •4.9.1 Параметры-ссылки
- •4.9.2 Передача ссылок на объекты
- •4.9.3 Возврат ссылок
- •4.9.4 Независимые ссылки
- •4.9.5 Использование ссылок для перегрузки унарных операторов
- •4.10 Перегрузка оператора []
- •4.11 Создание функций преобразования типов
- •Глава 5. Наследование, виртуальные функции и полиморфизм
- •5.1 Наследование и спецификаторы доступа
- •5.1.1 Спецификаторы доступа
- •5.1.2 Спецификатор доступа при наследовании базового класса
- •5.1.3 Дополнительная спецификация доступа при наследовании
- •5.2 Конструкторы и деструкторы производных классов
- •5.3 Множественное наследование
- •5.4 Передача параметров в базовый класс
- •5.5 Указатели и ссылки на производные типы
- •5.6 Ссылки на производные классы
- •5.7 Виртуальные функции
- •5.8 Для чего нужны виртуальные функции?
- •5.9 Чисто виртуальные функции и абстрактные типы
- •5.10 Виртуальный базовый класс
- •5.11 Раннее и позднее связывание
- •Глава 6. Подсистема динамического выделения памяти
- •6.1 Введение в обработку исключений
- •6.1.1 Перехват всех исключений
- •6.2 Работа с памятью с помощью new и delete
- •6.3 Размещение объектов
- •6.4 Перегрузка new u delete
- •7.1.1 Потоки
- •7.3 Создание собственных операторов вставки и извлечения
- •7.3.1 Создание операторов вставки
- •7.3.2 Перегрузка операторов извлечения
- •7.4 Форматирование ввода/вывода
- •7.4.1 Форматирование с помощью функций-членов класса ios
- •7.4.2 Использование манипуляторов
- •7.5 Создание собственных функций-манипуляторов
- •7.5.1 Создание манипуляторов без параметров
- •7.5.2 Создание манипуляторов с параметрами
- •7.6 Файловый ввод/вывод
- •7.6.1 Открытие и закрытие файлов
- •7.6.2 Чтение и запись в текстовые файлы
- •7.6.3 Двоичный ввод/вывод
- •7.6.4 Определение конца файла
- •7.6.5 Произвольный доступ
- •Глава 8. Ввод/вывод в массивы
- •8.1 Классы ввода/вывода в массивы
- •8.2 Создание потока вывода
- •8.3 Ввод из массива
- •8.4 Использование функций-членов класса ios
- •8.5 Потоки ввода/вывода в массивы
- •8.6 Произвольный доступ в массив
- •8.7 Использование динамических массивов
- •8.8 Манипуляторы и ввод/вывод в массив
- •8.9 Собственные операторы извлечения и вставки
- •8.10 Форматирование на основе массивов
- •Глава 9. Шаблоны и библиотека stl
- •9.1 Функции-шаблоны
- •9.2 Функции с двумя типами-шаблонами
- •9.3 Ограничения на функции-шаблоны
- •9.4 Классы-шаблоны
- •9.5 Пример с двумя типами-шаблонами
- •9.6 Обзор библиотеки stl
- •9.7 Класс vector
- •9.7 Класс string
- •9.8 Класс list
5.5 Указатели и ссылки на производные типы
Прежде чем переходить к виртуальным функциям и полиморфизму, следует объяснить один из их важнейших атрибутов. Начнем с указателей. В общем случае указатель одного типа не может указывать на объект другого типа. Из этого правила, однако, есть исключение, которое относится только к производным классам. В C++ указатель на базовый класс может указывать на объект производного класса, полученного из этого базового класса. Предположим, например, что имеется базовый класс B_class и его производный класс D_class. В C++ любой указатель типа B_class* может также указывать на объект типа D_class. Например, если имеются следующие объявления переменных:
B_class *p; // указатель на объект типа B_class
B_class B; // объект типа B_class
D_class D; // объект типа D_class
то следующие присваивания абсолютно законны:
р = &B; // р указывает на объект типа B_class
р = &D; /* р указывает на объект типа D_class, являющийся объектом, порожденным от B_class */
Используя указатель р, можно получить доступ ко всем членам D, которые наследованы от B. Однако специфические члены D не могут быть получены с использованием указателя р (по крайней мере до тех пор, пока не будет осуществлено приведение типов). Это является следствием того, что указатель «знает» только о членах базового типа и не знает ничего о специфических членах производных типов.
В следующей короткой программе иллюстрируется использование указателей на базовый класс. В ней определен класс CName, от которого порожден класс CBook. Этот производный класс реализует функции простой автоматической телефонной книги.
#include <iostream.h>
#include <string.h>
class CName
{
char name[80];
public:
void put_name(char *s) { strcpy(name, s); }
void show_name() { cout << name << " "; }
};
class CBook: public CName
{
char phone_num[80];
public:
void put_phone(char *num) { strcpy(phone_num, num); }
void show_phone() { cout << phone_num << "\n"; }
};
int main()
{
CName *p_name, name;
CBook *p_book, book;
p_name = &name;
// доступ к CName через указатель
p_name->put_name("Ivanov I. I.");
// доступ к CBook через указатель
p_name = &book;
p_name->put_name("Petrov P. P.");
// показать каждое имя соответствующего объекта
name.show_name();
book.showname();
cout << "\n";
/* поскольку put_phone и show_phone не являются частью базового класса, они не доступны через указатель на базовый класс и доступ должен осуществляться или напрямую, или, как показано ниже, через указатель на порожденный класс */
p_book = &book;
p_book->put_phone("555 555-1234");
p_name->show_name(); // в данной строке могут использоваться р и dp
p_book->show_phone();
return 0;
}
В этом примере указатель p_name определен как указатель на класс CName. Однако он может указывать также на объект производного класса CBook и может использоваться для доступа к членам производного класса, которые были определены в базовом классе. Вместе с тем следует запомнить, что этот указатель не может использоваться для доступа к членам, специфическим для производного класса, до тех пор, пока не выполнено приведение типов. Именно поэтому доступ к функции show_phone() осуществляется с использованием указателя p_book, являющегося указателем на производный класс.
Если необходимо получить доступ к элементам производного класса с помощью указателя, имеющего тип указателя на базовый класс, необходимо воспользоваться приведением типов. Например, в следующей строке кода осуществляется вызов функции show_phone() класса CBook:
((CBook*)p_name)->show_phone();
Внешние скобки необходимы для того, чтобы ассоциировать приведение типа именно с указателем p_name, а не с возвращаемой величиной функции show_phone(). Хотя ничего неправильного с технической точки зрения в таком приведении типов нет, лучше исключить его использование, так как оно может служить дополнительным источником ошибок в коде.
Хотя указатель на базовый класс может использоваться в качестве указателя на производный объект, обратное не имеет места. Это означает, что указатель на производный класс не может использоваться для доступа к объектам базового типа. И еще одно замечание. Инкремент и декремент указателя выполняются по отношению к его базовому типу. Таким образом, если указатель на базовый класс указывает на объект производного класса, инкремент или декремент его не даст указатель на следующий объект производного класса. Поэтому нельзя использовать операции инкремента и декремента указателей, когда они указывают на объект производного класса.