
- •Часть 1. Основы создания программ в Си Лекция.1.Язык Си
- •Технология разработки программ
- •Базовые элементы языка Си
- •Представление данных в Си
- •Встроенные типы данных
- •Переменные
- •Операции и выражения
- •Функции
- •Лекция.2.Язык Си Обзор элементов языка Си Типизированные константы
- •Область действия переменных
- •Управляющие конструкции с
- •Массивы и указатели
- •Типы, определяемые пользователем
- •Лекция.3.Препроцессор и отладка программ
- •Макроопределения
- •Условная компиляция
- •1. Предотвращение включения файлов
- •Отладка программ
- •Элементы отладки
- •Лекция.1.Объектно-ориентированное программирование
- •Что такое объектно-ориентированное программирование
- •Пример определений классов
- •Конструктор и деструктор
- •Элементы класса
- •Элементы-функции
- •Элементы данных
- •Класс как область действия
- •Указатель this
- •Лекция.3.Элементы класса Статические элементы класса
- •Статические элементы-функции
- •Специальные элементы-функции класса
- •Конструктор
- •Список инициализации элементов
- •Конструктор копии
- •Операция присваивания
- •Деструктор
- •Операции класса new и delete
- •Функции преобразования
- •Ключевое слово explicit
- •Операции приведения
- •Доступ к базовым классам
- •Простое наследование
- •Конструкторы, деструкторы и наследование
- •Сложное наследование
- •Неоднозначности при сложном наследовании
- •Виртуальные базовые классы
- •Виртуальные функции
- •Чисто виртуальные функции и абстрактные классы
- •Реализация виртуального механизма
- •Лекция.5.Семинар 1-2: Обработка исключений
- •Объекты-исключения
- •Повторное возбуждение исключения
- •Перехват всех исключений
Сложное наследование
Язык C++ допускает не только простое, но и сложное наследование, т.е. наследование от двух и более непосредственных базовых классов. Это позволяет создавать классы, комбинирующие в себе свойства нескольких независимых классов-предков.
Это чаще всего имеет смысл, когда у вас есть некоторый набор понятий, которые могут более или менее независимо комбинироваться с различными элементами другого набора понятий. Простейший пример. Имеется понятие «растение». Бывают «культурные растения», и бывают «дикорастущие растения». С другой стороны, растение может иметь или не иметь «товарной ценности», т.е. быть полезным или бесполезным с коммерческой точки зрения. Если говорить о товарной ценности, то тут у растений бывают «цветы» и «плоды» и т.д. Все это образует довольно развернутую структуру, которая может порождать понятия вроде «дикое растение, цветы которого можно продавать на рынке». (Возможно, кстати, и такое: «дикое растение, цветы которого имеют товарную ценность, но которые нельзя продавать на рынке»!) А можно, с некоторыми модификациями, говорить то же самое не о растениях, а о животных или веществах, минералах. И есть не только «товарные» сущности, но и сорняки, вредители. И так далее.
Очевидно, здесь существует ряд довольно независимых категорий — «растение», «товар», «культурность» и прочее. Подобная структура — отличный кандидат на реализацию в виде иерархии классов со сложным наследованием.
//Производный класс сообщений таймера.
class Alarm: public Time, public Message
{
public:
Alarm(char* str, int h, int m): Time(h, m), Message(str)
{}
void show();
};
Конструктор производного класса Alarm имеет пустое тело и список инициализации, вызывающий конструкторы базовых классов.
Неоднозначности при сложном наследовании
В иерархии классов со сложным наследованием вполне может получиться так, что класс косвенно унаследует несколько экземпляров некоторого базового класса. Если В и С оба являются наследниками A, a D наследует В и С, то D получает двойной набор элементов класса А. Это может приводить к неоднозначностям при обращении к ним, что будет вызывать ошибки времени компиляции. Вот иллюстрация:
class A
{
public:
int aData;
void aFunc ();
// . . .
};
class B: public A {// ...};
class C: public A {// . . .};
class D: public B, public С // Двукратно наследует А.
{// ...}
int main(void)
D d;
d.aData =0; // Ошибка!
d.aFunc(); // Ошибка!
return 0;
}
В этом примере строки в main (), содержащие обращения к унаследованным от А элементам, будут вызывать ошибку компиляции с выдачей сообщения о том, что элемент класса неоднозначен. Однако эту неоднозначность несложно устранить, применив операцию разрешения области действия, например, так:
d.B::aData = 0;
d.C::aFunc() ;
Виртуальные базовые классы
В качестве альтернативы применению операции разрешения области действия при сложном наследовании, подобном описанному в предыдущем параграфе, можно потребовать, чтобы производный класс содержал только одну копию базового. Этого можно достигнуть, описав базовый класс при наследовании от него как виртуальный с помощью ключевого слова virtual. Вот модификация предыдущего примера, которая делает класс А виртуальным базовым классом:
class A
{
public:
int aData;
void aFunc();
// .. .
}
class B: public virtual A //A - виртуальный базовый класс.
// . . .
class С: public virtual A //A - виртуальный базовый класс.
// ...
class D: public B, public С // Содержит только одну копию А.
// . . .
int main(void)
{
D d;
d.aData = 0; // Теперь неоднозначности не возникает,
d.aFunc (); //
return 0;
}