Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
САОД_ответы_catsto_NEW.doc
Скачиваний:
16
Добавлен:
16.04.2019
Размер:
365.57 Кб
Скачать
  1. Наследование, иерархии классов и обобщенная обработка данных. Чистый полиморфизм (полиморфизм виртуальных методов). Интерфейсы и абстрактные классы.

Множественное наследования интересная возможность C++, но она приводит к некоторым проблемам. Рассмотрим структуру классов на рис. 3.

Рис.3. Структура классов с циклом.

Допустим, класс CA объявлен как

class CA {

public:

Int m_nCount;};

В этом случае, каждый из классов CB и CC имеет поле m_nCount. Сколько же полей m_nCount имеет класс CD? Если вспомнить аналогию, что предок эквивалентен неименованному полю класса, то становится ясно, что есть у CD имеется 2 неименованных поля типа CB и CC, и каждое из них имеет поле m_nCount.

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

CD d;

d.m_nCount = 3;

Такой код приведет к ошибке. Имя m_nCount двусмысленно. Справиться с этой проблемой поможет оператор разрешения контекста. Правильное обращение

d.CB::m_nCount = 3;

или

d.CC::m_nCount = 3;

Аналогично решается проблема и вызова методов соответствующего предка.

Рассмотрим второй случай, когда эти поля обязаны иметь одно и то же значение. Тогда дублирование не желательное явление. Мало того, что до поля стало труднее добираться, они занимают дополнительную память, и нужно “следить” за тем, что бы их значения всегда были одинаковы.

В этом случае дублирования можно избежать следующим объявлением.

class CB: virtual public CA {

public:

Int m_nCount;

};

class CC: virtual public CA {

public:

Int m_nCount;

};

class CВ: public CB, public CC {

public:

Int m_nCount;

};

Обратите внимание на слово virtual. Оно носит совсем иной смысл, чем для виртуальных методов. В данном случае означает, что если в классе уже существует предок CA, то второго экземпляра этого предка создавать не нужно. Т.е. дублирование CA в классе CD вообще не происходит.

Рассмотрим различие понятий потомок и подкласс. Если бы классы предки всегда были public, как в Delphi, то класс потомок всегда был бы подклассом предка (обязательно имеет все те же поля, методы и свойства, плюс некоторые другие). В случае же со скрытым предком это не так. Будучи потомком, класс не является подклассом своего предка.

Итак:

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

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

Чистый полиморфизм (полиморфизм виртуальных методов)

Полиморфизм означает в программировании “один интерфейс – много реализаций”. Например, суммирование для строк и чисел – совершенно разные операции, но нам привычно и удобно обозначать их одной операцией сложения. Это одно из проявлений полиморфизма. Другой вид полиморфизма – шаблоны. О нем достаточно рассказано в лекции, посвященной STL.

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

Первая проблема – где хранить (в каком контейнере) выбранные предметы мебели? Можно под каждый вид мебели создать список и хранить весь набор в отдельных списках, например, стулья, столы, шкафы, диваны, кровати, кресла и т.п. Это вызовет проблему, если появится новый тип мебели, да и сама программа будет сложна.

Другой вариант решения – организовать все классы мебели в иерархию, во главе которой находится класс CFurniture. Тогда, потенциально, мы могли бы поместить выбранный набор в список указателей на объекты CFurniture. Пока что это решение выглядит проще и более универсально.

Давайте искусственно заложим в CFurniture метод string Iam(), который просто вернет название класса для данного объекта (furniture). В более реалистических случаях это мог бы быть метод, который нарисует предмет или вернет список его свойств, но разработка реалистичного метода скрыла бы идею.

Пусть имеется потомки CFurniture – классы CChair и CTable. Переопределим их методы Iam так, что бы для CChair он возвращал слово “chair”, а для CTable Iam - “table”.

Лаборатория готова. Давайте попробуем написать приложение, которое позволит помещать в контейнер CTable и CChair, а потом пробежим по контейнеру и попытаемся вызвать различающиеся методы.

class CFurniture {

public:

string Iam(){return "furniture";}

};

class CChair: public CFurniture {

public:

string Iam(){return "chair";}

};

class CTable: public CFurniture {

public:

string Iam(){return "table";}

};

typedef list<CFurniture *> FRNT_LST;

FRNT_LST fList;

fList.push_back(new CTable);

fList.push_back(new CChair);

fList.push_back(new CChair);

fList.push_back(new CChair);

// Сейчас в списке 1 стол и 3 стула.

for(FRNT_LST::iterator i = fList.begin(); i != fList.end(); i++)

cout << (*i)->Iam() << endl;

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

К счастью, из этого обстоятельства есть выход – полиморфизм виртуальных методов. Что бы воспользоваться его плодами, достаточно перед методом Iam в базовом классе поставить слово virtual.