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

Содержание

Слайд 2

Варианты… Наследование Одиночное наследование Множественное наследование При множественном наследовании объекты производных

Варианты…

Наследование

Одиночное наследование

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

При множественном наследовании объекты производных классов могут использовать данные

и методы нескольких классов, а также в свою очередь быть базовыми классами для других.
Слайд 3

Множественное наследование - определение class D: атрибут_наследования base1, …, атрибут_наследования baseN

Множественное наследование - определение

class D: атрибут_наследования base1, …, атрибут_наследования baseN
{
. .

.
};

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

Слайд 4

Аргументы конструкторов Конструктор_производного_класса (список аргументов) : base1 (список аргументов), …, baseN

Аргументы конструкторов

Конструктор_производного_класса (список аргументов) :
base1 (список аргументов), …, baseN (список

аргументов)
{
. . .
}

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

Список конструкторов базовых классов не влияет на порядок их вызова. Он определяется только списком имен базовых классов в начале определения производного класса!

Слайд 5

Виртуальные базовые классы class A { . . . }; class

Виртуальные базовые классы

class A { . . . };
class B: public

A, protected A { . . . }; //Нельзя!

В С++ запрещено непосредственно передавать базовый класс в производный более одного раза, т.е. имя класса в списке базовых классов повторяться не может.

Однако могут возникать ситуации, когда один производный класс косвенно может наследовать один и тот же базовый класс через другие базовые классы. Например:

class A
{
public:
int n;
. . .
};

class B: public A
{
. . .
};

class C: public A , public B
{
. . .
};

Слайд 6

Виртуальные базовые классы - пример Объект класса A int n; Объект

Виртуальные базовые классы - пример

Объект класса A

int n;

Объект класса B

Объект класса

A

int n;

Объект класса C

Объект класса A

int n;

Объект класса B

Объект класса A

int n;

Ситуация неоднозначности:
С obj;
obj.n=0; // Ошибка

По примеру получается такая структура объектов

Слайд 7

Виртуальные базовые классы - пример Такое наследование вполне возможно и компилятор

Виртуальные базовые классы - пример

Такое наследование вполне возможно и компилятор не

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

В данном примере ошибка возникнет на стадии компиляции. Компилятор не различит, какую именно переменную с именем n базового класса необходимо изменить: или переменную , которая непосредственно является компонентой класса A, или переменную, которая доступна из наследуемого класса B?

Решить такую проблему в С++ можно путем использования виртуальных базовых классов.

Слайд 8

Виртуальные базовые классы - пример class A { . . .

Виртуальные базовые классы - пример

class A { . . . };
class

B: virtual public A { . . . };
class C: virtual public A, public B { . . . };

Если один базовый класс косвенно наследуется в одном производном классе и наследуется с атрибутом virtual, то в экземпляр производного класса будет помещена только одна копия базового класса.
Базовый класс объявляется как виртуальный только при наследовании – в списках базовых классов с указанием спецификации virtual.

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

Слайд 9

Множественное наследование на примере (1) #include using namespace std; class A

Множественное наследование на примере (1)

#include
using namespace std;
class A {
public:
int a;
A(int

a) {this->a =a; cout << " Constructor A\n"; }
~A() {cout << " Destructor A\n"; }
void print() { cout << "a = " << a << endl; }
};
class B {
public:
int b;
B(int b) {this->b =b; cout << " Constructor B\n"; }
~B() {cout << " Destructor B\n"; }
void print() { cout << "b = " << b << endl; }
};
Слайд 10

Множественное наследование на примере (2) class C : virtual public A{

Множественное наследование на примере (2)

class C : virtual public A{
public:
int

c;
C(int c): A(c) {this->c =c; cout << " Constructor C\n"; }
~C() {cout << " Destructor C\n"; }
void print() { cout << "c = " << c << endl; }
};
class D : public B, virtual public C, virtual public A{
public:
int d;
D(int d): A(d+1), B(d+2), C(d+3) {this->d =d; cout << " Constructor D\n"; }
~D() {cout << " Destructor D\n"; }
void print()
{
cout << "d = " << d << endl; A::print(); B::print(); C::print();
}
};
Слайд 11

Множественное наследование на примере Результат: void f() { D obj(1); obj.print();

Множественное наследование на примере

Результат:

void f()
{
D obj(1);
obj.print();
}
void main()
{
f();

char s; std::cin>>s;
}

При виртуальном наследовании базовых классов в первую очередь вызываются конструкторы виртуальных базовых классов в порядке наследования.

Слайд 12

Множественное наследование на примере Результат: … class C : virtual public

Множественное наследование на примере

Результат:

… class C : virtual public A{ …

class D : public B, virtual public C, virtual public A
Слайд 13

Множественное наследование на примере Результат: … class C : : virtual

Множественное наследование на примере

Результат:

… class C : : virtual public A{


… class D : public B, : virtual public C,
virtual public A
Слайд 14

Множественное наследование на примере Результат: … class C : : virtual

Множественное наследование на примере

Результат:

… class C : : virtual public A{


… class D : public B, : virtual public C,
virtual public A
Слайд 15

Рассмотрим пример программы, которая основана на использовании виртуальных функций. Организация списка

Рассмотрим пример программы, которая основана на использовании виртуальных функций.

Организация списка разнородных

элементов

Предположим, что необходимо создать список элементов разных типов, например int и char*. Любой элемент вне зависимости от его типа может принадлежать какому-то множеству. Необходимо также создать функции, которые выводили бы значения элементов, принадлежащих заданному множеству, а также удаляли бы из списка элементы заданного множества. Принадлежность элемента тому или иному множеству задается при помощи идентификатора множества.

Слайд 16

Абстрактный базовый класс для организации списка Пример программы (1) #include using

Абстрактный базовый класс для организации списка

Пример программы (1)

#include
using namespace std;
class

base { // Базовый класс
protected:
base *next; // Следующий элемент
base *prev; // Предыдущий элемент
int num; // Идентификатор множества
static base *start; // Указатель начала списка
static int NmOb; // Количество элементов в списке
public:
base(int); // Конструктор
virtual void print() = 0; // Чисто виртуальная функция печати элемента
static void printall(int); // Печать всех элементов заданного множества
static void del(int); // Удаление всех элементов заданного множества
};
base *base::start=NULL; // Начало списка
int base::NmOb=0; // Число элементов списка
Слайд 17

Абстрактный базовый класс для организации списка Пример программы (1)

Абстрактный базовый класс для организации списка

Пример программы (1)

Слайд 18

Конструктор и метод печати заданного множества Пример программы (2) base::base(int n)

Конструктор и метод печати заданного множества

Пример программы (2)

base::base(int n)
{
num =

n; // Идентификатор множества
NmOb++; // Счетчик множеств
next = start; // Первый элемент списка
if (start != NULL) // в списке уже были элементы?
start ->prev = this;
start = this;
start->prev = NULL;
}
void base::printall(int n)
{
base *p = start;
for (int i=0; i < NmOb; p = p -> next, i++)
if (p->num == n) p -> print();
}
Слайд 19

Метод удаления заданного множества Пример программы (3) void base::del(int n) {

Метод удаления заданного множества

Пример программы (3)

void base::del(int n)
{
int i=NmOb;
for

(base *tmp = start; i>0; tmp = tmp -> next, i--)
if (tmp -> num == n) {
if (tmp == start) start =tmp -> next;
else tmp-> prev -> next = tmp -> next;
delete tmp;
}
}
Слайд 20

Базовый класс для работы с множествами Пример программы (4) class Set

Базовый класс для работы с множествами

Пример программы (4)

class Set
{
static

int Last_id;
int id;
public:
Set() { id = ++Last_id; }
~Set() { base::del(id); }
inline int get_id () { return id; }
friend ostream &operator<<(ostream &, Set&);
};
int Set::Last_id = 0;
ostream &operator<<(ostream &os, Set&s)
{
base::printall(s.id);
return os << endl;
}

Перегрузка вывода в поток

Слайд 21

Элемент множества – целое число Пример программы (5) class Integer: public

Элемент множества – целое число

Пример программы (5)

class Integer: public base
{

int i;
public:
Integer(int i, int num): base(num)
{this -> i = i;}
void print() { cout << i << ' '; }
inline int& get() { return i; }
};
class String: public base
{
char * str;
public:
String(char * str, int num): base(num)
{this -> str = str;}
void print() { cout << str << ' '; }
};

Элемент множества – строка символов

Слайд 22

Головная программа - тест Пример программы (6) void main(int argc, char

Головная программа - тест

Пример программы (6)

void main(int argc, char * argv[])
{

Set* s1= new Set;
int id1=s1->get_id(), id2;
Integer* t1= new Integer(1,id1);
Integer* t2 = new Integer(5,id1);
String* t3 = new String("str1", id1);
String* t4 = new String("str2", id1);
{
Set* s2 = new Set;
id2 = s2->get_id();
String* t5 = new String("hello", id2);
Integer* t6 = new Integer(12,id2);
cout << (*s1) << (*s2);
}
base::printall(id1);
char xxx; cin >> xxx;
}
Слайд 23

Виртуальная функция и ее порожденные экземпляры, имеющие одну и ту же

Виртуальная функция и ее порожденные экземпляры, имеющие одну и ту

же сигнатуру, должны иметь один и тот же возвращаемый тип. Перекрытие виртуальной функции называется переопределением.
Внимание! Невиртуальные функции–члены, имеющие ту же самую сигнатуру в порожденных классах, могут иметь различный возвращаемый тип.
Конструкторы и деструкторы не имеют возвращаемых типов. Тип, возвращаемый перегруженным оператором new должен быть void*. Тип, возвращаемый перегруженным оператором delete должен быть void.
Все функции-члены, за исключением конструкторов и перегруженных операторов new и delete, могут быть виртуальными.
Конструкторы, деструкторы, перегруженный operator= и friend функции не наследуются.

Повтор некоторых деталей - 1

Слайд 24

Перегрузка операторов =, (), [] и -> может быть выполнена только

Перегрузка операторов =, (), [] и -> может быть выполнена

только нестатическими функциями-членами. Перегрузка операторов new и delete может быть выполнена только статическими функциями-членами. Прочие перегружаемые операторы могут быть выполнены любыми другими функциями, дружественными или обычными. Операторы ввода/вывода потоков >> и << не могут перегружаться функциями-членами класса – только дружественными.
Union могут иметь конструкторы и деструкторы, но не виртуальные функции. Union не может ни служить базовым классом, ни иметь базового класса.
Модификация доступа при наследовании возможна, но его использование с общим наследованием разрушает связь подтипов. Модификация доступа не может расширять видимость.

Повтор некоторых деталей - 2

Слайд 25

Термин перегрузка относится к использованию одного и того же имени для

Термин перегрузка относится к использованию одного и того же имени

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

Повтор некоторых деталей - 3

Слайд 26

Пусть функция print описана как void print( int i = 0);

Пусть функция print описана как
void print( int i = 0);

// сигнатура int
void print( int i, double x); // сигнатура int, double
void print(double x, int i); // сигнатура double, int
Вызов:
print (‘A’); // преобразует и соответствует int
print (str[]); // нет соответствия – неправильный тип
print (15,9); // неоднозначно
print (); // соответствует int по умолчанию
print (15, 9.2); // соответствует int, double

Повтор некоторых деталей - 4

Слайд 27

Существует два этапа алгоритма выбора соответствия сигнатуры. Первый этап определяет лучшее

Существует два этапа алгоритма выбора соответствия сигнатуры. Первый этап определяет лучшее

соответствие для каждого параметра. На втором этапе проверяется, есть ли функция, которая уникально лучше всего соответствует по каждому параметру.
Лучшее соответствие – это значит точное соответствие. Оно также включает в себя и тривиальные преобразования.
Из В Из В
T T& T* const T*
T& T T* volatile T*
T const T T& const T&
T volatile T T& volatile T&
T[ ] T*
T (параметры) (*T) (параметры)

Повтор некоторых деталей - 5

Эти преобразования не могут использоваться для абсолютно однозначного точного соответствия.

Слайд 28

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

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

типов
Проверить стандартные преобразования типов
Проверить преобразования, определяемые пользователем
Использовать соответствие для аргументов, если оно найдено

Повтор некоторых деталей - 6

Слайд 29

Тесты … Вопрос: Что напечатает программа: #include void ampersand( int n,

Тесты …

Вопрос: Что напечатает программа:

#include
void ampersand( int n, int

&k );
int main()
{
int n=5;
int k=10;
ampersand( n, k );
printf( "n=%d &k=%d\n", n, k );
return 0;
}
void ampersand( int n, int &k )
{
k -= 5;
--n;
}

Варианты ответа:
(1) Произойдет ошибка во время компиляции
(2) n=5 & k=10
(3) n=5 & k=5
(4) n=5 & k=<адрес переменной в памяти>

Слайд 30

Тесты … Вопрос: Что напечатает программа: #include void ampersand( int n,

Тесты …

Вопрос: Что напечатает программа:

#include
void ampersand( int n, int

&k );
int main()
{
int n=5;
int k=10;
ampersand( n, k );
printf( "n=%d &k=%d\n", n, k );
return 0;
}
void ampersand( int n, int &k )
{
k -= 5;
--n;
}

Варианты ответа:
(1) Произойдет ошибка во время компиляции
(2) n=5 & k=10
(3) n=5 & k=5
(4) n=5 & k=<адрес переменной в памяти>

Слайд 31

Тесты … Вопрос: в каких строках есть ошибки: class A {

Тесты …

Вопрос: в каких строках есть ошибки:

class A {
void

Test() const; // 1
};
void A::Test() const; // 2
void A::Test() const {} // 3
void Test() const; // 4
void Test() const {} // 5
Слайд 32

Тесты … Вопрос: в каких строках есть ошибки: class A {

Тесты …

Вопрос: в каких строках есть ошибки:

class A {
void

Test() const; // 1
};
void A::Test() const; // 2
void A::Test() const {} // 3
void Test() const; // 4
void Test() const {} // 5
Слайд 33

Тесты … Вопрос: Что будет выведено на экран? #include class A

Тесты …

Вопрос: Что будет выведено на экран?

#include
class A
{
public:
A(void){std::cout

<< "A";}
~A(void){std::cout << "a";}
};
class B : public A
{
public:
B(void){std::cout << "B";}
~B(void){std::cout << "b";}
};
int main(void)
{
B b;
return 0;
}