Порядок выполнения лабораторной работы. 1. Внимательно изучите теоретическую часть методического указания.
1. Внимательно изучите теоретическую часть методического указания.
2. Разработайте класс представления окна на экране компьютера. В состав класса должны входить деструктор и следующие конструкторы:
-конструктор по умолчанию;
-конструктор с параметрами;
-копирующий конструктор.
Создайте несколько экземпляров класса. В деструкторе и конструкторах предусмотрите вывод сообщения, чтобы было наглядно видно, когда осуществляется их вызов.
3. Преобразуйте разработанный в предыдущей работе класс так, чтобы в нем присутствовали деструктор и несколько конструкторов.
4. Оформите отчет
Содержание отчета
1. Название работы.
2. Цель работы.
3. Программа, разработанная по заданию 1.
4. Программа, разработанная по заданию 2.
5. Выводы по работе
Контрольные вопросы
1. Каково назначение конструктора и деструктора?
2. Какие свойства имеет конструктор?
3. Какие свойства имеет деструктор?
4. Можно ли поручить конструктору выполнять другую работу, кроме создания и инициализации объектов класса?
5. Какой конструктор называется конструктором по умолчанию? В каком случае он необходим?
6. Приведите формат конструктора со списком инициализации. В каких случаях он необходим?
7. Какой конструктор называется конструктором копирования?
8. Как копирует конструктор копирования, созданный компилятором?
9. Какие конструкторы являются функциями преобразования?
10. Каково назначение спецификатора объявления explicit?
ЛАБОРАТОРНАЯ РАБОТА №5
ПРОСТОЕ И МНОЖЕСТВЕННОЕ НАСЛЕДОВАНИЕ
Цель работы: программирование и использование классов, специализированных с помощью отношения простого и множественного наследования.
Теоретическая часть
Одно из преимуществ системного программирования – многократное использование разработанных объектов. Реализовать это преимущество позволяет механизм наследования.
Под наследованием обычно понимают то, что потомок получает определенные характерные черты своих родителей. Это справедливо и для классов в С++. В С++ можно описать классы родителей (базовые классы) и классы потомков (производные классы). Производные классы наследуют члены базового класса. Производный класс описывается при помощи следующего синтаксиса:
class <имя потомка> : [<спецификатор доступа>] <имя родителя>
{ …};
Спецификатор доступа – это одно из трех ключевых слов: public, private или protected. Этот спецификатор определяет то, как элементы базового класса наследуются производным классом: как открытые, как закрытые или как защищенные. Открытое наследование сохраняет статус доступа всех элементов базового класса, защищенное – понижает статус доступа public элементов базового класса до protected, и, наконец, закрытое – понижает статусы доступа public и protected элементов базового класса до private. Если спецификатор доступа не указан, то элементы базового класса по умолчанию наследуются как закрытые.
Рассмотрим пример. Создадим класс, описывающий фигуру с координатами x, y и z. Класс содержит члены – функции, которые устанавливают и возвращают каждую координату отдельно.
class Shape
{ protected:
float x, y, z; //координаты фигуры
public:
void SetX( float xcoord) //функция установки координаты х
{ x = xcoord;}
void SetY( float ycoord) //функция установки координаты y
{ y = ycoord;}
void SetZ( float zcoord) //функция установки координаты z
{ z = zcoord;}
float GetX ( ) //функция, которая возвращает координату х
{ return x;}
float GetY ( ) //функция, которая возвращает координату y
{ return y;}
float GetZ ( ) //функция, которая возвращает координату z
{ return z;}
};
В описании класса Shape имеется метка видимости protected - защищенный уровень. Защищенные члены базового класса доступны производному классу точно так же, как если бы они были объявлены как общие члены, но не доступны для функций, не являющихся членами класса- потомка.
Теперь создадим производный класс Sphere.
class Sphere: public Shape
{
float r;
public:
void SetR (float radius)
{r = radius;}
float GetR( )
{ return r;}
float Surface_area ( )
{ return r*r*4*3.14159;}
};
После имени класса Sphere стоит двоеточие, а затем имя базового класса. Таким образом, класс Sphere является производным от класса Shape и, следовательно, наследует координаты х, y, z и функции установки и получения этих координат из базового класса. Благодаря этому заново объявлять эти данные и функции не нужно, и проектирование производного класса упрощается. Производный класс можно рассматривать как расширение базового класса, объявив в нем только специфичные данные и функции. Так в классе объявлены радиус и функции установки и получения значения радиуса, а также функция определения площади поверхности сферы. На основе одного базового класса можно создать целый ряд производных классов, например, на базе класса фигура можно создать классы, которые описывают конус, пирамиду и т.д.
Если одно и то же имя атрибута или метода встречается как в базовом классе, так и в производном, то производный класс перекрывает базовый. Однако метод базового класса не исчезает. Просто при поиске имени сначала просматриваются атрибуты и методы самого класса, а уже потом, если имя не найдено, поиск продолжается в базовом классе. С помощью записи
имя базового класса :: имя члена класса
можно вызвать функцию именно из базового класса.
При создании объекта производного класса должны вызываться и конструктор самого производного класса, и конструктор базового класса, причем конструктор базового класса должен вызываться раньше. Если конструкторы в классах не объявлены, то они будут сгенерированы и вызваны автоматически в нужном порядке.
Для деструкторов правилен обратный порядок: деструктор производного класса выполняется раньше деструктора базового класса. Если нет необходимости освобождать какие–либо ресурсы, выделенные в конструкторе, то деструктор для производного класса можно не определять и доверить компилятору создать деструктор по умолчанию. В нем обеспечивается вызов деструктора базового класса.
Конструктор может использоваться не только для создания объектов класса, но и для инициализации атрибутов класса. Когда инициализация проводится только в производном классе, аргументы передаются конструктору обычным способом. Если же в базовом классе объявлен конструктор с параметрами, то он должен быть явно описан и в конструкторе производного класса с указанием тех параметров, которые передаются конструктору базового класса для инициализации. В этом случае используется расширенная форма объявления конструктора производного класса, синтаксис этой формы приведен ниже:
<конструктор класса – потомка> (<список аргументов>) :
<конструктор класса - родителя>(<список аргументов>)
{
//… тело конструктора производного класса
}
Например, если бы в классе Shape был описан конструктор, инициализирующий координаты х, y, z, то конструктор производного класса должен быть объявлен следующим образом:
Sphere (float _x, float _ y, float _ z, float _ r) : Shape ( _x, _y, _z)
{r = _r;}
В примере конструктору производного класса передаются все 4 параметра, необходимые конструкторам обоих классов. Затем конструктор производного класса передает конструктору базового класса те аргументы, которые ему требуются.
Производный класс может наследовать более одного базового класса. Это может осуществляться двумя способами.
В первом способе производный класс может использоваться в качестве базового для другого производного класса, создавая многоуровневую иерархию классов. Изображая отношения наследования, их часто рисуют в виде иерархии или дерева.
Рис.1. Пример иерархии классов.
Иерархия классов может быть сколь угодно глубокой. На рис.1 изображен класс А, который является непосредственным или прямым базовым классом для классов B и C, а для класса D класс А является косвенным базовым классом. Таким образом, класс D наследует два класса. Ниже приведен пример реализации такого наследования.
#include <iostream.h>
class A
{ int a;
public:
A (int a1) { a = a1; }
int Geta ( ) { return a; }
};
// прямое наследование базового класса
class C: public A
{ int c;
public:
C (int a1, int c1) : A ( a1 )
{ c = c1; }
int Getc ( ) { return c; }
};
// прямое наследование производного класса и
// косвенное наследование базового класса
class D: public C
{ int d;
public:
D (int a1, int c1, int d1) : C ( a1, c1 )
{ d = d1; }
void Print ( )
{
cout << Geta ( ) <<’ ‘ << Getc ( ) << ‘ ‘ << d << “\n”;
}
};
main ( )
{
D ob ( 1, 2, 3 );
ob. Print ( );
// функции Geta ( ) и Getc ( ) здесь тоже открыты
cout << ob.Geta ( ) << “ “ << ob.Getc ( ) << “\n”;
return 0;
}
Как показано в примере, каждый класс иерархии классов должен передавать все аргументы, необходимые каждому предшествующему базовому классу. Невыполнение этого правила приведет к ошибке при компиляции программы.
Второй способ основан на том, что производный класс может прямо наследовать более одного базового класса. Такое наследование называется множественным. В этой ситуации созданию производного класса помогает комбинация двух или более базовых классов.
Рис.2. Пример множественного наследования
На рис 2. изображен класс С, который прямо наследует два класса А и В.
Для объявления производного класса при множественном наследовании используется следующий формат:
class <имя потомка>: <спецификатор доступа><имя родителя1>,
<спецификатор доступа><имя родителя2>,
…, <спецификатор доступа><имя родителяN>
{
// … тело класса
}
Спецификатор доступа может быть разным у разных базовых классов или опущен, в последнем случае по умолчанию элементы базовых классов наследуются как закрытые.
При создании объектов производного класса сначала вызываются конструкторы всех базовых классов, причем слева направо в том порядке, который задан в объявлении производного класса, и в последнюю очередь вызывается конструктор самого базового класса. При уничтожении объекта производного класса деструкторы вызываются в порядке, обратном вызову конструкторов.
Когда производный класс наследует несколько базовых классов, конструкторам которых необходимы аргументы, он должен передавать эти аргументы, используя расширенную форму объявления конструктора производного класса:
<конструктор класса –потомка> (<список аргументов>) :
<имя родителя1>(<список аргументов>),
<имя родителя2>(<список аргументов>),
…, <имя родителяN>(<список аргументов>)
{
//… тело конструктора производного класса
}
Рассмотрим пример. Пусть для перемещения фигур используется специальный класс, который назовем Move. Атрибутами этого класса являются сдвиги по осям координат. Теперь объявим производный класс Sphere от базовых классов Shape и Move.
class Sphere: public Shape, public Move
{
//… тело класса
};
Для создания конструктора с параметрами используем расширенную форму объявления конструктора производного класса:
Sphere (float _x, float _ y, float _ z, float _ r, float mov_x,
float mov_y, float mov_z) : Shape ( _x, _y, z), Move (mov_x , mov_y, mov_z)
{r = _r;}
Конструктор производного класса получил все необходимые аргументы и передал конструкторам базовых классов те аргументы, которые им нужны.