Дополнить систему, состоящую из трех классов “Класс №1”, “Класс №2” и “Класс №3”, которые были разработаны в лабораторной работе 3, новым классом “Класс №4”. Новый класс должен быть связан public наследованием с классом “Класс №3”. Класс “Класс №4” должен иметь одно поле, которое выбирается студентом самостоятельно. Для разрабатываемого класса написать конструкторы умолчания, с параметрами и конструктор копирования, деструктор, методы доступа и метод print(). Метод print() в классах “Класс №2”, “Класс №3” и “Класс №4” должен быть виртуальным. Написать тестовую программу для проверки работоспособности разработанных классов. Разработать глобальную функцию printAll(), имеющую два параметра: массив указателей типа “Класс №2”* и количество элементов в этом массиве int n.
В тестовой программе массив указателей должен быть инициализирован адресами объектов типа “Класс №2”, “Класс №3” и “Класс №4”.
|
|
Класс №3 |
Свойства (Класс №3) |
Класс №4 |
Свойства (Класс №4) |
|
Пример |
СDog (Собака) |
String NameDogs (Имя собаки, например – Ушастик) |
CPug (Мопс) |
Int AgePugs (Возраст Мопса, например – 5) СDog obj |
Иногда требуется , чтобы базовый класс определял только интерфейс лля порожденных классов. В этом случае следует запретить в клиентском коде создание объектов базового класса. С этой целью базовый класс объявляется абстрактным. Для этого в базовом классе необходимо объявить хотя бы одну чисто виртуальную функцию.
Объявление чисто виртуальной функции должно начинаться с зарезервированного слова virtual и заканчиваться символами «= 0;». Такую виртуальную функцию иногда ассоциируют с нулевым указателем.
Пример.
class Shape { public: virtual void Draw(); // ... };
Если попытаться создать объект для класса, содержащего хотя бы одну чисто виртуальную функцию, то компилятор выдаст сообщение об ошибке.
Пример.
Shape; // Ошибка
Объект абстрактного класса не может быть параметром функции.
Например.
void foo(Shape sh); // Ошибка
Абстрактный класс нельзя использовать в качестве значения, возвращаемого функцией.
Например.
Shape foo(); // Ошибка
Допускается использовать указатель и ссылки для абстрактных классов.
Например.
Shape& f(Shape& sh); // Нормально Shape* ps; // Нормально
При наследовании от абстрактного класса все чисто виртуальные функции должны быть переопределены, иначе производный класс будет абстрактным.
Абстрактные классы вида Shape воплощают отвлеченные понятия.
Для абстрактных классов таблица виртуальных методов оказывается неполной. Строки этой таблицы, соответствующие чисто виртуальным функциям, оказываются незаполненными.
Отметим, что чисто виртуальные функции могут иметь реализацию.
Виртуальный деструктор оказывается необходимым в тех случаях, когда необходимо предусмотреть полиморфную работу с объектами через указатель или ссылку на базовый класс.
Пусть имеются два класса B и D, связанные public – наследованием.
class B { public: B(); ~B()003B }; class D : public B { public: D(); ~D(); }
Предположим теперь, что имеется следующий клиентский программный код
int main() {
B* pb = new B; // ... delete pb; // ... }
Рассмотрим два случая. Вначале предположим, что деструктор в классе B является виртуальным. В этом случае оператор delete вызовет деструктор класса D, который после завершения своей работы вызовет деструктор базового класса. Это обусловлено тем обстоятельством, что при работе с виртуальными функциями определяющим является динамический тип указателя.
Перейдем к рассмотрению второго случая, когда деструктор базового класса является невиртуальным. В этом случае определяющим является статический тип указателя. В результате будет вызван только деструктор базового класса.
Автономный класс – это одиночный класс, от которого не предполагается порождать другие классы. Это класс, который не предполагается использовать в качестве базового класса. Как отличить такой класс от класса, от которого предполагается порождать другие классы.
Рекомендация.
Признаком того, что рассматриваемый класс может служить в качестве базового класса является наличие в нем виртуального деструктора.
Деструктор может быть сделан чисто виртуальным. Особенности использования чисто виртуальных деструкторов состоят в следующем:
Чисто виртуальные деструкторы должны иметь реализацию.
Чисто виртуальные деструкторы не обязательно переопределять в порожденном классе. Компилятор сгенерирует недостающий деструктор.
Объявление деструктора чисто виртуальным позволяет сделать класс абстрактным.
Пример на основе 4 классов: Spine, Mammal, Dog и Pug
#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 полей класса*/
virtual 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;
int agePugs;
int n = 0;
string nameSpine, nameDogs;
/*Устанавливаем русский язык*/
system("chcp 1251");
/*Очищаем окно консоли*/
system("cls");
cout << "Введите название позвоночника: ";
/*Считываем переменную nameSpine, введенной с клавиатуры*/
cin >> nameSpine;
cout << "Введите длину позвоночника: ";
/*Считываем переменную lengthSpine, введенной с клавиатуры*/
cin >> lengthSpine;
cout << "Введите вес млекопитающего: ";
/*Считываем переменную weightMammal, введенной с клавиатуры*/
cin >> weightMammal;
cout << "Введите кличку собаки: ";
/*Считываем переменную nameDogs, введенной с клавиатуры*/
cin >> nameDogs;
cout << "Сколько лет собаке? ";
/*Считываем переменную agePugs, введенной с клавиатуры*/
cin >> agePugs;
cout << "----------------------------------------------------------------------------" << "\n"
<< "----------------------------------------------------------------------------" << endl;
/*Объявляем экземпляр класса Pug с именем defaultPug, который вызывает конструктор по умолчанию*/
Pug defaultPug;
/*Выводим на экран получившиеся данные*/
defaultPug.print();
cout << "----------------------------------------------------------------------------" << "\n"
<< "----------------------------------------------------------------------------" << endl;
/*Объявляем экземпляр класса Pug с именем pugWithUserConstr, который вызывает конструктор с параметрами*/
Pug pugWithUserConstr(lengthSpine, nameSpine, weightMammal, nameDogs,agePugs);
/*Выводим на экран получившиеся данные*/
pugWithUserConstr.print();
cout << "----------------------------------------------------------------------------" << "\n"
<< "----------------------------------------------------------------------------" << endl;
/*Объявляем экземпляр класса Pug с именем cpyPug, который вызывает конструктор копирования*/
Pug cpyPug(defaultPug);
/*Выводим на экран получившиеся данные*/
cpyPug.print();
cout << "----------------------------------------------------------------------------" << "\n"
<< "----------------------------------------------------------------------------" << endl;
cout << "Введите количество элементов для массива ";
/*Считываем переменную n, введенной с клавиатуры*/