
- •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
7.1.1 Потоки
Системы ввода/вывода С и C++ имеют одну общую особенность: они обе оперируют потоками. То, что потоки в С и C++ подобны между собой, означает, что все, что известно о потоках в языке С, полностью применимо и в C++.
7.1.2 Предопределенные потоки C++
Как и в языке С, в C++ существует несколько предопределенных потоков, открывающихся автоматически вместе с началом выполнения программы. Ими служат cin, cout, cerr и clog. Как известно, cin является потоком, ассоциированным со стандартным вводом, a cout представляет собой поток, ассоциированный со стандартным выводом. Потоки cerr и clog используются для вывода сообщений об ошибках. Разница между cerr и clog заключается в том, что, хотя они оба привязаны к стандартному выводу, cerr не буферизирован, поэтому все посланные в него данные выводятся немедленно. В противоположность этому clog буферизирован, так что данные выводятся только тогда, когда буфер оказывается полным.
По умолчанию стандартные потоки C++ привязаны к консоли, но программа может перенаправить их на другие устройства или файлы. Они также могут быть перенаправлены операционной системой.
7.2 Классы потоков C++
C++ обеспечивает поддержку системы ввода/вывода в заголовочном файле iostream.h. В этом файле определены две иерархии классов, поддерживающие операции ввода/вывода. Классом нижнего уровня является streambuf. Этот класс обеспечивает базовые операции ввода/вывода. До тех пор, пока не вводятся свои собственные классы ввода/вывода, непосредственно streambuf не используется. Вторая иерархия классов начинается с класса ios. Он обеспечивает поддержку форматированного ввода/вывода. От него порождены классы istream, ostream и iostream. Эти классы использованы для создания потоков, способных осуществлять ввод, вывод и ввод/вывод соответственно. Как будет показано далее, от класса ios порождено много других классов, поддерживающих файлы на диске и форматирование в памяти.
7.3 Создание собственных операторов вставки и извлечения
В предыдущих главах создаваемые функции-члены классов осуществляли вывод и ввод данных класса с помощью вызова функций наподобие show_data() или get_data(). Хотя технически здесь нет ничего неправильного, язык C++ предоставляет гораздо более совершенный способ выполнения операций ввода/вывода классов с помощью перегрузки операторов << и >>.
На языке C++ оператор << называют оператором вставки (insertion), потому что он вставляет символы в поток. Аналогичным образом оператор >> называется оператором извлечения (extraction), поскольку он извлекает символы из потока. Операторы, перегружающие эти операторы вставки и извлечения, обычно называют инсертером (inserter) и экстрактором (extractor) соответственно. Базовые операторы вставки и извлечения перегружаются в файле iostream.h для того, чтобы выполнять потоковый ввод/вывод любых встроенных типов C++. В этом разделе объясняется, каким образом определить эти операторы по отношению к собственным классам.
7.3.1 Создание операторов вставки
В C++ имеется легкий способ определения оператора вставки для создаваемых классов. В следующем простом примере создается оператор вставки для ранее уже встречавшегося класса point:
class point
{
public:
int x, у, z;
point(int a, int b, int с) { x=a; y=b; z=c; }
}
Для того, чтобы определить оператор вставки для объекта класса point, необходимо перегрузить оператор << по отношению к этому классу. Один из способов показан ниже.
// вывод координат x, y, z (оператор вставки для point)
ostream& operator<<(ostream &stream, point &obj)
{
stream << obj.x << ", " << obj.y << ", " << obj.z << "\n";
return stream; // возврат потока
}
Многие особенности этой функции являются общими для всех функций вставки. Прежде всего, обратим внимание, что в качестве возвращаемого значения этой функции объявлен объект типа ostream. Это необходимо для того, чтобы один оператор мог содержать несколько операторов вставки. Далее, функция имеет два параметра. Первым служит ссылка на поток, который фигурирует в левой части оператора <<. Вторым параметром служит объект с правой стороны оператора <<. В самой функции осуществляется вывод трех величин, содержащихся в объекте point, после чего возвращается поток stream. Следующая короткая программа служит для демонстрации оператора вставки:
int main()
{
point a(1, 2, 3), b(3, 4, 5), c(5, 6, 7);
cout << a << b << c;
return 0;
}
Общая форма функции вставки показана ниже:
ostream& operator<<(ostream &поток, тип_класса &объект)
{
// характерный для типа код
return stream; // возврат потока
}
Остается только определить конкретные действия, выполняемые таким оператором вставки. Обратим внимание на необходимость возвращать поток. Также общей практикой является передача параметра объект по ссылке, поскольку если объект является большим, то гораздо быстрее передать его адрес. Также это предотвращает вызов деструктора объекта когда функция вставки возвращает результат.
В программе перегруженная функция вставки не является членом класса point. Действительно, ни функция вставки, ни функция извлечения не могут быть членами класса. Причина заключается в том, что если функция-оператор является членом класса, то левым операндом, неявно передаваемым с использованием указателя this, служит объект того класса, который осуществляет вызов функции-оператора. И нет способа изменить такой порядок. Вместе с тем при перегрузке оператора вставки левым аргументом является поток, а правым аргументом — объект. Поэтому перегруженные операторы вставки не могут быть функциями-членами. Этот факт вызывает логичный вопрос: как перегруженный оператор вставки может получить доступ к частным членам класса? В предыдущей программе переменные x, y и z были публичными, так что оператор вставки имел к ним доступ. Однако защита данных является одной из важнейших особенностей объектно-ориентированного программирования, и требовать, чтобы все данные были публичными, значит противоречить самому духу ООП. Тем не менее, данная проблема имеет решение: оператор вставки может быть другом класса. В качестве друга класса, для которого он определен, он имеет доступ к частным данным. Иллюстрация этого приводится в следующем примере:
#include <iostream.h>
class point
{
int x, y, z; // теперь частные
public:
point(int a, int b, int c) { x=a; y=b; z=c; }
friend ostream& operator<<(ostream &stream, point &obj);
};
ostream& operator<<(ostream &stream, point &obj)
{
stream << obj.x << ", " << obj.y << ", " << obj.z << "\n";
return stream; // возврат потока
}
int main()
{
point a(1, 2, 3), b(3, 4, 5), c(5, 6, 7);
cout << a << b << c;
return 0;
}
Обратим внимание, что переменные x, у и z являются в данном случае частными членами класса point, но тем не менее продолжают быть доступными непосредственно с помощью функции вставки. Объявление операторов вставки и извлечение друзьями классов, для которых они определены, позволяет сохранить неприкосновенным принцип инкапсуляции объектно-ориентированного программирования.