Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Tekhnologia_programmirovania.pdf
Скачиваний:
182
Добавлен:
08.04.2015
Размер:
1.76 Mб
Скачать

Взаимоотношения классов 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){

// Если присваивание не самому себе,