
- •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
Глава 4. Перегрузка функций и операторов
В прошлой главе кратко обсуждалась перегрузка функций и операторов, являющаяся важнейшей особенностью языка C++. В этой главе мы подробно рассмотрим вопросы, связанные с перегрузкой. Также в ходе этого обсуждения будут затронуты и смежные вопросы.
4.1 Перегрузка конструкторов
Хотя конструкторы и предназначены для решения особо важных задач, они мало отличаются от обычных функций и могут быть перегружены. Как было показано в примере пункта 3.11, для перегрузки конструктора класса достаточно просто объявить различные его формы. Как будет показано далее, во многих случаях перегрузка конструкторов позволяет добиться определенных преимуществ.
Начнем с примера. В следующей программе объявляется класс timer, действующий как таймер обратного отсчета. При создании объекта типа timer ему присваивается начальное значение времени. В результате вызова функции run() таймер начинает отсчет в сторону уменьшающихся значений, пока не достигнет значения 0. В данном примере конструктор перегружен для того, чтобы можно было указывать время в секундах с помощью целого числа или строки, или в минутах и секундах, если указываются два целых числа.
Эта программа использует библиотечную функцию clock(), возвращающую число тиков, прошедших с момента запуска программы. Поделив это значение на макрос CLOCKS_PER_SEC, получаем значение в секундах. Прототип clock() и макрос CLOCKS_PER_SEC содержатся в заголовочном файле time.h.
#include <iostream.h>
#include <stdlib.h>
#include <time.h>
class timer
{
int seconds;
public:
timer(char *t) { seconds = atoi(t); }
timer(int t) { seconds = t; }
timer(int min, int sec) { seconds = min*60 + sec; }
void run();
};
void timer::run()
{
clock_t t1, t2;
t1 = t2 = clock()/CLOCKS_PER_SEC;
while(seconds)
{
if(t1+1 <= (t2=(clock()/CLOCKS_PER_SEC)))
{
seconds--; t1 = t2;
}
}
cout << "\a"; // звонок
}
int main()
{
timer a(10), b("20"), c(1, 10);
a.run(); // отсчитать 10 секунд
b.run(); // отсчитать 20 секунд
с.run(); // отсчитать 1 минуту 10 секунд
return 0;
}
Как можно видеть, при создании объектов a, b и с внутри функции main() они получают начальное значение с использованием трех различных методов, поддерживаемых перегруженными конструкторами. Каждый из них позволяет провести инициализацию для соответствующих данных.
4.2 Локализация переменных
Прежде чем продолжать дискуссию о перегрузке конструкторов, стоит обсудить важное различие между объявлением локальных переменных в С и тем способом, каким они могут быть объявлены в C++. В С необходимо объявлять все локальные переменные блока в самом начале этого блока. Нельзя объявлять переменные в блоке, где попало. Например, в С следующий фрагмент кода не является корректным:
void f()
{
int i;
i = 10;
int j;
/* ... */
}
Поскольку оператор присваивания i=10 попадает между двумя объявлениями i и j, то компилятор С выдаст ошибку и откажется компилировать эту функцию. Однако в C++ этот фрагмент абсолютно приемлем, и компилятор не обнаружит ошибки. В C++ локальная переменная может объявляться где угодно в блоке. Более того, она оказывается известной только тому коду, который следует после нее в блоке.
Ниже представлен другой пример, показывающий, каким способом локальные переменные могут объявляться в блоке при использовании языка C++:
#include <iostream.h>
#include <string.h>
int main()
{
int i;
i = 10;
int j = 100; // совершенно корректно в C++
cout << i*j << "\n";
cout << "Enter a string: ";
char str[80]; // также корректно в C++
cin >> str;
// вывод строки в обратном порядке
int k;
k = strlen(str) - 1;
while(к>=0) cout << str[k--];
return 0;
}
Все, что эта программа иллюстрирует, заключается в том, что, используя C++, можно объявлять локальные переменные где угодно в блоке кода. Поскольку философия языка C++ тесно связана с инкапсуляцией кода и данных, то имеет смысл объявлять переменные вблизи того кода, который их использует, а не при входе в блок. Здесь объявления i и j разделены между собой просто для иллюстрации. Однако можно видеть, каким образом локализация k вблизи соответствующего кода способствует инкапсуляции этой процедуры. Объявление переменных вблизи той точки, где они будут использованы, помогает избежать случайных побочных эффектов. Эта черта языка C++ также удобна при создании объектов, что иллюстрируется в следующем разделе.