- •Предисловие
- •Глава 1. Основные понятия
- •1.1. Элементы языка программирования
- •1.2. Процесс создания программы
- •1.3. Первая программа
- •1.4. Состав программы
- •Глава 2. Средства разработки на C++
- •2.1. Системы Turbo C++ 3.0/Borland C++ 3.1
- •2.2. Система C++ Builder
- •Глава 3. Работа с числовыми данными
- •3.1. Целые типы
- •3.2. Числа с плавающей точкой
- •3.3. Ввод и вывод чисел
- •3.4. Логический тип и логические операции
- •3.5. Математические функции
- •Глава 4. Операторы. Ключевые слова
- •4.1. Операторы
- •4.2. Приоритеты операторов
- •4.3. Ключевые слова
- •4.4. Структура программы
- •4.5. Константы
- •Задачи - . Простейшие вычисления
- •Глава 5. Управление и циклы
- •5.1. Условный оператор
- •5.2. Операторы цикла
- •5.3. Переключатель
- •5.4. Операторы break и continue
- •Задачи -. Выбор и циклы
- •Глава 6. Массивы
- •6.1. Одномерные массивы
- •6.2. Двумерные массивы
- •Задачи -. Одно- и двумерные массивы
- •Глава 7. Функции
- •7.1. Определение функции
- •7.2. Формальные параметры и фактические аргументы
- •7.3. Автоматические и статические переменные
- •7.4. Прототипы функций
- •7.5. Массивы как аргументы функций
- •7.6. Внешние переменные
- •7.7. Рекурсия
- •7.8. Перегруженные имена функций
- •7.9. Аргументы функций по умолчанию
- •Задачи -. Функции
- •Глава 8. Символы и строки
- •8.1. Символы
- •8.2. Строки символов
- •Задачи -. Символы и строки
- •Глава 9. Препроцессор
- •9.1. Директивы препроцессора
- •9.2. Макросы
- •Задачи -. Макросы
- •Глава 10. Указатели и ссылки
- •10.1. Указатели и адреса
- •10.2. Указатели и массивы
- •10.3. Адресная арифметика
- •10.4. Символьные указатели
- •10.5. Массивы указателей
- •10.6. Указатели на функции
- •10.7. Ссылки
- •10.8. Операторы new и delete
- •Задачи -. Указатели и ссылки
- •Глава 11. О файлах и командной строке
- •11.1. Знакомство с файлами
- •11.2. Командная строка
- •11.3. Перенаправление стандартного ввода и вывода на файл
- •11.4. Аргументы командной строки
- •Задачи -. Файлы и командная строка
- •Глава 12. Работа с экраном дисплея
- •12.1. Текстовый режим
- •12.2. Графический режим
- •Задачи -. Работа с экраном
- •Глава 13. Внутреннее представление чисел
- •13.1. Двоичная система счисления
- •13.2. Беззнаковые целые
- •13.3. Двоичный дополнительный код
- •13.4. Двоичный код с избытком
- •13.5. Побитовые операторы
- •13.6. Дробные числа в двоичной системе
- •13.7. Внутреннее представление плавающих типов
- •13.8. Преобразование типов
- •Задачи -. Побитовые операторы
- •Глава 14. Структуры, перечисления, объединения
- •14.1. Объявление структур
- •14.2. Структуры и функции
- •14.3. Указатели на структуры
- •14.4. Массивы структур
- •14.5. Перечисления
- •14.6. Объединения
- •14.7. Битовые поля
- •14.8. О бинарных файлах
- •Задачи -. Структуры
- •Глава 15. Классы
- •15.1. Структуры в C++. Инкапсуляция
- •15.2. Встроенные функции
- •15.3. Классы. Скрытие данных
- •15.4. Конструкторы
- •15.5. Статические члены класса
- •15.6. Друзья класса
- •15.7. Копирование объектов класса
- •15.8. Управление доступом
- •15.9. Ссылка на себя
- •15.10. Деструкторы
- •Задачи -. Работа с классами
- •Глава 16. Программы из нескольких файлов
- •16.1. Работа с проектами
- •16.2. Область действия имен
- •16.3. Заголовочные файлы
- •16.4. Пространства имен
- •Задачи -. Работа со стеком
- •Глава 17. Перегрузка операторов
- •17.1. Правила перегрузки операторов
- •Задачи -. Перегрузка операторов
- •Глава 18. Конструктор копирования и оператор присваивания
- •18.1. Проблемы при копировании
- •Задачи -. Конструктор копирования
- •Глава 19. Ввод и вывод
- •19.1. Вывод
- •19.2. Ввод
- •19.3. Ввод и вывод определяемых пользователем типов
- •19.4. Работа с файлами
- •Глава 20. Взаимоотношения классов
- •20.1. Объекты как члены класса
- •20.2. Конструкторы встроенных типов
- •20.3. Наследование
- •20.4. Виртуальные функции
- •20.5. Абстрактные классы
- •20.6. Совместимость типов
- •20.7. Множественное наследование
- •Задачи -. Наследование классов
- •Глава 21. Шаблоны, исключения
- •21.1. Шаблоны
- •21.2. Шаблоны функций
- •21.3. Классы и шаблоны
- •21.4. Обработка исключений
- •21.5. Стандартная библиотека шаблонов
- •Литература
- •Предметный указатель
Взаимоотношения классов 279
24 5 1905
Здесь 9 – количество записей в файле. Далее в отдельных строках располагаются имена и компоненты даты рождения. На такую структуру файла исходных данных настроена функция ReadFromFile.
Программа выдает следующее:
Состав группы: |
|
Толстой Л.Н. |
9.9.1828 |
Пушкин А.С. |
6.6.1799 |
Лермонтов М.Ю. |
15.10.1814 |
Крылов И.А. |
13.2.1769 |
Тургенев И.С. |
28.10.1818 |
Ломоносов М.В. |
19.11.1711 |
Горький А.М. |
28.3.1868 |
Достоевский Ф.М. |
11.11.1821 |
Шолохов М.А. |
24.5.1905 |
Состав группы по возрастанию даты:
Ломоносов М.В. |
19.11.1711 |
Крылов И.А. |
13.2.1769 |
Пушкин А.С. |
6.6.1799 |
Лермонтов М.Ю. |
15.10.1814 |
Тургенев И.С. |
28.10.1818 |
Достоевский Ф.М. |
11.11.1821 |
Толстой Л.Н. |
9.9.1828 |
Горький А.М. |
28.3.1868 |
Шолохов М.А. |
24.5.1905 |
20.3. Наследование
Классы могут находиться в отношении наследования. Исходные классы называются базовыми или классами – предками. Новые классы, образуемые на основе базовых классов, называются производными или классами – потомками. Производные классы наследуют все компоненты предков. Любой производный класс может стать базовым для другого класса, в результате чего может быть создана иерархия классов.
Определение производного класса S имеет вид: class S: X, Y, Z
{
… // Собственные компоненты класса S };
Здесь X, Y, Z – базовые классы для класса S.
280 20
Использование сразу нескольких базовых классов при создании одного производного класса называется множественным наследованием.
Пример наследования
При наследовании все члены базового класса становятся членами производного класса. Однако закрытые члены базового класса не становятся доступными для функций производного класса. В следующей программе приведен простейший случай наследования.
Программа 59. Наследование
Пусть объявлен класс Base:
// Файл BaseDer.cpp
#include <iostream.h> class Base{
int a; public:
void set_a(int aa){a = aa; } // Установка значения void Print(){cout << "Base::" << a << endl;}
int& get_a(){ return a;}
};
На основе класса Base объявим производный класс:
class Derived : public Base{ double x;
public:
void set_x(double xx){x = xx;}
void Print(){cout << "Derive::" << x << endl;} double get_x(){return x;}
int IntMoreDbl() |
// Сравнение, больше ли a чем x |
{ |
// Функции производного класса имеют доступ |
return get_a() > x; |
// только к открытым членам базового класса |
} |
|
};
В базовом и производном классах имеется функция с одним и тем же именем Print. Конфликта имен не возникнет, так как зоной действия имен членов класса является их класс. В состав класса Derived входит не только собственная переменная x типа double, но и переменная целого типа a, переходящая к нему из базового класса Base, но в функции производного класса IntMoreDbl нельзя написать просто
return a > x;
Взаимоотношения классов 281
так как a – это закрытая переменная класса Base, недоступная функциям производного класса.
#include <conio.h> |
|
int main() |
|
{ |
|
Base b; |
// Переменная базового типа |
b.set_a(1); |
// Установка значения переменной b |
cout << "b: \n"; b.Print(); |
// Вызов функции Base::print() |
Derived d; |
// Переменная производного класса |
d.set_a(2); // Установка целой части вызовом функции базового класса
d.set_x(3); |
// Установка вещественной части |
|
// функцией производного класса |
cout << "d: \n"; d.Print(); |
// Вызов функции Derived::print() |
d.Base::Print(); |
// Для печати элемента из базового класса |
|
// требуется явное указание класса функции |
if(d.IntMoreDbl())
cout << "Base > Derived"; else
cout << "Base < Derived"; getch();
return 0;
}
Программа выводит:
b:
Base::1
d:
Derive::3
Base::2
Base < Derived
Управление доступом при наследовании
В иерархии классов действуют следующие соглашения о доступности членов.
Закрытые члены класса, объявленные в его разделе private, доступны только внутри класса.
Закрытые члены класса можно сделать доступными в производном классе, объявив их защищёнными, то есть поместив их в раздел protected. Изменим объявление класса Base в программе 59:
class Base{ |
|
protected: |
// Закрытые члены, доступные в производных классах |
int a; |
|
public: |
|
282 20
…
};
Теперь в классе Derived можно написать функцию: int IntMoreDbl() { return a > x; }
которая непосредственно обращается к члену a базового класса.
Можно изменять статус доступа членов базового класса через производный класс, указывая спецификатор доступа public, protected или private перед именем базового класса. Поясним это примером. Пусть имеется класс B, который по-разному наследуется другими классами.
class B{ |
|
private: |
float x; |
protected: |
int i; |
public: |
char c; |
}; |
|
class PU : public B{ |
// x наследуются как private |
... |
// i наследуются как protected |
}; |
// c наследуются как public |
Методы и друзья класса PU могут обращаться к i и c. Внешние функции могут обращаться только к c. Например,
void f1(PU b) |
// Внешняя функция |
{ |
|
char c1 = b.c; |
// Можно, B::c доступна через класс PU |
int k = b.i; |
// Ошибка, B::i недоступна через класс |
PU |
|
} |
|
class PRT : protected B{ |
// x наследуются как private |
... |
// i наследуются как protected |
}; |
// c наследуются как protected |
Методы и друзья класса PRT могут обращаться к i и c. Для внешних функций x, i, c закрыты.
class PRV : private B{ |
// x наследуются как private |
... |
// i наследуются как private |
… |
// c наследуются как private |
friend void f2(PRV d); |
// Друг класса |
}; |
|
void f2(PRV d) |
|
|
Взаимоотношения классов 283 |
{ |
|
char c2 = d.c; |
// Ошибка, B::c недоступна через класс PRV |
} |
|
Наследование и конструкторы
При создании объекта производного класса должны быть созданы все его объекты – предки. Если в базовых классах есть конструкторы, то они должны быть вызваны. При написании конструкторов производного класса следует предусматривать вызов конструктора базового класса. В следующей программе демонстрируется схема вызова конструктора базового класса.
Программа 60. Производный класс личных данных
Изменим программу 58 так, чтобы дата входила в класс Pers не как объект класса Date, а за счет объявления класса Pers производным от класса Date.
В класс Date добавим оператор присваивания, объявление класса разместим в модуле DateCls1.
// Файл DateCls1.h |
|
#ifndef DateCls1H |
|
#define DateCls1H |
|
class Date |
// Класс для работы с датами |
{ |
|
int d, m, y; |
// Поля класса: День, месяц, год |
static int d0, m0, y0; |
// Начальная дата |
static int dw0; |
// День недели начальной даты. |
public: |
|
Date& operator=(Date& D) |
// Оператор присваивания |
{ d = D.d; m = D.m; y = D.y; return *this; } /* Остальное как в файле DateCls.h */
#endif
Содержимое файла DateCls1.cpp совпадает с содержимым файла DateCls.cpp из программы 57.
Для класса Pers, производного от класса Date, создадим модуль
PrsDeriv.
// Файл PrsDeriv.h
#ifndef PrsDerivH #define PrsDerivH
#include "DateCls1.h" // Подключение класса Date #include <iostream.h>
#include <fstream.h>
284 |
20 |
|
#include <string.h> |
|
|
class Pers : public Date |
// Класс Pers – производный от Date |
|
{ |
|
|
char* name; |
// Строка с фамилией и инициалами |
|
public: |
|
|
// Остальная часть класса не изменяется, см. программу 57
};
Класс Persons оставляем без изменений.
Наследование означает, что все поля и методы класса Date являются теперь полями и методами класса Pers. Ключевое слово public перед Date означает, что все открытые члены класса Date остаются открытыми, становясь членами класса Pers.
Вреализацию класса Pers внесем изменения, приводимые ниже.
//Файл PrsDeriv.cpp
#include "PrsDeriv.h" #include "DateCls1.h"
Pers :: Pers(char *s, Date D) : Date(D) |
// Конструктор |
{ |
|
name = new char [strlen(s)+1]; |
// Выделение памяти |
strcpy(name, s); |
// Копирование имени |
} |
|
Перед телом конструктора производного класса Pers вызывается конструктор базового класса Date, который создаст дату, являющуюся членом класса Pers. Здесь используется конструктор копирования, имеющийся в класс Date.
Pers :: Pers() : Date() |
// Конструктор по умолчанию |
{ |
// Текущая дата как дата рождения |
name = NULL; |
// Пустое имя |
} |
|
Pers::Pers(Pers& ps) : Date(ps) |
// Конструктор копирования |
{ |
|
name = new char [strlen(ps.name) + 1];// Выделение памяти под имя
strcpy(name, ps.name); |
// Копирование имени |
} |
|
В качестве аргумента конструктору базового класса Date передается объект ps производного класса Pers. Это допустимо, так как в производном классе есть все, что есть в базовом классе.
Pers& Pers :: operator = (Pers& ps) // Перегрузка оператора присваивания
{
if (this != &ps){ |
// Если присваивание не самому себе, |