Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЛекцООППССРВ_89.doc
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
673.79 Кб
Скачать

20. Простое наследование

При простом наследовании производный класс наследует один предшествующий ему по иерархии базовый или промежуточный базовый класс. Схематично простое наследование будет иметь вид:

Синтаксис описания производного класса при простом наследовании:

class имя_производного_класса: ключ_доступа имя_базового_класса { };

Пример:

class A { private: int a;

public: A (int b) {a = b;} // Конструктор

};

class B: public A

{ private: int c, d;

public: B (int i, int j):A (j) {с = i; d = j; } // Конструктору предшествующего базового

// класса передается параметр j

// (используется список инициализации )

}; B ob(10,15);

В результате инициализации глобального объекта ob его элементы-данные будут иметь значения:

а = 15; c = 10; d = 15.

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

21. Множественное наследование

При множественном наследовании производный класс наследует несколько базовых классов. Схематично:

Пример:

class A { int a; public: A(int i){ a=i; } };

class B {int b; public: B(int j){ b=j; }};

class C: public A, public B {int c; public: C(int i, int j, int k): A(i),B(j) {c=k;} };

В данном примере конструктор производного класса передает параметры конструкторам всех наследуемых классов.

Передача параметров:

C  A  B;

Вызов конструкторов:

A( )  B( )  C( )

Вызов деструкторов:

~C( )  ~B( )  ~A( )

22. Виртуальное наследование

Рассмотрим следующую иерархию классов:

При создании объекта производного класса D3 в результате последовательной работы конструкторов будут созданы две копии объектов базового класса В в классах D1 и D2. Возникает неоднозначность, какую копию взять для создания объекта класса D3. Для устранения неоднозначности базовый класс В наследуется в D1 и D2 как виртуальный. Для этого перед ключом доступа ставится ключевое слово virtual.

Пример:

class B { };

class D1: virtual public B { };

class D2: virtual public B { };

class D3: public D1, public D2 { };

В этом случае создаётся одна копия объекта базового класса.

23. Виртуальные функции

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

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

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

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

class B { . . . virtual void func(){} . . . };

class C: public B { . . . void func(){} . . . };

class D: public B { . . . void func(){} . . . };

void main(){

B obB; C obC; D obD;

obB.func(); // Вызов функции базового класса B

obC.func(); // Вызов переопределенной функции производного класса С

obD.func(); // Вызов переопределенной функции производного класса D

}

Алгоритмы функции func( ) в классах B, C и D могут быть различными.

Следующие примеры демонстрируют отличие в использовании перегруженной и полиморфной функций.

Пример 1.

class A

{ public: double f1 (double x) { return x*x; }

double f2 (double x) { return f1(x)/2; }

};

class B: public A

{ public: double f1 (double x) { return x*x*x; } // Перегрузка функции f1

};

void main( )

{ B b; cout << b.f2(3) << endl;

}

Результат работы программы: 4.5.

В данном примере класс parent содержит компонентные функции f1 и f2, причем f2 вызывает f1. класс B, производный от класса A, наследует функцию f2, однако переопределяет (перегружает) функцию f1. Вместо ожидаемого результата 13.5 программа выдает результат 4.5. Дело в том, что компилятор оттранслирует выражение b.f2(3) в обращение к унаследованной функции A::f2, которая, в свою очередь, вызовет функцию A::f1, а не B::f1.

Пример 2.

class A

{ public: virtual double f1 (double x) { return x*x; }

double f2 (double x) { return f1(x)/2; }

};

class B: public A

{ public: virtual double f1 (double x) { return x*x*x; }

};

void main( )

{B b; cout << b.f2(3) << endl;

}

Результат работы программы: 13.5.

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