Задачи 199-202. ввод и вывод

199. Для комплексных чисел (программа 53) перегрузите операторы ввода и вывода. Используйте эти операторы для ввода коэффициентов уравнения из файла и записи решения уравнения в файл.

200. Перегрузите операторы ввода и вывода для структуры Time (программа 40).

201. Для класса Date (программы 41-47) перегрузите операторы ввода и вывода.

202. В классе Polinom (задача 198) перегрузите операторы ввода и вывода. Используйте их для ввода коэффициентов полинома из файла и записи результатов действий над полиномами в файл.

Глава 20. Взаимоотношения классов

На базе ранее разработанных классов можно создавать новые классы. Это можно делать, включая объекты одного класса в состав другого, например, так как прямоугольник rect включает две точки point в программе 35, или делая новый класс производным от другого.

Объекты как члены класса

Пусть есть класс:

class Member {

int a;

public:

Member (int i) // Конструктор класса Member

{a = i;}

};

Объявим новый класс, который будет включать в качестве своего члена объект класса Member:

сlass Container{ // Класс, содержащий объект другого класса

Member aa; // Объект aa класса Member – член класса Container

double x;

public:

Container(int i, double xx); // Конструктор

};

При создании объекта класса Container должен создаваться и объект aa класса Member, для чего должен вызываться соответствующий конструктор. Это реализуется с помощью специальной синтаксической конструкции:

Container :: Container(int i, double xx): aa(i) //Конструктор Container

{ x = xx; }

После заголовка конструктора ставится двоеточие, за которым идет имя объекта и в скобках аргументы конструктора объекта. Далее идет реализация остальной части конструктора класса, содержащего объект.

Конструкторы встроенных типов

Встроенные типы можно рассматривать как классы, имеющие конструкторы, поэтому конструктор для Member можно реализовать в виде:

Member(int i): a(i) {}

Здесь запись a(i) означает вызов конструктора встроенного типа int. Аналогично, конструктор класса Container можно записать в виде:

Container :: Container(int i, double xx): aa(i), x(xx) { }

Запись x(xx) означает вызов конструктора для типа double.

Программа 57. Личные данные

Напишем программу, для работы с массивами персональных сведений о людях. Эти сведения должны включать фамилию человека и его дату рождения.

Создадим модуль DateCls для класса моделирования календарных дат, внеся в этот класс конструктор копирования и оператор сравнения дат.

// Файл DateCls.H

#ifndef DateClsH

#define DateClsH

class Date // Класс для работы с датами

{

int d, m, y; // День, месяц, год

static int d0, m0, y0; // Начальная дата

static int dw0; // День недели начальной даты.

public:

Date(int = 0, int = 0, int = 0); // Конструктор

Date(Date&); // Конструктор копирования

static void SetInitDate(int di, int mi, int yi, int dwi)

{ d0 = di; m0 = mi; y0 = yi; dw0 = dwi; }

int DayOfYear(); // Номер дня в году

long Diapason(Date dt); // Диапазон между двумя датами

void PrintDate(); // Печать даты

char* WeekDay(); // Название дня недели

bool operator<=(Date&); // Сравнение двух дат

};

bool Leap(int year); // Проверка високосный или нет год year

#endif

Реализацию класса Date поместим в файле DateCls.CPP. Ниже приводится только та часть этого файла, которая отличается от рассмотренных ранее реализаций класса дат (см. программы 41-47).

// Файл DateCls.cpp

#include ”DateCls.h”

Date :: Date(Date& D) // Конструктор копирования

{ d = D.d; m = D.m; y = D.y; } // создает копию даты D

bool Date :: operator<=(Date& D) // Сравнение двух дат

{

return (y < D.y) || (y == D.y && m < D.m)

|| (y == D.y && m == D.m && d <= D.d);

}

Создадим модуль Persons, в котором разместим классы для работы со сведениями об отдельной личности и о группе лиц. Далее приводится заголовочный файл этого модуля:

// Файл Persons.h

#ifndef PersonsH

#define PersonsH

#include "DateCls.h" // Подключение класса Date

#include <iostream.h>

#include <fstream.h>

#include <string.h>

class Pers // Класс со сведениями о человеке

{

char* name; // Строка с фамилией и инициалами

Date bd; // Дата рождения

public:

Pers(char*, Date); // Конструктор

Pers(); // Конструктор по умолчанию

Pers(Pers&); // Конструктор копирования

~Pers() // Деструктор

{ delete[] name;} // Удаление строки с именем

Pers& operator = (Pers &); // Оператор присваивания

void Print(); // Вывод сведений о человеке

// CmpDate: возвращает true, если дата объекта ps1

// более ранняя или та же, что и у объекта ps2

friend bool CmpDate(Pers& ps1, Pers& ps2) // Используется

{ return ps1.bd <= ps2.bd; } // функция-оператор сравнения дат

};

class Persons // Класс для моделирования группы лиц

{

Pers* G; // Массив сведений о людях

int size; // Размер массива

int n; // Текущее число людей в группе

public:

Persons(int size = 25); // Конструктор. По умолчанию 25 человек

void Add(Pers&); // Добавление человека в группу

void Print(); // Печать группы

~Persons(); // Деструктор

friend void ReadFromFile(ifstream& inf, Persons&); // Чтение данных

// о группе из файла

void SortPersons(bool (*Compare)(Pers& ps1, Pers& ps2)); // Сортировка

// массива по критерию, задаваемому функцией сравнения Compare

void Swap(int i, int j); // Поменять местами элементы i и j массива G

};

#endif

Теперь приведем файл реализации модуля Persons.cpp.

// Файл Persons.cpp

#include "Persons.h"

Конструктор класса Pers пишем по изложенному выше правилу:

Pers :: Pers(char *s, Date D): bd(D) // Конструктор

{ // Сначала вызывается конструктор для объекта bd

name = new char [strlen(s)+1]; // Выделение памяти под имя

strcpy(name, s); // Копирование имени

}

Так как у класса Date есть конструктор по умолчанию, он будет вызываться в следующем конструкторе по умолчанию для класса Pers:

Pers :: Pers() // Конструктор по умолчанию

{ // Текущая дата как дата рождения

name = NULL; // Пустое имя

}

Pers::Pers(Pers& ps) // Конструктор копирования

{

name = new char [strlen(ps.name) + 1]; // Выделение памяти под имя

strcpy(name, ps.name); // Копирование имени

bd = ps.bd; // Копирование даты

}

Pers& Pers :: operator = (Pers& ps) // Перегрузка оператора присваивания

{

if (this != &ps){ // Если присваивание не самому себе,

delete[] name; // удаление старого имени,

name = new char [strlen(ps.name) + 1]; // память под новое имя,

strcpy(name, ps.name); // копирование имени,

bd = ps.bd; // копирование даты

}

return *this; // Возвращение ссылки на объект

}

void Pers :: Print() // Вывод сведений о человеке

{

cout << name << " \t"; // Вывод имени

bd.PrintDate(); // Вывод даты

}

Здесь после вывода имени печатается несколько пробелов и символ табуляции, чтобы даты выравнивались по вертикали. Число пробелов определяется подбором.

Далее идет реализация класса Persons, моделирующего группу.

Persons :: Persons(int sz) // Конструктор

{

G = new Pers[size = sz]; // Выделение памяти под массив

n = 0; // Вначале группа пуста

}

Создание массива объектов класса Pers возможно потому, что в этом классе есть конструктор по умолчанию, который создает объекты с пустыми фамилиями и текущими датами.

Без наличия в классе конструктора по умолчанию создание массива объектов класса невозможно.

Persons :: ~Persons() // Деструктор

{ delete[] G; } // Освобождение памяти

При удалении каждого объекта из массива для него будет автоматически вызван деструктор, так как при создании каждого объекта использовался конструктор.

void Persons :: Add(Pers& st) // Добавление в группу

{ // нового человека

if (n == size){ // Если в группе нет

cout << "Мест нет\n"; // мест, то выход

return;

}

G[n++] = st; // Добавление сведений о новом члене группы

}

В инструкции G[n++] = st; используется перегруженный оператор присваивания класса Pers.

void Persons :: Print() // Печать группы

{

for(int i = 0; i < n; i++) // Печать данных

G[i].Print(); // о каждом человеке

}

void ReadFromFile(ifstream& inf, Persons& grp) // Чтение данных

{ // о группе из файла

const int N = 200;

int ng; // Число людей в группе

char s[N]; // Массив для фамилии

int d, m, y;

inf >> ng; // Чтение размера группы

inf.getline(s, N); // Чтение '\n' из первой строки и переход к следующей

for(int i = 0; i < ng; i++){

inf.getline(s, N); // Чтение из файла строки с фамилией и инициалами

inf >> d >> m >> y; //Чтение компонентов даты

grp.Add(Pers(s, Date(d, m, y))); // Добавление сведений в группу

inf.getline(s, N); // Чтение '\n' для перехода на следующую строку

}

}

// Сортировка массива методом пузырька по критерию,

// задаваемому функцией сравнения Compare

void Persons :: SortPersons(bool (*Compare)(Pers& ps1, Pers& ps2))

{

for(int i = n - 1; i > 0; i--)

for(int j = 0; j < i; j++)

if(!Compare(G[j], G[j + 1])) //Если не тот порядок элементов,

Swap(j, j + 1); //переставить их

}

Аргументом функции сортировки является указатель Compare на функцию, которая сравнивает личные сведения двух людей.

void Persons :: Swap(int i, int j) //Переставить элементы i и j массива G

{

Pers tmp = G[i];

G[i] = G[j];

G[j] = tmp;

}

В главной программе создадим один объект класса Persons (одну группу). Заполним объект сведениями, прочитанными из файла PersData.txt, распечатаем, отсортируем по возрастанию даты рождения и снова распечатаем. Все это – в файле MainPers.cpp:

// Файл MainPers.cpp

#include "Persons.h"

int main()

{

Persons Writers; // Writers – объект класса Persons

ifstream inf("PersData.txt"); // Создаем файловый поток для чтения

if(!inf){ // Если файл не удалось открыть,

cerr << "Не удалось открыть файл PersData.txt ";

exit(1); // завершаем программу

}

ReadFromFile(inf, Writers); // Чтение сведений из файла

cout << "Состав группы: \n";

Writers.Print(); // Вывод состава группы

// Сортировка по возрастанию даты рождения

Writers.SortPersons(CmpDate);

cout << "\nСостав группы по возрастанию даты: \n";

Writers.Print(); // Вывод состава группы

cin.get(); // Ждем нажатия Enter

return 0;

}

Текстовый файл исходных данных можно создать любым редактором, в частности редактором среды разработки, с помощью которого создаются исходные тексты программ. Подготовим в файле PersData.txt следующие исходные данные:

Толстой Л.Н.

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

Здесь 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

Наследование

Классы могут находиться в отношении наследования. Исходные классы называются базовыми или классами – предками. Новые классы, образуемые на основе базовых классов, называются производными или классами – потомками. Производные классы наследуют все компоненты предков. Любой производный класс может стать базовым для другого класса, в результате чего может быть создана иерархия классов.

Определение производного класса S имеет вид:

class S: X, Y, Z

{

… // Собственные компоненты класса S

};

Здесь X, Y, Z – базовые классы для класса S.

Использование сразу нескольких базовых классов при создании одного производного класса называется множественным наследованием.

Пример наследования

При наследовании все члены базового класса становятся членами производного класса. Однако закрытые члены базового класса не становятся доступными для функций производного класса. В следующей программе приведен простейший случай наследования.

Программа 58. Наследование

Пусть объявлен класс 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;

так как 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

Наши рекомендации