721.50K
Категория: ПрограммированиеПрограммирование

Наследование и полиморфизм. Полиморфизм (лекция № 2)

1.

Программирование
(Курс лекций)
2 курс
радиотехнического
факультета
Воронежского института
МВД России
Воронеж – 2018

2.

Раздел № 4 «Объектно-ориентированное
программирование на С++»
Тема 4 «Наследование и полиморфизм»
Лекция № 2 «Полиморфизм»

3.

Учебные вопросы
1. Полиморфизм
2. О перегрузке функций и операций

4.

Литература
Основная:
1. Павловская, Т. А. С/С++. Программирование на
языке высокого уровня [Текст] : учебник: доп. Мин.
образ. РФ / Т. А. Павловская. − СПб : Питер, 2014. - 461 с.
Дополнительная:
1. Мишин, С.А. Объектно-ориентированное
программирование на С++ [Электронный ресурс]
/ С. А. Мишин. – Электр. дан. и прогр. – Воронеж :
Воронежский институт МВД России, 2018.
2. Основы высокоуровневого программирования [Текст]
: учебное пособие / Сумин В. И. [и др.] ; Воронежский
ин-т МВД России ; В. И. Сумин, М. В. Питолин, С. Г.
Мачтаков, С. В. Белокуров. − Воронеж : ВИ МВД России,
2009. − 101 с.

5.

Введение

6.

7.

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

8.

Виртуальные методы
Объекты класса Dog одновременно являются
объектами класса Mammal. До сих нор под этим
подразумевалось, что объекты класса Dog
наследуют все атрибуты (данные) и
возможности (методы) базового класса. Но в
языке С++ принципы иерархического
построения классов несут в себе еще более
глубинный смысл.
Полиморфизм в С++ развит настолько, что
допускается присвоение указателям на
базовый класс адресов объектов производных
классов, как в следующем примере:

9.

10.

11.

Значение переменной S?
95
5
3
15
25

12.

Значение переменной S?
50
15
5
25
20

13.

class pistolet
{
protected: int its_ves;
public:
2
3
pistolet() { cout << 8; }
void vystrel_PM() { cout << 315; }
~pistolet() { cout << 8; } 51
void set_ves(int m) { its_ves = m; }
int get_ves() const {return its_ves; }};
class avtomat
{
protected: double its_kalibr;
4
public:
6
avtomat() { cout << 30; }
void vystrel_AKMY() { cout << 735; }
~avtomat() { cout << 30; } 9
void set_kalibr(double m) { its_kalibr = m; }
double get_kalibr(double m) const
{ return its_kalibr; }
};
class Pistolet_avtomat: public pistolet, public avtomat
{
public:
76
Pistolet_avtomat() { cout << 18; }
~Pistolet_avtomat() { cout << 18; } 94
void vystrel_AKMY() { vystrel_PM(); }};
int main()
{avtomat *pAvtomat;
Pistolet_avtomat *pPA;
pPA = new Pistolet_avtomat;
cout << endl; //1
pAvtomat = new avtomat;
cout << endl;
//2
pAvtomat->vystrel_AKMY();
cout << endl;
//3
pPA->vystrel_AKMY();
cout << endl;
//4
delete pPA;
cout << endl;
//5
delete pAvtomat;
cout << endl;
//6
return 0;
}
2476
Введите значение (без пробелов), которое появится
на экране в 1 строке при выполнении данной
программы. Например, 40320

14.

class pistolet
{
protected: int its_ves;
public:
2
3
pistolet() { cout << 8; }
void vystrel_PM() { cout << 315; }
~pistolet() { cout << 8; } 51
void set_ves(int m) { its_ves = m; }
int get_ves() const {return its_ves; }};
class avtomat
{
protected: double its_kalibr;
4
public:
6
avtomat() { cout << 30; }
void vystrel_AKMY() { cout << 735; }
~avtomat() { cout << 30; } 9
void set_kalibr(double m) { its_kalibr = m; }
double get_kalibr(double m) const
{ return its_kalibr; }
};
class Pistolet_avtomat: public pistolet, public avtomat
{
public:
76
Pistolet_avtomat() { cout << 18; }
~Pistolet_avtomat() { cout << 18; } 94
void vystrel_AKMY() { vystrel_PM(); }};
int main()
{avtomat *pAvtomat;
Pistolet_avtomat *pPA;
pPA = new Pistolet_avtomat;
cout << endl; //1
pAvtomat = new avtomat;
cout << endl;
//2
pAvtomat->vystrel_AKMY();
cout << endl;
//3
pPA->vystrel_AKMY();
cout << endl;
//4
delete pPA;
cout << endl;
//5
delete pAvtomat;
cout << endl;
//6
return 0;
}
94951
Введите значение (без пробелов), которое
появится на экране в 5 строке при выполнении
данной программы. Например, 40320

15.

1. Полиморфизм

16.

Виртуальные деструкторы
Что произойдет при удалении указателя,
ссылающегося на объект производного класса?
Если деструктор будет объявлен как виртуальный,
то все пройдет отлично — будет вызван
деструктор соответствующего производного
класса. Затем деструктор производного класса
автоматически вызовет деструктор базового
класса, и указанный объект будет удален
целиком.
Отсюда следует правило: если в классе
объявлены виртуальные функции, то и
деструктор должен быть виртуальным.

17.

Виртуальный конструктор-копировщик
Конструкторы не могут быть виртуальными,
из чего можно сделать вывод, что не может
быть также виртуального конструкторакопировщика. Но иногда требуется, чтобы
программа могла передать указатель на
объект базового класса и правильно
скопировать его в объект производного
класса. Чтобы добиться этого, необходимо в
базовом классе создать виртуальный метод
Сlonе(). Метод Сlоnе() должен создавать и
возвращать копию объекта текущего класса.

18.

Поскольку в производных классах метод
Сlоnе() замещается, при вызове его
создаются копии объектов,
соответствующие выбранному классу.

19.

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

20.

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

21.

22.

23.

24.

Абстрактные классы
Класс, содержащий хотя бы один чисто
виртуальный метод, называется абстрактным.
Абстрактные классы предназначены для
представления общих понятий,
которые предполагается конкретизировать в
производных классах. Абстрактный
класс может использоваться только в качестве
базового для других классов — объекты
абстрактного класса создавать нельзя, поскольку
прямой или косвенный вызов
чисто виртуального метода приводит к ошибке
при выполнении.

25.

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

26.

“ДРУЖЕСТВЕННЫЕ” (FRIEND) ФУНКЦИИ
Функция, объявленная в производном классе,
может иметь доступ только к защищенным
(protected) или общим (public) компонентам
базового класса.
Функция, объявленная вне класса, может иметь
доступ только к общим (public) компонентам
класса и обращаться к ним по имени, уточненному
именем объекта или указателя на объект.
Чтобы получить доступ к личным компонентам
объектов некоторого класса Х в функции, не
имеющей к ним доступа, эта функция должна быть
объявлена дружественной в классе X:

27.

class X
{ friend void Y:: fprv( int, char*);
/* Другие компоненты класса X
*/
}

28.

Можно объявить все функции класса Y
дружественными в классе X;
class Y;
class X
{ friend Y;
/* Другие компоненты класса X */
}
class Y
{ void fy1(int, int);
int fy2( char*, int);
/* Другие компоненты класса Y */
}

29.

Дружественной может быть и функция, не
являющаяся компонентой какого-либо
класса, например,
class XX
{ friend int printXX ( );
/* Другие компоненты класса ХХ */
}
Здесь функция printXX имеет доступ ко
всем компонентам класса XX, независимо
от закрепленного за ними уровня доступа.

30.

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

31.

Статические компоненты класса
Описатель static в С++ имеет различное
назначение в зависимости от контекста, в
котором он применен.
Переменные и функции, объявленные вне класса
и вне тела функции с описателем static, имеют
область действия, ограниченную файлом, в
котором они объявлены.
Переменные, объявленные как static внутри
функции, видимы только внутри этой функции, но
сохраняют свои значения после выхода из
функции и инициализируются только при первом
обращении к функции.

32.

Компоненты класса также могут объявляться с
описателем static, такие компоненты - данные
являются общими для всех экземпляров
объектов этого класса и размещаются в памяти
отдельно от данных объектов класса. Доступ к
static - компонентам класса возможен по имени,
уточненному именем класса (именем типа) или
именем объекта этого класса, причем к static компонентам класса можно обращаться до
создания экземпляров объектов этого класса.
Статическое данное - член класса должно быть
обязательно инициализировано вне описания
класса

33.

class TBase
//базовый класс для массивов всех типов
{ static int nw;
int size, //размер элемента
count, //текущее число элементов
maxCount, //размер выделенной памяти
delta; //приращение памяти
/* Другие компоненты класса TBase */
}
int TBase::nw =1; /* Инициализация
статической компоненты класса */

34.

Статические компоненты - функции могут вызываться до
создания экземпляров объектов этого класса и поэтому
имеют доступ только к статическим данным класса:
class X
{ static int sx1,sx2;
static void fsx ( int k);
int x1,x2;
/* Другие компоненты класса X */
}
int X::sx1 = 1;
int X::sx2 = 2;
int main ()
{ ..........
X:: fsx( 3 );
..............
}

35.

2. О перегрузке
функций и операций

36.

Язык С++ располагает рядом встроенных типов
данных, включая int, real, char и т.д. Для работы с
данными этих типов используются встроенные
операторы — суммирования (+) и умножения (*).
Кроме того, в С++ существует возможность добавлять
и перегружать эти операторы для собственных
классов.
operator

37.

7: class Counter
8: {
9: public:
10: Counter();
11: ~Counter(){ }
12: int GetItsVal()const { return itsVal; }
13: void SetItsVal(int x) { itsVal = x; }
14: void Increment() { ++itsVal; >
15: void operator++ () < ++itsVal; }
16: private:
18: int itsVal;
19: };
21:
22: Counter::Counter():
23: itsVal(0)
24: { }
25:

38.

26: int main()
27: {
28: Counter i;
29: cout << "The value of i is " << i.GetItsVal() << endl;
30: i.Increment();
31: i cout << "The value of i is " << i.GetItsVal() << endl;
32: ++i;
33: cout << "The value of i is " << i.GetItsVal() << endl;
34: return 0;
35: }
Результат:
The value of i is 0
The value of i is 1
The value of i is 2

39.

Но в работе перегруженного оператора инкремента
существует один серьезный недостаток. В данный
момент в программе не удастся выполнить следующее
выражение:
Counter а = ++i;
В этой строке делается попытка создать новый объект
класса Counter — а, которому присваивается
приращенное значение переменной i. Хотя встроенный
конструктор- копировщик поддерживает операцию
присваивания, текущий оператор инкремента не
возвращает объект класса Counter. Сейчас он
возвращает пустое значение void. Невозможно
присвоить значение void объекту класса Counter.

40.

class Counter
8: {
9: public:
10: Counter();
11: ~Counter(){ }
12: int GetItsVal()const { return itsVal; }
13: void SetItsVal(int x) { itsVal = x; }
14: void Increment() { ++itsVal; }
15: Counter operator++ ();
16:
17: private:
Все, что нам нужно, — это
18: int itsVal;
возвратить объект класса Counter
19:
таким образом, чтобы ero можно
20: };
было присвоить другому объекту
21:
класса Counter. Как это сделать?
22: Counter::Counter():
23: itsVal(0)
24: { }

41.

26: Counter Counter::operator++()
27: {
Результат:
28: ++itsVal;
The value of i is 0
29: Counter temp;
The value of i is 1
30: temp.SetItsVal(itsVal);
The value of i is 2
31: return temp;
32: }
The value of a: 3 and i: 3
33:
34: int main()
35: {
36: Counter i;
37: cout << "The value of i is " << i.GetItsVal() << endl;
38: i.Incrernent();
39: cout << "The value of i is " << i.GetItsVal() << endl;
40: ++i;
41: cout << "The value of i is " << i.GetItsVal() << endl;
42: Counter а = ++i;
43: cout << "The value of a: " << a.GetItsVal();
44: cout << " and i: " << i.GetItsVal() << endl;
45: return 0;
46: }

42.

class Counter
Возвращение безымянных
8: {
9: public:
временных объектов
10: Counter();
11: Counter(int val);
12: ~Counter(){ }
13: int GetItsVal()const { return itsVal; }
14: void SetItsVal(int x) { itsVal = x; }
15: void Increment() { ++itsVal; }
16: Counter operator++ ();
17:
18: private:
19: int itsVal;
20:
21: };
22:
23: Counter::Counter():
24: itsVal(0)
25: { }
26:
27: Counter::Counter(intval):
28: itsVal(val)
29: { }

43.

31: CounterCounter::operator++()
32: {
33: ++itsVal;
34: return Counter (itsVal);
35: }
36:
37: int main()
38: {
39: Counter i;
40: cout << "The value of i is" << i.GetItsVal() << endl;
41: i.Increment();
42: cout << "The value of i is" << i.GetItsVal() << endl;
43: ++i;
44: cout << "The value of i is" << i.GetItsVal() << endl;
45: Counter a = ++i;
46: cout << "The value of a: " << a.GetItsVal();
47: cout << " and i: " << i.GetItsVal() << endl;
48: return 0;
49: }

44.

Оператор суммирования
Операторы приращения, рассмотренные выше,
оперируют только с одним операндом. Оператор
суммирования (+) — это представитель операторов с
двумя операндами.
Он выполняет операции с двумя объектами. Как
выполнить перегрузку оператора суммирования для
класса Counter?
Цель состоит в том, чтобы объявить две переменные
класса Counter, после чего сложить их, как в следующем
примере:
Counter переменная_один, переменная_два,
переменная_три;
переменная_три= переменная_один + перененная_два;

45.

46.

47.

48.

Перегрузка оператора суммирования (+) сделала бы работу класса Counter
более гармоничной
7: class Counter
8: {
9: public:
10: Counter();
11: Counter(int initialValue);
12: ~Counter(){ }
13: int GetItsVal()const { return itsVal; }
14: void SetItsVal(int x) { itsVal = x; }
15: Counter operator+ (const Counter &);
16: private:
17: int itsVal;
18: };
19:
20: Counter::Counter(int initialValue):
21: itsVal(initialValue)
22: { }
23:
24: Counter::Counter():
25: itsVal(0)
26: { }

49.

28: Counter Counter::operator+ (const Counter & rhs)
29: {
30: return Counter(itsVal + rhs.GetItsVal());
31: }
Для компилятора различия не
32:
принципиальные, но программа при этом
становится более понятной и читабельной,
33: int main()
что облегчает работу программиста.
34: {
35: Counter varOne(2), varTwo(4), varThree;
36: varThree = varOne + varTwo;
37: cout << "varOne: " << varOne.GetItsVal()<< endl;
38: cout << "varTwo: " << varTwo.GetItsVal() << endl;
39: cout << "varThree: " << varThree.GetItsVal() << endl;
40:
41: return 0;
42: }

50.

Вопросы для самопроверки и задание на самостоятельную работу
Мишин, С.А. Объектно-ориентированное
программирование на С++ [Электронный ресурс]
/ С. А. Мишин. – Электр. дан. и прогр. – Воронеж : Воронежский институт
МВД России, 2018. C. 26-27.
Вопросы:
1. В чем заключается механизм наследования?
2. Как произвести один класс из другого?
3. Что такое защищенный доступ и как его использовать?
4. Как используются ключи доступа при наследовании?
5. В чем заключается механизм полиморфизма?
6. Какая функция называется виртуальной, чисто вирутальной,
дружественной?
7. Почему в классе Kursant (листинг 4.2) виртуальная функция
Answer() описывается без служебного слова virtual?
8. Поясните появление первых и последних трех строчек на рис. 4.1,
демонстрирующем результат работы программы листинга 4.1.
Задания:
1. В лекционную тетрадь по «Программированию» законспектируйте
ответы на следующие вопросы: множественное наследование, статические
методы и перегрузка операторов.

51.

3. Дан фрагмент программного кода:
сlass massiv
{ int *num; int kol;
publiс:
massiv();
massiv(int n);
void print();
virtual int kolich(){return kol;}
void put(int k,int n){num[k]=n;}
virtual int get_num(int i){ return(num[i]);}
virtual double sr_zn ()
{ double s=3.0;
for (int i=0;i<kolich();i++) s+=num[i];
s/=kolich(); return s; }
~massiv(){ delete num;} };
massiv :: massiv (){ num=new int[3]; kol=3; num[0]=-1; num[1]=-2; num[2]=6;}
massiv::massiv(int n) { num=new int[n]; kol=5; num[0]=1; num[1]=5; num[2]=-12; num[3]=10;
num[4]=2; }
void massiv::print() {
for(int i=0;i<kolich();i++) cout<<num[i]<<" "; cout<<endl; }
class ocherd:public massiv
{ int top;
publiс: ocherd();
ocherd(int);
~ocherd(){};
virtual int kolich() {return top;}
double sr_zn ()
{ double s=2.0;
for (int i=0;i<kolich();i++) s+=get_num(i); return s; }
void pop(int k); };
ocherd::ocherd():massiv(){ top=1; }
ocherd::ocherd(int n):massiv(n) { top=0; }
void ocherd::pop(int k) { put(top++,k); }
void main()
{massiv a(8); ocherd b(10); b.pop(0); b.pop(1); b.pop(2); cout<<"Sr="<<a.sr_zn()<<endl; }
Запишите в тетрадь по «Программированию» значение a.sr_zn(), которое будет выведено на экран.
English     Русский Правила