Similar presentations:
Наследование. Правила наследования
1. Наследование
Наследование является мощнейшим инструментомООП и применяется для следующих
взаимосвязанных целей:
исключения из программы повторяющихся
фрагментов кода;
упрощения модификации программы;
упрощения создания новых программ на основе
существующих.
Кроме того, наследование является единственной
возможностью использовать объекты, исходный
код которых недоступен, но в которые требуется
внести изменения.
1
2. Наследование
23.
#include <iostream> // Пример Counter CountDnusing namespace std;
class Counter
//базовый класс
{
protected:
unsigned int count;
//счетчик
public:
Counter ( ) : count ( 0 ) { } //конструктор без аргументов
Counter ( int c ) : count ( c ) { }
unsigned int get_count ( ) const
{ return count; }
// возвращает значение счетчика
Counter operator++ ( ) //увеличивает значение счетчика
(префикс)
{ return Counter ( ++count ); }
};
class CountDn : public Counter//производный класс
{
public:
Counter operator-- ( ) //уменьшает значение счетчика
{ return Counter ( --count ); } };
3
4.
int main ( ){
CountDn c1;
// объект с1
cout << "\n c1=" << c1.get_count ( ); //вывод на
печать
++c1; ++c1; ++c1;
//увеличиваем c1 три раза
cout << "\n c1=" << c1.get_count ( ); //вывод на
печать
--c1; --c1; //уменьшаем c1 два раза
cout << "\n c1=" << c1.get_count ( ); //вывод на
печать
cout << endl;
return 0;
4
5.
Синтаксис наследованияКлючи доступа
class имя : [private | protected | public] базовый_класс
{ тело класса };
class
class
class
class
{ ...
A { ... };
B { ... };
C { ... };
D: A, protected B, public C
};
5
6. Спецификаторы доступа в ситуации без наследовании
67. Спецификаторы доступа в ситуации с наследованием
78. Правила наследования
Ключ доступаprivate
protected
public
Спецификатор в
базовом классе
Доступ в
производном классе
private
нет
protected
private
public
private
private
нет
protected
protected
public
protected
private
нет
protected
protected
public
public
8
9. Область видимости
В наследнике можно описывать новые поля иметоды и переопределять существующие методы.
Переопределять методы можно несколькими
способами.
Если какой-либо метод в потомке должен работать
совершенно по-другому, чем в предке, метод
описывается в потомке заново. При этом он может
иметь другой набор аргументов.
Если требуется внести добавления в метод предка,
то в соответствующем методе потомка наряду с
описанием дополнительных действий выполняется
вызов метода предка с помощью операции доступа
к области видимости.
Если в программе планируется работать
одновременно с различными типами объектов
иерархии или планируется добавление в иерархию
новых объектов, метод объявляется как
виртуальный с помощью ключевого слова virtual.
Все виртуальные методы иерархии с одним и тем
же именем должны иметь одинаковый список
аргументов.
10
10.
Иными словами:•private элементы базового класса в производном
классе недоступны вне зависимости от ключа.
Обращение к ним может осуществляться только
через методы базового класса.
•Элементы protected при наследовании с ключом
private становятся в производном классе private, в
остальных случаях права доступа к ним не
изменяются.
•Доступ к элементам public при наследовании
становится соответствующим ключу доступа.
11
11. Иными словами:
Если базовый класс наследуется с ключом private,можно выборочно сделать некоторые его элементы
доступными в производном классе:
class Base{
...
public: void f();
};
class Derived : private Base{
...
public: Base::void f();
};
12
12.
Правила наследованияВажнейшим принципом ООП является наследование.
Класс, который наследуется, называется базовым, а наследуемый
– производным.
В примере класс Derived наследует компоненты класса Base,
точнее, компоненты раздела public, которые остаются
открытыми, и компоненты раздела protected (защищенный),
которые остаются закрытыми.
Компоненты раздела private также наследуются, но являются
недоступными напрямую для производного класса, которому
доступны все данные и методы базового класса, наследуемые из
разделов public и protected.
Для переопределенных методов в производном классе действует
принцип полиморфизма, который будет рассмотрен ниже.
Объекту базового класса можно присвоить объект производного,
указателю на базовый класс – значение указателя на
производный класс.
В этом случае через указатель на базовый класс можно получить
доступ только к полям и функциям базового класса. Для доступа
к полям и функциям производного класса следует привести
(преобразовать) ссылку на базовый класс к ссылке
13
производный на класс.
13. Правила наследования
class Base { // определение базового классаint i; //private по умолчанию
protected:
int k;
public:
Base(){i=0; k=1;}
void set_i(int n); // установка i
int get_i(){ // возврат i
return i;}
void show(){
cout<<i<<" "<<k<<endl;}
}; //конец Base
class Derived : public Base { // производный класс
int j;
public:
void set_j(int n);
int mul(); //умножение i на k базового класса и на j
производного
}; //конец Derived
//установка значения i в базовом классе
void Base::set_i(int n){
i = n; }
//установка значения j в производном классе
void Derived::set_j(int n){
j = n;}
//возврат i*k из Base умноженного на j из Derived
int Derived::mul(){
/*производный класс наследует функции-члены
базового класса*/
return j * get_i()*k;//вызов get_i() базового
класса
}
int main(){
Derived ob;
ob.set_i(10); //загрузка i в Base
ob.set_j(4); // загрузка j в Derived
cout << ob.mul()<<endl; //вывод числа 40
ob.show(); //вывод i и k, 10 1
Base bob=ob;//присваивание объекта ссылке на
базовой тип
cout<<bob.get_i(); // вывод i
while (!kbhit());
return 0;
}
Переменная i недоступна в производном классе,
переменная k доступна, поскольку находится в
разделе protected.
В производном классе наследуются также
функции get_i(), set_i() и show() класса Base из
раздела public.
Функция show() позволяет получить доступ из
производного класса к закрытой переменной i
производного класса.
В результате выводится 40 10 1 10.
Принцип полиморфизма, состоящий в перегрузке
методов, объявленных в различных классах с
одним и тем же именем и списком параметров,
будет рассмотрен ниже.
14
14.
Простое наследованиеclass daemon : public monstr{
int brain;
public:
// ------------- Конструкторы:
daemon(int br = 10){brain = br;};
daemon(color sk) : monstr (sk) {brain = 10;}
daemon(char * nam) : monstr (nam) {brain = 10;}
daemon(daemon &M) : monstr (M) {brain = M.brain;}
Если конструктор базового класса требует
указания параметров, он должен быть явным
образом вызван в конструкторе производного
класса в списке инициализации
15
15.
Порядок вызова конструкторовКонструкторы не наследуются, поэтому производный класс должен
иметь собственные конструкторы. Порядок вызова конструкторов:
Если в конструкторе потомка явный вызов конструктора предка
отсутствует, автоматически вызывается конструктор предка по
умолчанию.
Для иерархии, состоящей из нескольких уровней, конструкторы
предков вызываются начиная с самого верхнего уровня. После
этого выполняются конструкторы тех элементов класса, которые
являются объектами, в порядке их объявления в классе, а затем
исполняется конструктор класса.
В случае нескольких предков их конструкторы вызываются в
порядке объявления.
16
16.
Операция присваиванияconst daemon& operator = (daemon &M){
if (&M == this) return *this;
brain = M.brain;
monstr::operator = (M);
return *this;
Поля, унаследованные из класса monstr, недоступны
функциям производного класса, поскольку они
определены в базовом классе как private.
Производный класс может не только дополнять, но и
корректировать поведение базового класса.
Переопределять в производном классе рекомендуется
только виртуальные методы
17
17.
Наследование деструкторовДеструкторы не наследуются. Если деструктор в производном
классе не описан, он формируется автоматически и вызывает
деструкторы всех базовых классов.
В деструкторе производного класса не требуется явно
вызывать деструкторы базовых классов, это будет сделано
автоматически.
Для иерархии, состоящей из нескольких уровней, деструкторы
вызываются в порядке, строго обратном вызову конструкторов:
сначала вызывается деструктор класса, затем — деструкторы
элементов класса, а потом деструктор базового класса.
18
18.
Раннее связываниеОписывается указатель на базовый класс:
monstr *p;
Указатель ссылается на объект производного класса:
p = new daemon;
Вызов методов объекта происходит в соответствии с
типом указателя, а не фактическим типом объекта:
p->draw(1, 1, 1, 1);
// Метод monstr
Можно использовать явное преобразование типа
указателя:
(daemon * p)->draw(1, 1, 1, 1);
19
19.
Описание и использование виртуальных методовЕсли в предке метод определен как виртуальный, метод,
определенный в потомке с тем же именем и набором параметров,
автоматически становится виртуальным, а с отличающимся
набором параметров — обычным.
Виртуальные методы наследуются, то есть переопределять их в
потомке требуется только при необходимости задать отличающиеся
действия. Права доступа при переопределении изменить нельзя.
Если виртуальный метод переопределен в потомке, объекты этого
класса могут получить доступ к методу предка с помощью операции
доступа к области видимости.
Виртуальный метод не может объявляться с модификатором
static, но может быть объявлен как дружественный.
Если в классе вводится объявление виртуального метода, он
должен быть определен хотя бы как чисто виртуальный.
20
20.
Чисто виртуальные методы- содержит признак = 0 вместо тела:
virtual void f(int) = 0;
- должен переопределяться в производном классе.
Класс, содержащий хотя бы один чисто виртуальный метод,
называется абстрактным.
абстрактный класс нельзя использовать при явном
приведении типов, для описания типа параметра и типа
возвращаемого функцией значения;
допускается объявлять указатели и ссылки на абстрактный
класс, если при инициализации не требуется создавать
временный объект;
если класс, производный от абстрактного, не определяет все
чисто виртуальные функции, он также является абстрактным.
21
21.
Виртуальные методыvirtual void draw(int x, int y, int scale, int position);
monstr *r, *p;
r = new monstr; // Создается объект класса monstr
p = new daemon; // Создается объект класса daemon
r->draw(1, 1, 1, 1); // Вызывается метод monstr::draw
p->draw(1, 1, 1, 1); // Вызывается метод daemon::draw
p-> monstr::draw(1, 1, 1, 1); //Обход механизма виртуальных методов
22
22. Виртуальные методы
Виртуальным называется метод, ссылка накоторый разрешается на этапе выполнения
программы
Перевод слова «virtual» в данном значении
«фактический», т.е. ссылка разрешается по факту
вызова
23
23. Виртуальные методы
Множественное наследованиеclass monstr{
public: int get_health(); ...
};
class hero{
public: int get_health();...
};
class ostrich: public monstr, public hero { ... };
int main(){
ostrich A;
cout << A.monstr::get_health();
cout << A.hero::get_health();
}
24
24.
class monstr{...
};
class daemon: virtual public monstr{
...
};
class lady: virtual public monstr{
...
};
class baby: public daemon, public lady{
...
};
monstr
daemon
lady
baby
25
25.
РекомендацииМножественное наследование применяется для того,
чтобы обеспечить производный класс свойствами
двух или более базовых.
Чаще всего один из этих классов является
основным, а другие обеспечивают некоторые
дополнительные свойства, поэтому они называются
классами подмешивания.
По возможности классы подмешивания должны быть
виртуальными и создаваться с помощью
конструкторов без параметров, что позволяет
избежать многих проблем, возникающих, когда у
базовых классов есть общий предок.
26
26. Рекомендации
Диаграммы классов на UMLВарианты изображения класса
27
27. Диаграммы классов на UML Варианты изображения класса
Виды отношений между классамиассоциация
(два класса концептуально
взаимодействуют друг с
другом);
наследование
(отношение обобщения, «is a»);
агрегация (отношение
целое/часть, «has a»);
строгая (композиция)
нестрогая (по ссылке)
зависимость
(отношение использования)
28