
- •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
2.3 Перегрузка операторов
Другим способом реализации полиморфизма в языке C++ служит перегрузка операторов. Например, в C++ можно использовать операторы << и >> для выполнения операций ввода/вывода. Это оказывается возможным благодаря тому, что в заголовочном файле iostream.h эти операторы перегружены. Когда оператор перегружен, он приобретает дополнительное значение в зависимости от своего использования. Тем не менее, он сохраняет возможность своего использования в определенном ранее смысле.
В общем случае можно перегружать операторы C++, определяя, что они означают применительно к специфическому классу. В качестве примера снова рассмотрим класс queue, представленный ранее в этой главе. Можно перегрузить оператор + по отношению к объектам типа queue таким образом, что он добавляет содержимое одного объекта queue к другому. Тем не менее, оператор + сохраняет свое прежнее значение оператора сложения по отношению к другим типам данных.
Поскольку перегрузка операторов фактически представляет собой более сложную задачу, чем перегрузка функций, мы отложим ее рассмотрение до соответствующей главы.
2.4 Наследование
Наследование является одной из главных особенностей объектно-ориентированного программирования. В C++ наследование поддерживается за счет того, что одному классу разрешается при своем объявлении включать в себя другой класс. Наследование позволяет построить иерархию классов от более общего к более частным. Этот процесс включает в себя определение базового класса, определяющего общие качества всех объектов, которые будут выведены затем из базового класса. Базовый класс представляет собой наиболее общее описание. Выведенные из базового класса классы обычно называют производными классами. Производные классы включают в себя все черты базового класса и, кроме того, могут добавлять новые качества, характерные именно для данного производного класса. Чтобы продемонстрировать, как работает этот процесс, в следующем примере созданы классы, классифицирующие различные типы средств передвижения.
В качестве начального рассмотрим класс, названный road_vehicle (дорожное средство передвижения), который служит очень широким определением средств передвижения по дорогам. Он хранит информацию о числе колес движущегося средства и о числе пассажиров, которые он может вмещать:
class road_vehicle
{
int wheels;
int passengers;
public:
void set_wheels(int num);
int get_wheels();
void set_pass(int num);
int get_pass();
};
Теперь можно использовать это определение дорожного средства передвижения для того, чтобы определить другие, более конкретные типы. Например, в следующем фрагменте кода определен класс truck (грузовик), используя класс road_vehicle:
class truck: public road_vehicle
{
int cargo;
public:
void set_cargo(int size);
int get_cargo();
void show();
};
Обратим внимание, каким образом наследуется класс road_vehicle. Общая форма записи наследования имеет следующий вид
class имя_нового_класса: доступ наследуемый_класс
{
//тело нового класса
}
Здесь использование доступа не обязательно, но если оно используется, то должно принимать значение public, protected или private. Более подробно данная опция обсуждается в дальнейшем. Доступ public означает, что все члены класса-предшественника сохраняют свои спецификаторы доступа во всех производных классах. Поэтому в данном примере члены класса truck имеют доступ к функциям-членам класса road_vehicle так, как если бы эти функции-члены были объявлены внутри класса truck. Однако функции-члены класса truck не имеют доступа к частным членам класса road_vehicle, а именно числу колес и пассажиров.
Следующая программа иллюстрирует наследование с помощью создания двух подклассов класса road_vehicle: truck и automobile:
#include <iostream.h>
class road_vehicle
{
int wheels;
int passengers;
public:
void set_wheels(int num);
int get_wheels();
void set_pass(int num);
int get_pass ();
};
class truck: public road_vehicle
{
int cargo;
public:
void set_cargo(int size);
int get_cargo();
void show();
};
enum type{car, van, wagon};
class automobile: public road_vehicle
{
enum type car_type;
public:
void set_type(enum type t);
enum type get_type();
void show();
};
void road vehicle::set_wheels(int num)
{
wheels = num;
}
int road_vehicle::get_wheels()
{
return wheels;
}
void road_vehicle::set_pass(int num)
{
passengers = num;
}
int road_vehicle::get_pass()
{
return passengers;
}
void truck::set_cargo(int num)
{
cargo = num;
}
int truck::get_cargo()
{
return cargo;
}
void truck::show()
{
cout << "Wheels: " << get_wheels() << "\n";
cout << "Passengers: " << get_pass() << "\n";
cout << "Cargo capacity: " << cargo << "\n";
}
void automobile::set_type(enum type t)
{
car_type = t;
}
enum type automobile::get_type()
{
return car_type;
}
void automobile::show()
{
cout << "Wheels: " << get_wheels() << “\n”;
cout << "Passengers: " << get_pass() << "\n";
cout << "Type: ";
switch(get_type())
{
case van: cout << "Van\n";
break;
case car: cout << "Car\n";
break;
case wagon: cout << "Wagon\n";
break;
}
}
int main()
{
truck t1, t2;
automobile c;
t1.set_wheels(18);
t1.set_pass(2);
t1.set_cargo(3200);
t2.set_wheels(6);
t2.set_pass(3);
t2.set_cargo(1200);
t1.show();
с.set_wheels(4);
с.set_pass(6);
с.set_type(van);
с.show();
return 0;
}
Наибольшим достоинством наследования служит возможность создания классификации типов данных, которая может быть затем включена в конкретные классы. Таким образом, каждый объект выражает в точности те свои черты, которые определяют его место в классификации.
Обратим внимание, что классы truck и automobile включают функции-члены с одинаковым именем show(), которые служат для вывода информации об объекте. Это еще один аспект полиморфизма. Поскольку каждая функция show() относится к своему собственному классу, компилятор может легко установить, какую из них вызывать в конкретной ситуации.