Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
41
Добавлен:
30.03.2020
Размер:
43.08 Кб
Скачать

ЛАБОРАТОРНАЯ РАБОТА №3.

ОТНОШЕНИЕ НАСЛЕДОВАНИЯ.

Постановка задачи.

Вариант

Класс №2

Свойства

(Класс №2)

Класс №3

1

CStudent

(Студент)

String Surname

(Фамилия, например – Иванов)

CGroup* G

Дополнить систему, состоящую из двух классов “Класс №1” и “Класс №2”, которые были разработаны в лабораторной работе 2, новым классом “Класс №3”. Новый класс должен быть связан public наследованием с классом “Класс №2”. Класс “Класс №3” должен иметь одно поле, которое выбирается студентом самостоятельно. Для разрабатываемого класса написать конструкторы умолчания, с параметрами и конструктор копирования, деструктор, методы доступа и метод print(). Написать тестовую программу для проверки работоспособности разработанных классов.

Дополнить систему, состоящую из двух классов “Класс №1” и “Класс №2”, которые были разработаны в лабораторной работе 2, новым классом “Класс №3”. Новый класс должен быть связан public наследованием с классом “Класс №2”. Класс “Класс №3” должен иметь одно поле, которое выбирается студентом самостоятельно. Для разрабатываемого класса написать конструкторы умолчания, с параметрами и конструктор копирования, деструктор, методы доступа и метод print(). Написать тестовую программу для проверки работоспособности разработанных классов.

Дополнить систему, состоящую из двух классов “Класс №1” и “Класс №2”, которые были разработаны в лабораторной работе 2, новым классом “Класс №3”. Новый класс должен быть связан public наследованием с классом “Класс №2”. Класс “Класс №3” должен иметь одно поле, которое выбирается студентом самостоятельно. Для разрабатываемого класса написать конструкторы умолчания, с параметрами и конструктор копирования, деструктор, методы доступа и метод print(). Написать тестовую программу для проверки работоспособности разработанных классов.

Дополнить систему, состоящую из двух классов “Класс №1” и “Класс №2”, которые были разработаны в лабораторной работе 2, новым классом “Класс №3”. Новый класс должен быть связан public наследованием с классом “Класс №2”. Класс “Класс №3” должен иметь одно поле, которое выбирается студентом самостоятельно. Для разрабатываемого класса написать конструкторы умолчания, с параметрами и конструктор копирования, деструктор, методы доступа и метод print(). Написать тестовую программу для проверки работоспособности разработанных классов.

2

CWorker

(Сотрудник)

String Surname

(Фамилия, например – Иванов)

CProfession* P

3

CFile

(Файл)

String Name

(Имя, например – Иван)

CCharacteristic * S

4

CCar

(Машина)

String Brand

(Бренд, например – LADA)

CWheel* W

5

CRadioDetal (Радиодеталь)

String Name (Наименование, например – Транзистор)

CComponent* C

6

CBook

(Книга)

String Name

(Имя, например – Александр Сергеевич Пушкин)

CAuthor* A

7

CFilm

(Фильм)

Double Rating (Рейтинг фильма, например – 8.61)

CDescription* D

8

СRailwayWagon (Железн. Вагон)

String Company

(Компания, например - Алтайвагон)

СProperties obj

9

CAircraft

(Самолёт)

String Company

(Компания в которой используются, например – Аэрофлот)

CType* T

10

CLanguageProg

(Язык программ.)

String Name

(Название, например – Prolog)

CDescription* D

11

CClientBank

(Клиент банка)

String Name

(Название банка, например – Сбербанк)

CCardClient obj

12

CParts

(Комплектующие)

Double Cost

(Цена, например – 16660.6)

CDetail obj

13

CFootbalPlayer

(Игрок футбол.ком.)

String FullName

(Имя футболиста, например – Криштиану Роналду)

CDescription obj

14

CProgramPocket

(Програм.пакет)

String Firm

(Компания, разработавшая, например – Microsoft)

CCharacteristic obj

15

CApartment (Квартира)

Double Cost

(Цена квартиры (в млн. руб., например – 3.41)

CAdvertisement* A

16

CTV

(Телевизор)

Double Cost

(Цена, например -13990)

CCharacteristic obj

17

CPassport

(Паспортные дан.)

String Country

(Страна держателя, например – Россия)

CData* D

18

CMobilePhone

(Мобильный тел.)

String Brand

(Производитель устройства, например – Samsung)

CCharacteristic obj

19

CWebsite

(Сайт)

String Name

(Название сайта, например – Ulmart)

CLogs* L

20

CPlanets

(Планеты СС)

String Name

(Название, например – Земля)

CDescription obj

21

CMuseumsSPB

(Музеи СПБ)

String Thematics

(Тематика, например – Исторический)

CGuide* G

22

CTheatresSPB

(Театры СПБ)

String Name

(Имя театра, например – МДТ)

CBrochure obj

23

CHotelSPB

(Отели СПБ)

String Name

(Название, например – RadisonRoyal)

CAdvertisement obj

24

CSmartphone

(Смартфон)

Double Cost

(Цена телефона, например – 13990)

CCharacteristic* С

25

CTransport (Транспортное ср. )

String Brand

(Бренд автомобиля, например – Rolls-Royce)

CEngine* E

26

CPersonalPC

(Персональный ПК)

String Brand

(Производитель компьютера, например – InvasionLabs)

CCharacteristic obj

27

CNotebook

(Ноутбук)

String Brand

(Производитель ноутбука, например – Acer)

CCharacteristic* С

28

CTV

(Телевизор)

String Brand

(Производитель, например – Samsung)

CCharacteristic* C

29

CPlants

(Растение)

String Name

(Название, например – Туя обыкновенная)

CDescription* D

30

CAnimals (Животные)

String Name

(Название, например – Панда)

CDescription obj

Класс №2

Свойства

(Класс №2)

Класс №3

Свойства

(Класс №3)

Пример

CMammal

(Млекопитающее)

Float WeightMammal

(Вес млекопитающего, например – 25)

CSpine* MammaFeatures

СDog

(Собака)

String NameDogs

(Имя собаки, например – Ушастик)

CMammal obj

Методические указания.

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

Вид наследования

Права доступа в базовом классе

Права доступа в порожденном классе

public

private

private

protected

protected

public

public

Protected

private

private

protected

protected

public

Private

private

private

protected

Public

Обсуждение.

В языке C++ для обеспечения сокрытия информации используется принцип управления доступом, а не управление видимостью. Для уяснения различия между этими двумя подходами, рассмотрим следующий пример.

// Файл Сlasses.h class A { private: int n; }; class B : public A { public: void foo(); }; // Файл B.cpp int n; void B :: foo() { int n = 5; // ... }

При компиляции рассматриваемого программного кода будет выведено сообщение об ошибке Дело в том, что в теле функции foo() видна закрытая переменная – член базового класса А. Эта переменная скрывает глобальную переменную n, объявленную в файле B.cpp. Если бы сокрытие информации осуществлялось за счет управления видимостью, то сообщение об ошибке появляться не должно.

Итак, очень важный вывод состоит в том, что все закрытые элементы базового класса в порожденном классе видны, но недоступны. Заметим, что в других объектно-ориентированных языках могут быть приняты свои способы сокрытия информации. Например, в Object Pascal Delphi это осуществляется за счет управления видимостью.

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

В разделе protected следует размещать те элементы базового класса, которые должны быть доступны порожденному классу, но не должны быть доступны его клиентам. Не рекомендуется в этот раздел базового класса включать элементы его реализации.

При public – наследовании открытый интерфейс базового класса входит составной частью в интерфейс порожденного класса. Можно говорить о том, что при public – наследовании наследуется интерфейс базового класса.

При использовании protected – наследования и public – наследования интерфейс базового класса клиентам базового класса не предоставляется. В этом случае можно говорить о наследовании реализации.

Имеется ряд элементов базового класса, которые не наследуются. К их числу относятся:

  • конструкторы.

  • Деструктор.

  • Перегруженный оператор присваивания.

Основным видом наследования в языке C++ является public – наследование.

Виртуальный деструктор оказывается необходимым в тех случаях, когда необходимо предусмотреть полиморфную работу с объектами через указатель или ссылку на базовый класс.

Пусть имеются два класса B и D, связанные public – наследованием.

class B { public: B(); ~B(); }; class D : public B { public: D(); ~D(); }

Предположим теперь, что имеется следующий клиентский программный код

int main() {

B* pb = new B; // ... delete pb; // ... }

Рассмотрим два случая. Вначале предположим, что деструктор в классе B является виртуальным. В этом случае оператор delete вызовет деструктор класса D, который после завершения своей работы вызовет деструктор базового класса. Это обусловлено тем обстоятельством, что при работе с виртуальными функциями определяющим является динамический тип указателя.

Перейдем к рассмотрению второго случая, когда деструктор базового класса является невиртуальным. В этом случае определяющим является статический тип указателя. В результате будет вызван только деструктор базового класса.

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

При применении множественного наследования возникает ряд проблем.

Возможны следующие разновидности наследования:

  1. Шаблон класса может быть порожден от шаблонного класса.

  2. Шаблон класса может быть порожен от нешаблонного класса.

  3. Нешаблонный класс может быть порожден от шаблонного класса.

Пример 1. Наследование шаблона класса от шаблона класса.

// базовый класс template <typename T> class Array { // ... private: T* p; int size_; }; // Порожденный класс template <typename T> class GrowArray : public Array<T> { // ... };

Пример 2. Шаблон класса порождается от нешаблонного класса

Рассматриваемый пример представляет интерес по нескольким причинам:

  1. Отсутствует «разбухания» кода.

  2. Демонстрируется целесообразность использования private-наследования.

  3. Используется вложенный класс.

// Базовый класс Файл BaseStack.h // Назначение класса состоит в реализации функциональности // стека class BaseStack { protected: BaseStack(); ~BaseStack(); void push(void* Object); void* pop(); bool isEmpty()const; private: struct Node // Вложенный класс: Узел { void* data_; Node* next_; Node(void* data, Node* next) : data_(data), next_(next) { } }; Node* top; BaseStack(const BaseStack& rhs); BaseStack& operator=(const BaseStack& rhs); }; // Реализация базового класса BaseStack. Файл BaseStack.cpp // Программный код не приводится // Порожденный с помощью private-наследования шаблон класса // Stack<T>. Назначение шаблона состоит в реализации // контроля типов. Файл Stack.h #includeBaseStack.htemplate <typename T> class Stack : public BaseStack { public: void push(T* ObjectPtr); { BaseStack :: push(ObjectPtr); } T* pop() { return static_cast<T”>(BaseStack :: pop()); } bool isEmpty()const { return BaseStack :: isEmpty(); } };

Обсуждение.

Реализация стека состоит из трех классов:

  1. BaseStack – нешаблонный класс, реализующий функциональность стека;

  2. Node – нешаблонный класс, вложенный в класс BaseStack.

  3. Stack – шаблонный класс, порожденный с помощью private-наследования от класса BaseStack.

Обращает на себя внимание отсутствие у класса BaseStack интерфейса пользователя. Интерфейс, который предоставляется этим классом, может быть назван интерфейсом наследования. Его элементы находятся в protected-секции. В частности объекты типа BaseStack могут создавать только функции-члены классов, порожденные от рассматриваемого класса. Для этой цели порожденные классы могут воспользоваться защищенным конструктором умолчания.

В private-секции рассматриваемого класса содержится объявление класса Node. Этот класс можно рассматривать как элемент реализации класса BaseStack. Дело в том, что объекты класса Node должны создаваться только функциями-членами класса BaseStack.

Интерфейс пользователя, обеспечивающий контроль типов, создается шаблоном класса, который порождается от класса BaseStack с помощью private-наследования. Следует отметить, что функции этого класса чрезвычайно просты. Поэтому они могут быть реализованы встроенными.

Пример 3. Порождение нешаблонного класса от шаблона класса.

template <typename T> class B { // ... }; class D : public B<int> { // ... };

Пример.

Пример на основе трех классов: Spine, Mammal и Dog

#include "pch.h"

#include <iostream>

#include <string>

using namespace std;

#define _CRT_SECURE_NO_WARNINGS

/*Создаем класс Spine(Позвоночник)*/

class Spine

{

/*Объявляем переменную lengthSpine(Длина позвоночника) типа float и

переменную WallColor(Название позвоночника) типа string c модификатором доступа private */

private:

float lengthSpine;

string nameSpine;

public:

/*Объявляем конструктор по умолчанию*/

Spine()

{

/*Производим отладочную печать работы конструктора по умолчанию и инициализируем переменные lengthSpine и nameSpine */

cout << "Конструктор по умолчанию Spine" << endl;

lengthSpine = 0.6;

nameSpine = "Грудной";

}

/*Объявляем конструктор, который принимает в качестве параметров две переменные:

lengthSpine типа float и nameSpine типа string.

В инициализации конструктора инициализируем переменные lengthSpine и nameSpine полученными значениями */

Spine(float lengthSpine, string nameSpine) : lengthSpine(lengthSpine), nameSpine(nameSpine)

{

/*Производим отладочную печать работы конструктора с параметром*/

cout << "Конструктор c параметрами Spine" << endl;

}

/*Объявляем конструктор копирования Spine, который принимает в качестве параметра объект Spine с именем cpySpine

В инициалиации конструктора копируем поля объекта cpySpine в lengthSpine и nameSpine соответственно скопированным значениями*/

Spine(const Spine& cpySpine) : lengthSpine(cpySpine.lengthSpine), nameSpine(cpySpine.nameSpine)

{

/*Производим отладочную печать работы конструктора копирования*/

cout << "Конструктор копирования Spine" << endl;

}

/*Объявляем перегруженный оператор присваивания*/

Spine& operator=(const Spine &spine)

{

/*Производим отладочную печать работы перегруженного оператора присваивания

Копируем поля объекта cpySpine в lengthSpine и nameSpine соответственно*/

cout << "Перегруженный оператор присваивания Spine" << endl;

this->lengthSpine = spine.lengthSpine;

this->nameSpine = spine.nameSpine;

/*Возвращаем текущий объект*/

return *this;

}

/*Объявляем деструктор*/

~Spine()

{

/*Производим отладочную печать работы деструктора*/

cout << "Деструктор Spine" << endl;

}

/*Объявляем setter для записи значения в private lengthSpine*/

void setLengthSpine(float lengthSpine)

{

this->lengthSpine = lengthSpine;

}

/*Объявляем getter для получения значения из private lengthSpine*/

float getLengthSpine()

{

return lengthSpine;

}

/*Объявляем setter для записи значения в private nameSpine*/

void setNameSpine(string nameSpine)

{

this->nameSpine = nameSpine;

}

/*Объявляем getter для получения значения из private nameSpine*/

string getNameSpine()

{

return nameSpine;

}

/*Метод вывода print полей класса*/

void print()

{

cout << "Длина позвоночника: " << lengthSpine << " м." << endl;

cout << "Название позвоночника: " << nameSpine << endl;;

}

};

/*Создаем класс Mammal(Млекопитающее)*/

class Mammal

{

/*Объявляем переменную weightMammal(Вес млекопитающего) типа float

и объявляем указатель mammalFeatures(Особенности млекопитающего) на класс Spine c модификатором доступа private */

private:

float weightMammal;

Spine* mammalFeatures;

public:

/*Объявляем конструктор по умолчанию*/

Mammal()

{

/*Производим отладочную печать работы конструктора по умолчанию и инициализируем переменную weightMammal значением,

и выделяем память для mammalFeatures */

cout << "Конструктор по умолчанию Mammal" << endl;

weightMammal = 25.5;

mammalFeatures = new Spine[2];

}

/*Объявляем конструктор, который принимает в качестве параметров три переменные:

lengthSpine типа float, nameSpine типа string и weightMammal типа float.

В инициализации конструктора инициализируем переменную weightMammal полученным значением */

Mammal(float lengthSpine, string nameSpine, float weightMammal) : weightMammal(weightMammal)

{

/*Производим отладочную печать работы конструктора с параметрами, выделяем память для mammalFeatures,

передавая в качестве размерности 2 и вызываем setter`ы setLengthSpine и setNameSpine класса Spine,

передавая в качестве параметров переменные lengthSpine и nameSpine соответственно */

cout << "Конструктор с параметром Mammal " << endl;

mammalFeatures = new Spine[2];

mammalFeatures->setLengthSpine(lengthSpine);

mammalFeatures->setNameSpine(nameSpine);

}

/*Объявляем конструктор копирования Mammal, который принимает в качестве параметров объект Mammal

с именем cpyMammal.

В инициализации конструктора инициализируем переменную weightMammal скопированным значением */

Mammal(const Mammal& cpyMammal) : weightMammal(cpyMammal.weightMammal)

{

/*Производим отладочную печать работы конструктора копирования. Выделяем память для mammalFeatures, передавая в качестве размерности 1

Копируем поля объекта cpyMammal в mammalFeatures соответственно*/

cout << "Конструктор копирования Mammal" << endl;

this->mammalFeatures = new Spine[1];

this->mammalFeatures[0] = cpyMammal.mammalFeatures[0];

}

/*Объявляем перегруженный оператор присваивания*/

Mammal& operator=(const Mammal &mammal)

{

/*Производим отладочную печать работы перегруженного оператора присваивания*/

cout << "Перегруженный оператор присваивания Mammal" << endl;

/*Проверка на выделенность памяти. Если выделено-очищаем*/

if (this->mammalFeatures != nullptr)

{

delete[] this->mammalFeatures;

}

/*Выделяем память*/

this->mammalFeatures = new Spine[1];

/*Копируем поля объекта mammal в weightMammal и в цикле mammalFeatures соответственно*/

weightMammal = mammal.weightMammal;

this->mammalFeatures[0] = mammal.mammalFeatures[0];

/*Возвращаем текущий объект*/

return *this;

}

/*Объявляем деструктор*/

~Mammal()

{

/*Производим отладочную печать работы деструктора и освобождаем выделенную память для mammalFeatures*/

cout << "Деструктор Mammal" << endl;

delete[] mammalFeatures;

}

/*Объявляем setter для записи значения в private weightMammal*/

void setWeightMammal(float weightMammal)

{

this->weightMammal = weightMammal;

}

/*Объявляем getter для получения значения из private weightMammal*/

float getWeightMammal()

{

return weightMammal;

}

/*Метод вывода print полей класса*/

void print()

{

/*Вызываем метод print из класса Spine*/

mammalFeatures->print();

cout << "Вес млекопитающего: " << weightMammal << " кг." << endl;

}

};

/*Объявляем класс Dog(Собака), который наследуется с видом наследования public от класса Mammal*/

class Dog : public Mammal

{

/*Объявляем переменную nameDogs типа string с модификатором доступа private*/

private:

string nameDogs;

public:

/*Объявляем конструктор по умолчанию, который,

в свою очередь, в инициализации конструктора инициализирует конструктор по умолчанию базового класса(Mammal)*/

Dog() : Mammal()

{

/*Производим отладочную печать работы конструктора по умолчанию и инициализируем переменную nameDogs*/

cout << "Конструктор по умолчанию Dog" << endl;

nameDogs = "Петруша";

}

/*Объявляем конструктор с параметрами, который принимает в качестве параметров четрые переменные:

lengthSpine типа float, nameSpine типа string и weightMammal типа float и nameDogs типа string,

в свою очередь, в инициализации конструктора инициализирует конструктор с параметрами базового класса(Mammal).

Также в конструкторе инициализации инициализируем nameDogs полученным значением*/

Dog(float lenghtSpine, string nameSpine, float weightMammal, string nameDogs) :

Mammal(lenghtSpine, nameSpine, weightMammal), nameDogs(nameDogs)

{

/*Производим отладочную печать работы конструктора с параметрами*/

cout << "Конструктор с параметрами Dog" << endl;

}

/*Объявляем конструктор копирования Dog, который принимает в качестве параметров объект Dog

с именем cpyPug.

В инициализации конструктора инициализируем конструктор базового класса(Mammal) и переменную nameDogs скопированным значением */

Dog(const Dog& cpyDog) : Mammal(cpyDog), nameDogs(cpyDog.nameDogs)

{

/*Производим отладочную печать работы конструктора копирования*/

cout << "Конструктор копирования Dog" << endl;

}

/*Объявляем деструктор класса Dog*/

~Dog()

{

/*Производим отладочную печать работы деструктора*/

cout << "Деструктор Dog" << endl;

}

/*Объявляем setter для записи значения в private nameDogs*/

void setNameDogs(string nameDogs)

{

this->nameDogs = nameDogs;

}

/*Объявляем getter для получения значения из private nameDogs*/

string getNameDogs()

{

return nameDogs;

}

/*Метод вывода print полей класса*/

void print()

{

/*Вызываем функция print базового класса(Mammal)*/

Mammal::print();

cout << "Кличка собаки: " << nameDogs << endl;

}

};

int main()

{

/*Объявляем переменные lengthSpine и weightMammal типа float и nameSpine типа string*/

float lengthSpine, weightMammal;

string nameSpine, nameDogs;

/*Устанавливаем русский язык*/

system("chcp 1251");

/*Очищаем окно консоли*/

system("cls");

cout << "Введите название позвоночника: ";

/*Считываем переменную nameSpine, введенной с клавиатуры*/

cin >> nameSpine;

cout << "Введите длину позвоночника: ";

/*Считываем переменную lengthSpine, введенной с клавиатуры*/

cin >> lengthSpine;

cout << "Введите вес млекопитающего: ";

/*Считываем переменную weightMammal, введенной с клавиатуры*/

cin >> weightMammal;

Соседние файлы в папке Описания лабораторных работ