
- •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
3.9 Возвращение объектов функциями
Функция может возвращать объект в точку вызова. В качестве примера рассмотрим программу:
#include <iostream.h>
class myclass
{
int i;
public:
void set_i(int n) { i=n; }
int get_i() {return i;}
};
myclass f(); // возвращение объекта типа myclass
int main()
{
myclass c1;
c1 = f();
cout << c1.get_i() << "\n";
return 0;
}
myclass f()
{
myclass x;
x.set_i(1);
return x;
}
Когда функция возвращает объект, автоматически создается временный объект, содержащий возвращаемое значение. Именно этот объект фактически возвращается функцией. После того, как значение возвращено, этот объект уничтожается. Уничтожение временного объекта может вызывать неожиданные побочные эффекты в некоторых ситуациях. Например, если возвращаемый функцией объект имеет деструктор, освобождающий динамически зарезервированную память, то эта память будет освобождена даже в том случае, когда объект, получающий возвращаемое значение, будет продолжать использовать ее. Перегрузка оператора присваивания и определение конструктора копирования позволяют преодолеть эту проблему.
3.10 Присваивание объектов
Если два объекта имеют один и тот же тип, то можно присваивать один объект другому. Это означает, что данные объекта с правой стороны равенства будут скопированы в данные объекта с левой стороны равенства. Например, следующая программа выводит значение 99:
#include <iostream.h>
class myclass
{
int i;
public:
void set_i(int n) { i=n; }
int get_i() { return i; }
};
int main()
{
myclass ob1, ob2;
ob1.set_i(99);
ob2 = ob1; // присвоение данных ob1 объекту ob2
cout << "this is ob2's i: " << ob2. get_i();
return 0;
}
По умолчанию все данные одного объекта присваиваются другому путем побитового копирования. Однако возможно перегрузить оператор присваивания и определить некоторые другие процедуры присваивания.
3.11 Конструктор копирования
По умолчанию при инициализации одного объекта другим C++ выполняет побитовое копирование. Это означает, что точная копия инициализирующего объекта создается в целевом объекте. Хотя в большинстве случаев такой способ инициализации объекта является вполне приемлемым, имеются случаи, когда побитовое копирование не может использоваться. Например, такая ситуация имеет место, когда объект выделяет память при своем создании. Рассмотрим в качестве примера два объекта А и В класса ClassType, выделяющего память при создании объекта. Положим, что объект А уже существует. Это означает, что объект А уже выделил память. Далее предположим, что А использовался для инициализации объекта В, как показано ниже:
ClassType В = А;
Если в данном случае используется побитовое копирование, то В станет точной копией А. Это означает, что В будет использовать тот же самый участок выделенной памяти, что и А, вместо того, чтобы выделить свой собственный. Ясно, что такая ситуация нежелательна. Например, если класс ClassType включает в себя деструктор, освобождающий память, то тогда одна и та же память будет освобождаться дважды при уничтожении объектов А и В.
Проблема того же типа может возникнуть еще в двух случаях. Первый из них возникает, когда копия объекта создается при передаче в функцию объекта в качестве аргумента. Второй случай возникает, когда временный объект создается функцией, возвращающей объект в качестве своего значения.
Для решения подобных проблем язык C++ позволяет создать конструктор копирования, который используется компилятором, когда один объект инициализирует другой. При наличии конструктора копирования побитовое копирование не выполняется. Общая форма конструктора копирования имеет вид:
имя_класса(const имя_класса &оbj)
{
тело конструктора
}
Здесь obj является ссылкой на объект в правой части инициализации. Конструктор копирования может иметь также дополнительные параметры, если для них определены значения по умолчанию. Однако в любом случае первым параметром должна быть ссылка на объект, выполняющий инициализацию.
Инициализация возникает в трех случаях: когда один объект инициализирует другой, когда копия объекта передается в функцию и когда создается временный объект (обычно он служит возвращаемым значением). Например, любая из следующих инструкций вызывает инициализацию:
myclass x = у; // инициализация
F(x); // передача параметра
У = F1(); // получение временного объекта
Ниже приведен пример, где необходим явный конструктор копирования. Эта программа создает очень простой «безопасный» тип массива целых чисел, предотвращающий вывод за границы массива. Память для каждого массива выделяется с использованием оператора new и в каждом объекте поддерживается работа с указателем на выделенную память.
#include <iostream.h>
#include <stdlib.h>
class array
{
int *p;
int size;
public:
array(int s) { p = new int[s]; if(!p) exit(1); size = s; }
~array() { delete [] p; }
array(const array &a);
void put(int i, int j) { if(i>=0 && i<size) p[i] = j; }
int get(int i) { if(i>=0 && i<size) return p[i]; else return 0; }
};
array::array(const array &a)
{
p = new int[a.size]; if(!p) exit(1);
for(int i=0; i<a.size; i++) p[i] = a.p[i];
}
int main()
{
array num(10);
int i;
for(i=0; i<10; i++) num.put(i, i);
for(i=9; i>=0; i--) cout << num.get(i);
cout << "\n";
// создание другого массива и инициализация его значениями num
array x(num); // вызов конструктора копирования
for(i=0; i<10; i++) cout << x.get(i);
return 0;
}
Когда объект num используется для инициализации х, то вызывается конструктор копирования, выделяющий новую память, адрес которой помещается в х.р, а затем содержание массива num копируется в массив объекта х. Таким образом, объекты х и num содержат массивы с одинаковыми значениями, но каждый массив независим от другого и располагается в своей области памяти. Если бы конструктор копирования не был создан, то использовалась бы инициализация по умолчанию путем побитового копирования, так что массивы х и num разделяли бы между собой одну и ту же область памяти.
Конструктор копирования вызывается только для инициализации. Например, следующие инструкции не содержат вызова конструктора копирования:
array a(10);
array b(10);
b = а; // не вызывает конструктор копирования
В данном случае b=а выполняет операцию присваивания. Если оператор = не перегружен, то будет сделана побитовая копия. Поэтому в определенных случаях требуется перегрузить оператор = в дополнение к созданию конструктора копирования (перегрузка функций и операторов рассматривается в следующей главе).