Резюме
Сегодня вы познакомились с методами преодоления некоторых ограничений одиночного наследования. Вы узнали об опасности передачи вверх по иерархии классов интерфейса производных функций и об ограничениях приведения типа данных объектов базового класса к производным классам во время выполнения программы. Кроме того, вы узнали, когда и как используется множественное наследование классов, какие проблемы при этом могут возникнуть и как их преодолеть.
На этом занятии также было представлено объявление абстрактных типов данных и способы создания абстрактного класса с помощью чистых виртуальных функций. Особое внимание уделялось логике использования абстрактных данных для моделирования реальных ситуаций.
Вопросы и ответы
Что означает передача функциональности вверх по иерархии классов?
Речь идет о переносе описаний общих функций-членов в базовые классы более высокого уровня. Если одна и та же функция используется в производных классах, имеет смысл описать эту функцию в общем для них базовом классе.
Во всех ли случаях передача функциональности вверх целесообразна в программе?
Если передаются вверх по иерархии только функции общего использования, то это целесообразно, но смысл теряется, если в базовые классы передается специфичный интерфейс производных классов. Другими словами, если метод не может быть использован во всех производных классах, то нет смысла описывать его в базовом классе. В противном случае вам во время выполнения программы придется отслеживать тип текущего объекта, прежде чем вызвать функцию.
В чем проблема с контролем типа объекта при выполнении программы?
В больших программах для выполнения контроля за типом объекта придется использовать достаточно массивный и сложный программный блок. Идея использования виртуальных функций состоит в том, что тип объекта определяется программой автоматически с помощью виртуальной таблицы, вместо того чтобы использовать для этого специальные программные блоки.
Что плохого в приведении типа объектов?
Приведение типов объектов к определенному типу данных, используемому конкретной функцией, довольно часто и эффективно используется в программах на C++. Но если программист применяет приведение типов для того, чтобы обойти заложенный в C++ строгий контроль за соответствием типов данных, например в случае приведения типа указателя к установленному во время выполнения программы типу объекта, то это говорит о серьезных недостатках в структуре программы, противоречащих идеологии C++.
Почему бы не сделать все функции виртуальными?
Для поддержания работы виртуальных функций создается виртуальная таблица, что увеличивает потребление памяти программой и время выполнения программы. Если в программе используется небольшой класс, от которого не производятся подклассы, то в использовании виртуальных функций нет никакого смысла.
В каких случаях используются виртуальные деструкторы?
Виртуальные деструкторы следует описывать в том случае, если в программе планируется использование указателя базового класса для получения доступа к объектам подклассов. Существует одно простое правило: если в программе описываются виртуальные функции, то обязательно должны использоваться виртуальные деструкторы.
Для чего возиться с созданием абстрактных типов данных? Не проще ли создать обычный базовый класс, для которого просто не создавать объектов в программе?
При написании программы всегда следует использовать такие подходы, которые гарантировали бы обнаружение ошибок в программе не во время ее выполнения, а во время компиляции. Если класс явно будет описан как абстрактный, то любая попытка создать объект этого класса приведет к показу компилятором сообщения об ошибке.
Коллоквиум
В этом разделе предлагаются вопросы для самоконтроля и укрепления полученных знаний и приводится несколько упражнений, которые помогут закрепить ваши практические навыки. Попытайтесь самостоятельно ответить на вопросы теста и выполнить задания, а потом сверьте полученные результаты с ответами в приложении Г. Не приступайте к изучению материала следующей главы, если для вас остались неясными хотя бы некоторые из предложенных ниже вопросов.
Контрольные вопросы
1. Что такое приведение типа объекта вниз?
2. Что такое v-ptr?
3. Предположим, для создания прямоугольника с закругленными углами используется класс RoundRect, произведенный от двух базовых классов — Rectangle и Circle, которые, в свою очередь, производятся от общего класса Shape. Как много объектов класса Shape создается при создании одного объекта класса RoundRect?
4. Если классы Horse и Bird виртуально наследуются от класса Animal как открытые, будут ли конструкторы этих классов инициализировать конструктор класса Animal? Если класс Pegasus наследуется сразу от двух классов, Horse и Bird, как в нем будет инициализироваться конструктор класса Animal?
5. Объявите класс Vehicle (Машина) как абстрактный тип данных.
6. Если в программе объявлен класс ADT с тремя чистыми виртуальными функциями, сколько из них нужно заместить в производных классах, чтобы получить возможность создания объектов этих классов?
Упражнения
1. Объявите класс JetPlane (Реактивный самолет), наследуя его отдвух базовых классов — Rocket (Ракета) и Airplane (Самолет).
2. Произведите от класса JetPlane, объявленного в первом упражнении, новый класс 747.
3. Напишите программу, производящую классы Car (Легковой автомобиль) и Bus (Автобус) от класса Vehicle (Машина). Объявите класс Vehicle как абстрактный тип данных с двумя чистыми виртуальными функциями. Классы Car и Bus не должны быть абстрактными.
4. Измените программу из предыдущего упражнения таким образом, чтобы класс Car тоже стал ADT, и произведите от него три новых класса: SportsCar (Спортивный автомобиль), Wagon (Фургон) и Coupe (Двухместный автомобиль-купе). В классе Car должна замещаться одна из виртуальных функций, объявленных в классе Vehicle, с вызовом функции базового класса.
День 14-й. Специальные классы и функции
Язык программирования C++ предлагает несколько способов ограничения области видимости и использования переменных и указателей. В предыдущих главах вы научились создавать глобальные переменные, используемые во всей программе, и локальные переменные, используемые в отдельных функциях. Вы узнали, что собой представляют указатели на переменные и переменные-члены класса. Сегодня вы узнаете:
• Что такое статические переменные-члены и функции-члены
• Как используются статические переменные-члены и функции-члены
• Как создавать и применять указатели на функции и на функции-члены
• Как работать с массивами указателей на функции
Статические переменные-члены
До настоящего момента вы считали, что всякие данные объекта уникальны для того объекта, в котором используются, и не могут совместно применяться несколькими объектами класса. Другими словами, если было создано пять объектов Cat, то каждый из них характеризуется своим временем жизни, размерами и т.п. При этом время жизни одного не влияет на время жизни остальных.
Однако иногда возникает необходимость контроля за накоплением данных программой. Может потребоваться информация о том, сколько всего было создано объектов определенного класса и сколько их существует в данный момент. Статические переменные-члены совместно используются всеми объектами класса. Они являются чем вроде "золотой серединки" между глобальными данными, доступными всем частям программы, и данными членов, доступными, как правило, только одному объекту.
Можно полагать, что статические члены принадлежат классу, а не объекту. Если данные обычных членов доступны одному объекту, то статические члены могут использоваться всем классом. В листинге 14.1 объявляется объект Cat со статическим членом HowManyCats. Эта переменная учитывает количество созданных объектов Cat, что реализуется приращением статической переменной HowManyCats при вызове конструктора или отрицательным приращением при вызове деструктора.
Листинг 14.1. Статические переменные-члены
1: //Листинг 14.1. Статические переменные-члены
2:
3: #include <iostream.h>
4:
5: class Cat
6: {
7: public:
8: Cat(int age):itsAge(age){ HowManyCats++; }
9: virtual ~Cat() { HowManyCats--; }
10: virtual int 6etAge() { return itsAge; }
11: virtual void SetAge(int age) { itsAge = age; }
12: static int HowManyCats;
13:
14: private:
15: int itsAge;
16:
17: };
18:
19: int Cat::HowManyCats = 0;
20:
21: int main()
22: {
23: const int MaxCats = 5;
24: int i; Cat *CatHouse[MaxCats];