
- •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.1.3 Дополнительная спецификация доступа при наследовании
При наследовании базового класса с ключевым словом private все его элементы, включая общедоступные члены, становятся частными элементами производного класса. Однако в определенных случаях может потребоваться, чтобы определенные общедоступные элементы базового класса сохранили свой статус общедоступных в производном классе. Для этого используется объявление доступа. Оно имеет следующую общую форму:
имя_базового_класса::элемент;
Объявление доступа помещается над подходящим заголовком при объявлении производного класса. Ниже представлен пример, иллюстрирующий использование объявления доступа:
#include <iostream.h>
class B
{
public:
int i, j;
};
class D: private B
{
public:
// объявление доступа
B::i; // i из класса B теперь опять публичный
int k;
};
int main()
{
D d;
d.i = 10; // допустимо, поскольку i сделан в D публичным
d.k = 20;
// d.j = 30; // недопустимо, поскольку j в D является частным
cout << d.i * d.k;
return 0;
}
В этом примере класс D наследуется от класса B с ключевым словом private. Это означает, что члены i и j становятся частными членами класса D. Однако в классе D объявление доступа указывает, что член i снова становится общедоступным.
Также можно использовать объявление доступа для того, чтобы предоставить защищенным членам базового класса статус защищенных в производном классе. Однако следует иметь в виду, что нельзя расширить доступ члена. Например, частный элемент базового класса не может стать публичным элементом производного класса.
5.2 Конструкторы и деструкторы производных классов
При использовании производных классов важно представлять себе, каким образом и когда исполняются конструкторы и деструкторы базового и производного классов. Начнем рассмотрение с конструкторов.
Как базовый класс, так и производный класс могут иметь конструкторы. (В многоуровневой иерархии классов каждый из классов может иметь конструкторы, но мы начнем с наиболее простого случая.) Когда базовый класс имеет конструктор, этот конструктор исполняется перед конструктором производного класса. Например, рассмотрим следующую короткую программу:
#include <iostream.h>
class Base
{
public:
Base() { cout << "\nBase created\n"; )
};
class D1() : public Base
{
public:
D1() { cout << "D1 created\n"; }
};
int main()
{
D1 d1;
// ничего не делается, но выполняются конструкторы
return 0;
}
Эта программа создает объект класса D1. Она выводит на экран следующий текст:
Base created
D1 created
Здесь d1 является объектом класса D1, производным класса Base. Таким образом, при создании объекта d1 сначала вызывается конструктор Base(), а затем вызывается конструктор D1().
Конструкторы вызываются в том же самом порядке, в каком классы следуют один за другим в иерархии классов. Поскольку базовый класс ничего не знает про свои производные классы, то его инициализация может быть отделена от инициализации производных классов и производится до их создания, так что конструктор базового класса вызывается перед вызовом конструктора производного класса.
В противоположность этому деструктор производного класса вызывается перед деструктором базового класса, поскольку уничтожение объектов должно выполняться в порядке, обратном порядку их создания. Следующая программа иллюстрирует порядок, в котором выполняются конструкторы и деструкторы:
#include <iostream.h>
class Base
{
public:
Base() { cout << "\nBase created\n"; }
~Base() { cout << "Base destroyed\n\n"; }
};
class D1() : public Base
{
public:
D1() { cout << "D1 created\n; }
~D1() { cout << "D1 destroyed\n"; }
};
int main()
{
D1 d1;
return 0;
}
Эта программа выдаст следующий текст на экран:
Base created
D1 created
D1 destroyed
Base destroyed
Производный класс может служить базовым классом для создания следующего производного класса. Когда такое происходит, конструкторы исполняются в порядке наследования, а деструкторы — в обратном порядке. В качестве примера рассмотрим следующую программу, использующую класс D2, производный от класса D1:
#include <iostream.h>
class Base
{
public:
Base() { cout << "\nBase created\n"; }
~Base() { cout << "Base destroyed\n"; }
};
class D1() : public Base
{
public:
D1() { cout << "D1 created\n"; }
~D1() { cout << "D1 destroyed\n"; }
};
class D2: public D1
{
public:
D2() { cout << "D2 created\n"; }
~D2() { cout << "D2 destroyed\n"; }
};
int main()
{
D1 d1;
D2 d2;
return 0;
}
Эта программа выдаст следующий результат:
Base created
D1 created
Base created
D1 created
D2 created
D2 destroyed
D1 destroyed
Base destroyed
D1 destroyed
Base destroyed
ПАМЯТКА: Следует запомнить, что конструкторы вызываются в том порядке, в котором классы выводились один из другого. Порядок вызова деструкторов обратный.