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

Класс-потомок наследует структуру (все элементы данных) и поведение (все методы) базового класса. Класс-наследник получает в наследство все поля базового класса (хотя, если они были приватные, доступа к ним не имеет). Если новые поля не добавляются, размер класса-наследника совпадает с размером базового класса. Порожденный класс может добавить собственные поля.

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

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

Класс-потомок наследует все методы базового класса, кроме операции присваивания - она создается для нового класса автоматически, если не определена явно. В классе-наследнике можно определять новые методы. В новых методах разрешается вызывать любые доступные методы базового класса.

Если в классе-наследнике имя метода и его прототип совпадают с именем метода базового класса, то говорят, что метод производного классаскрывает метод базового класса. Чтобы вызвать метод родительского класса, нужно указывать его с квалификатором класса.

Объекты класса-наследника могут использовать операции базового класса.

 

Операция присваивания и принцип подстановки

Открытое наследование устанавливает между классами отношение «является»: класс-наследник является разновидностью класса-родителя. Это означает, что везде, где может быть использован объект базового класса (при присваивании, при передаче параметров и возврате результата), вместо него разрешается подставлять объект производного класса. Данное положение называется принципом подстановки и поддерживается компилятором. Этот принцип работает и для ссылок, и для указателей: вместо ссылки (указателя) на базовый класс может быть подставлена ссылка (указатель) на класс-наследник. Обратное - неверно! Например, спортсмен является человеком, но не всякий человек - спортсмен. Здесь человек - базовый класс, а спортсмен - производный.

Помимо конструкторов, не наследуются два вида функций: операция присваивания и дружественные функции.

Операция присваивания, как и конструкторы с деструктором, для любого класса создается автоматически и имеет прототип:

класс& класс::operator=(const класс &t);

Операция бинарная, левым аргументом является текущий объект. Эта стандартная операция обеспечивает копирование значений полей объекта t в поля текущего объекта. Для базового класса Base и его наследника Derive соответствующие операции присваивания имеют вид:

Base& Base::operator=(const Base &t);

Derive& Derive::operator=(const Derive &t);

Наличие этих функций позволяет выполнять следующие присваивания:

Base b1,b2; // переменные базового класса

Derive dl,d2; // переменные производного класса

b1 = b2; // операция базового класса

dl = d2; // операция производного класса

// базовый=производный; операция базового класса

b1= d2;

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

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

При перегрузке операций для классов, находящихся в отношении наследования появляются особенности.

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

 

Рассмотрим пример1 программы, демонстрирующей особенности обращений из дружественных функций производного класса к дружественным функциям базового класса. В данном примере ввод-вывод объектов наследников изменяется.

//Листинг 18.1

#include <iostream>

using namespace std;

//Базовый класс

class Base{

protected: int k;

friend istream& operator>>(istream & stream,Base &ob);

friend ostream& operator<<(ostream & stream,Base &ob);

};

istream& operator>>(istream& stream,Base &ob)

{cout<<"k=";

stream>>ob.k;

return stream;

}

ostream& operator<<(ostream& stream,Base &ob)

{

stream<<"k="<<ob.k<<endl;

return stream;

}

// Производный класс

class Dir:public Base

{

double z;

friend istream& operator>>(istream & stream,Dir &ob);

friend ostream& operator<<(ostream & stream,Dir &ob);

};

 

istream& operator>>(istream& stream,Dir &ob)

{ cout<<"Base:";

//выделение из объекта производного класса //базовой части

operator>>(stream,dynamic_cast<Base&>(ob));

cout<<"Dir::z=";

stream>>ob.z;

return stream;

}

ostream& operator<<(ostream& stream,Dir &ob)

{ //вывод только базовой части

//производного класса

stream<<dynamic_cast<Base&>(ob);

stream<<"z="<<ob.z<<endl;

return stream;

}

int main()

{

Dir one,two;

cin>>one;

two=one;

cout<<two;

return 0;

}

 

Результаты выполнения программы:

Base:k=5

Dir::z=4.8

k=5

z=4.8

В теле операции-функции operator>>() производного класса Dir обращение

operator>>(stream,dynamic_cast<Base&>(ob));

обеспечивает ввод значений полей данных только базовой части объекта Dir ob. Затем выполняется ввод значения поля данных z, принадлежащего объекту производного класса. Приведение dynamic_cast<Base&>(ob) выделяет из объекта производного класса базовую часть. Очень важна роль приведения именно ссылки на объект производного класса Dir& к ссылке на объект базового класса Base&. Попытка приводить сами объекты приведет к неудаче.

В теле операции-функции operator<<() производного класса Dir оператор

stream<<dynamic_cast<Base&>(ob);

обеспечивает вывод только базовой части производного объекта. Вывод значения поля double z производного класса выполняется явным образом.

Для приведения указателей и ссылок лучше использовать специальные операции языка С++

static_cast<целевой тип>(выражение)

dynamic_cast<целевой тип>(выражение),

где выражение – это ссылка или указатель. Эта операция объединяет приведения типа и проверку допустимости этого преобразования во время исполнения.

Операция dynamic_cast объединяет приведение типа и проверку допустимости этого преобразования во время исполнения. При приведении указателей выражение dynamic_cast<Т*>(р) преобразует операнд р к нужному типу T*, если только это возможно. В противном случае будет возвращено значение 0. При приведении ссылок выражение dynamic_cast <Т&> (r) операнд r будет приведен к типу T&. Если приведение к этому ссылочному типу заканчивается неудачно, возникает исключение bad_cast.

 

Рассмотрим пример 2 программы на создание иерархии классов:

Круг - все точки плоскости, удовлетворяющие условию, расстояние от одной точки (x,y) (центра) до любой из этих точек меньше или равно R. Длина окружности = 2*p*R. Площадь круга = p*R2. В рассматриваемой иерархии за базовое понятие принята точка. Круг как производное понятие можно представить как точку (центр круга), дополненную радиусом R.

Круглый прямой цилиндр имеет в основании круг, и его образующие перпендикулярны к плоскости основания, R - радиус основания. Площадь поверхности = 2*Scircle + 2*p*R*h. Объем цилиндра = Scircle*h. Круглый прямой цилиндр как производное понятие можно представить как круг, дополненный высотой h.

 

Сначала мы опишем класс Point. Затем - класс Circle, который является производным классом от класса Point. И, наконец, - класс Cylinder, который является наследником класса Circle.

//Листинг 18.2

#include <iostream>

using namespace std;

class Point {

protected://доступно для производных классов

double x,y; //координаты Point x и y

public:

// конструктор с умолчанием

Point(double =0,double=0);

 

// установка координат

void setPoint(double,double);

double getX(){return x;} // получение x

double getY(){return y;} // получение y

friend istream& operator>>(istream & stream,Point &ob);

friend ostream& operator<<(ostream & stream,Point &ob);

};

 

//Функции-элемента класса Point

Point::Point(double a,double b)

{setPoint (a,b);}

void Point::setPoint(double a,double b)

{x=a; y=b;}

//дружественная функция –

//перегруженная операция вставки в поток

ostream& operator<<(ostream & stream,Point &ob)

{stream<<"("<<ob.x<<", "<<ob.y<<")\n";

return stream;}

 

//дружественная функция –

//перегруженная операция извлечения из потока

istream& operator>>(istream & stream,Point &ob)

{stream>>ob.x>>ob.y;

return stream;}

 

//Класс Circle наследует

//классу Point путем открытого наследования

 

//Circle наследует Point

class Circle:public Point {

protected://доступно для производных классов

double radius;

public:

//конструктор с умолчанием

Circle(double r=0.0,double x=0,double y=0);

void setRadius(double); //установка радиуса

double getRadius(); //возвращение радиуса

double area(); //вычисление площади круга

friend istream& operator>>(istream & stream,Circle &ob);

friend ostream& operator<<(ostream & stream,Circle &ob);

};

 

//определение функций-элементов класса Circle

//Конструктор Circle вызывает конструктор Point

// с инициализаторами элементов,

//затем инициализирует радиус

Circle::Circle(double r,double a,double b)

:Point(a,b) //вызов конструктора базового класса

{radius=r;}

 

void Circle::setRadius(double r)

{radius=r;}

 

double Circle::getRadius()

{return radius;}

double Circle::area()

{return 3.14159*radius*radius;}

 

//дружественная функция –перегруженная операция извлечения из потока

istream& operator>>(istream& stream,Circle &ob)

{ cout<<"Point(x,y): ";

//выделение из объекта

//производного класса базовой части

operator>>(stream,dynamic_cast<Point&>(ob));

cout<<"Circle::radius=";

stream>>ob.radius;

return stream;

}

 

//дружественная функция –перегруженная операция вставки в поток

ostream& operator<<(ostream& stream,Circle &ob)

{ //вывод только базовой части

//производного класса

stream<<dynamic_cast<Point&>(ob);

stream<<"r="<<ob.radius<<endl;

return stream;

}

 

//класс Cylinder наследует Circle

class Cylinder:public Circle

{

protected:

double height; //высота цилиндра

public:

//конструктор с умолчанием

Cylinder(double h=0.,double r=0.,

double x=0.,double y=0.);

void setHeight(double); //установка высоты

double getHeight(); //возвращение высоты

//вычисление площади поверхности цилиндра

double area();

double volume(); //вычисление объема

friend istream& operator>>(istream & stream,Cylinder &ob);

friend ostream& operator<<(ostream & stream,Cylinder &ob);

};

 

//Конструктор Cylinder вызывает

//конструктор Circle

Cylinder::Cylinder(double h,double r,double x,double y)

:Circle(r,x,y) //вызов конструктора

//базового класса

{height=h;}

 

//Установка высоты цилиндра

void Cylinder::setHeight(double h)

{height=h;}

 

// Получение высоты цилиндра

double Cylinder::getHeight()

{return height;}

//Вычисление площади поверхности цилиндра

//Переопределение функции

//area() базового класса

double Cylinder::area()

{

//Вызов функции area() класса Circle

//с помощью операции::

return 2*Circle::area()+2*3.14159*radius*height;

}

 

//Вычисление объема цилиндра

double Cylinder::volume()

{

//Вызов функции area() класса Circle

//с помощью операции::

return Circle::area()*height;}

 

//дружественная функция –

//перегруженная операция извлечения из потока

istream& operator>>(istream& stream,Cylinder &ob)

{

//выделение из объекта производного класса //базовой части

operator>>(stream,dynamic_cast<Circle&>(ob));

cout<<"Cylinder::height=";

stream>>ob.height;

return stream;

}

//дружественная функция –

//перегруженная операция вставки в поток

ostream& operator<<(ostream& stream,Cylinder &ob)

{

//вывод только базовой части

//производного класса

stream<<dynamic_cast<Circle&>(ob);

stream<<"h="<<ob.height<<endl;

return stream;

}

 

int main()

{

Point *pPtr,p(3.5,5.3);

Circle *cPtr,c(2.7,1.2,8.9),b;

 

//Ввод с клавиатуры значений полей-данных //объекта b класса Circle

cin>>b;

cout<<endl;

//Вывод на экран информации об объектах p,c и b

cout<<p<<endl<<c<<endl<<b<<endl;

 

//Circle как Point

//(видит только часть производного класса)

//присваивание адреса Circle указателю pPtr

pPtr=&c;

//вывод на экран информации об объекте c

//через указатель pPtr

cout<<(*pPtr)<<endl;

//Circle как Point (с помощью привидения типов)

//приведение базового к производному типу

cPtr=(Circle *)pPtr;

//вывод на экран информации об объекте c

//через указатель cPtr

cout<<(*cPtr)<<endl;

//правильное значение

cout<<cPtr->area()<<endl<<endl;

 

//Опасность!!! Рассмотрение Point как Circle

//адрес объекта класса Point присваивается //указателю pPtr

pPtr=&p;

//приведение базового к производному типу

cPtr=(Circle *)pPtr;

//на экран выведется "странное"

//значение радиуса

cout<<(*cPtr)<<endl;

//"странное" значение,т.к. нет радиуса

cout<<cPtr->area()<<endl<<endl;

 

Cylinder cyl(5.7,2.5,1.2,2.3);

cout<<cyl<<endl;

cyl.setHeight(10);

cyl.setRadius(4.25);

cyl.setPoint(2,2);

cout<<cyl<<endl; //новые данные у цилиндра

 

//отображение цилиндра как Point

//pRef "думает", что это Point

Point &pRef=cyl;

//вывод на экран информации об объекте cyl //через ссылку pRef

cout<<pRef<<endl;

//отображение цилиндра как Circle

//cRef "думает", что это Circle

Circle &cRef=cyl;

//вывод на экран информации об объекте cyl //через ссылку сRef

cout<<cRef<<endl;

return 0;

}

 

Результаты выполнения программы:

Point(x,y): 4.5 7.2

Circle::radius=5.1

(3.5, 5.3)

(1.2, 8.9)

r=2.7

(4.5, 7.2)

r=5.1

(1.2, 8.9)

(1.2, 8.9)

r=2.7

22.9022

(3.5, 5.3)

r=-9.25596e+061

2.69149e+124

(1.2, 2.3)

r=2.5

h=5.7

(2, 2)

r=4.25

h=10

(2, 2)

(2, 2)

r=4.25

 

Задание:

Написать программу на языке С++ согласно варианту. Во всех вариантах реализовать перегрузку операций << и >>, методы получения значений полей и методы установки значений полей, а также необходимые конструкторы (если это не указано в задании явно). Конструкторы и методы обязательно должны проверять параметры на допустимость; в случае неправильных данных — выводить сообщение об ошибке и заканчивать работу.

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

Создать многофайловый проект.

 

Вариант 1

Создать базовый класс Саг (машина), характеризуемый торговой маркой (строка), числом цилиндров, мощностью. Определить методы переназначения и изменения мощности. Создать производный класс Lorry (грузовик), характеризуемый также грузоподъемностью кузова. Определить функции переназначения марки и изменения грузоподъемности.

 

Вариант 2

Создать класс Pair (пара чисел); определить методы изменения полей и сравнения пар: пара p1 больше пары р2, если (first.p1 > first.p2) или (first.p1 = first.р2) и (second.p1 > second.р2). Определить класс-наследник Fraction с полями: целая часть числа и дробная часть числа. Определить полный набор методов сравнения.

 

Вариант 3

Создать класс Liquid (жидкость), имеющий поля названия и плотности. Определить методы переназначения и изменения плотности. Создать производный класс Alcohol (спирт), имеющий крепость. Определить методы переназначения и изменения крепости.

 

Вариант 4

Создать класс Pair (пара чисел); определить методы изменения полей и вычисления произведения чисел. Определить производный класс Rectangle (прямоугольник) с полями-сторонами. Определить методы вычисления периметра и площади прямоугольника.

 

Вариант 5

Создать класс Man (человек), с полями: имя, возраст, пол и вес. Определить методы переназначения имени, изменения возраста и изменения веса. Создать производный класс Student, имеющий поле года обучения. Определить методы переназначения и увеличения года обучения.

Вариант 6

Создать класс Triad (тройка чисел); определить методы изменения полей и вычисления суммы чисел. Определить производный класс Triangle с полями-сторонами. Определить методы вычисления углов и площади треугольника.

 

Вариант 7

Создать класс Triangle с полями-сторонами. Определить методы изменения сторон, вычисления углов, вычисления периметра. Создать производный класс Equilateral (равносторонний), имеющий поле площади. Определить метод вычисления площади.

 

 

Вариант 8

Создать класс Triangle с полями-сторонами. Определить методы изменения сторон, вычисления углов, вычисления периметра. Создать производный класс RightAngled (прямоугольный), имеющий поле площади. Определить метод вычисления площади.

 

Вариант 9

Создать класс Pair (пара чисел); определить методы изменения полей и вычисления произведения чисел. Определить производный класс RightAngled с полями-катетами. Определить методы вычисления гипотенузы и площади треугольника.

 

Вариант 10

Создать класс Triad (тройка чисел); определить метод сравнения триад (аналогично варианту 2). Определить производный класс Date с полями: год, месяц и день. Определить полный набор методов сравнения дат.

 

Вариант 11

Создать класс Triad (тройка чисел); определить метод сравнения триад (аналогично варианту 2). Определить производный класс Time с полями: час, минута и секунда. Определить полный набор методов сравнения моментов времени.

 

Вариант 12

Реализовать класс-оболочку Number для числового типа float. Реализовать методы сложения и деления. Создать производный класс Real, в котором реализовать метод возведения в произвольную степень, и метод для вычисления логарифма числа.

 

Вариант 13

Создать класс Triad (тройка чисел); определить методы увеличения полей на 1. Определить производный класс Date с полями: год, месяц и день. Переопределить методы увеличения полей на 1 и определить метод увеличения даты на n дней.

 

Вариант 14

Реализовать класс-оболочку Number для числового типа double. Реализовать методы умножения и вычитания. Создать производный класс Real, в котором реализовать метод, вычисляющий корень произвольной степени, и метод для вычисления числа n в данной степени.

 

Вариант 15

Создать класс Triad (тройка чисел); определить методы увеличения полей на 1. Определить класс-наследник Time с полями: час, минута, секунда. Переопределить методы увеличения полей на 1 и определить методы увеличения на n секунд и минут.

 

Вариант 16

Создать базовый класс Pair (пара целых чисел) с операциями проверки на равенство и перемножения полей. Реализовать операцию вычитания пар по формуле (а, b) - (с, d) = (а - b, с - d). Создать производный класс Rational; определить новые операции сложения (a, b) + (с, d) = (ad + be, bd) и деления (a, b) / (с, d) = (ad, bc), переопределить операцию вычитания (a, b) - (с, d) = = (ad - be, bd).

 

Вариант 17

Создать класс Pair (пара чисел); определить метод перемножения полей и операцию сложения пар (а, b) + (с, d) = (а + b, с + d). Определить производный класс Complex с полями: действительная и мнимая части числа. Определить методы умножения (а, b)*(c,d) = (ас - bd, ad + be) и вычитания (а, b) - - (с, d) = (а - b, с - d).

 

Вариант 18

Создать класс Pair (пара целых чисел); определить методы изменения полей и операцию сложения пар (а, b) + (с, d) = (а + b, с + d). Определить класс-наследник Long с полями: старшая часть числа и младшая часть числа. Переопределить операцию сложения и определить методы умножения и вычитания.

Вариант 19

Создать базовый класс Triad (тройка чисел) с операциями сложения с числом, умножения на число, проверки на равенство. Создать производный класс vector3D, задаваемый тройкой координат. Должны быть реализованы: операция сложения векторов, скалярное произведение векторов.

 

Вариант 20

Создать класс Pair (пара целых чисел); определить метод умножения на число и операцию сложения пар (а, b) + (с, d) = (а + b, с + d). Определить класс-наследник Money с полями: рубли и копейки. Переопределить операцию сложения и определить методы вычитания и деления денежных сумм.

 



Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  




Подборка статей по вашей теме: