|
Программирование >> Поддержка объектно-ориентированного программирования
employee e( J.Brown ,1234); manager m( J.Smith ,2,1234); employee::print list(); напечатает J.Smith 1234 level 2 J.Brown 1234 Обратите внимание, что функция печати будет работать даже в том случае, если функция employee list() была написана и оттранслирована еще до того, как был задуман конкретный производный класс manager! Очевидно , что для правильной работы виртуальной функции нужно в каждом объекте класса employee хранить некоторую служебную информацию о типе. Как правило, реализации в качестве такой информации используют просто указатель. Этот указатель хранится только для объектов класса с виртуальными функциями, но не для объектов всех классов, и даже для не для всех объектов производных классов. Дополнительная память отводится только для классов, в которых описаны виртуальные функции. Заметим, что при использовании поля типа, для него все равно нужна дополнительная память. Если в вызове функции явно указана операция разрешения области видимости ::, например, в вызове manager::print(), то механизм вызова виртуальной функции не действует. Иначе подобный вызов привел бы к бесконечной рекурсии. Уточнение имени функции дает еще один положительный эффект: если виртуальная функция является подстановкой (в этом нет ничего необычного), то в вызове с операцией :: происходит подстановка тела функции. Это эффективный способ вызова, который можно применять в важных случаях, когда одна виртуальная функция обращается к другой с одним и тем же объектом. Пример такого случая - вызов функции manager::print(). Поскольку тип объекта явно задается в самом вызове manager::print(), нет нужды определять его в динамике для функции employee::print(), которая и будет вызываться. 6.3 Абстрактные классы Многие классы сходны с классом employee тем, что в них можно дать разумное определение виртуальным функциям. Однако, есть и другие классы. Некоторые, например, класс shape, представляют абстрактное понятие (фигура), для которого нельзя создать объекты. Класс shape приобретает смысл только как базовый класс в некотором производном классе. Причиной является то, что невозможно дать осмысленное определение виртуальных функций класса shape: class shape { ... public: virtual void rotate(int) { error( shape::rotate ); } virtual void draw() { error( shape::draw ): } нельзя ни вращать, ни рисовать абстрактную фигуру ... Создание объекта типа shape (абстрактной фигуры) законная, хотя совершенно бессмысленная операция: shape s; бессм1слица: фигура вообще Она бессмысленна потому, что любая операция с объектом s приведет к ошибке. Лучше виртуальные функции класса shape описать как чисто виртуальные. Сделать виртуальную функцию чисто виртуальной можно, добавив инициализатор = 0: class shape { ... public: virtual void rotate(int) = 0; чисто виртуальная функция virtual void draw() = 0; чисто виртуальная функция Бьерн Страуструп. Язык программирования С++ Класс, в котором есть виртуальные функции, называется абстрактным. Объекты такого класса создать нельзя: shape s; ошибка: переменная абстрактного класса shape Абстрактный класс можно использовать только в качестве базового для другого класса: class circle : public shape { int radius; public: void rotate(int) { } нормально: переопределение shape::rotate void draw(); нормально: circle(point p, int r); переопределение shape::draw Если чисто виртуальная функция не определяется в производном классе, то она и остается таковой, а значит производный класс тоже является абстрактным. При таком подходе можно реализовывать классы поэтапно: class X { public: virtual void f() = 0; virtual void g() = 0; X b; ошибка: описание объекта абстрактного класса X class Y : public X { void f(); переопределение X::f Y b; ошибка: описание объекта абстрактного класса Y class Z : public Y { void g(); переопределение X::g Z c; нормально Абстрактные классы нужны для задания интерфейса без уточнения каких-либо конкретных деталей реализации. Например, в операционной системе детали реализации драйвера устройства можно скрыть таким абстрактным классом: class character device { public: virtual int open() = 0; virtual int close(const char*) = 0; virtual int read(const char*, int) =0; virtual int write(const char*, int) = 0; virtual int ioctl(int ...) = 0; ... Настоящие драйверы будут определяться как производные от класса character device. После введения абстрактного класса у нас есть все основные средства для того, чтобы написать законченную программу. 6.4 Пример законченной программы Рассмотрим программу рисования геометрических фигур на экране. Она естественным образом распадается на три части: [1] монитор экрана: набор функций и структур данных низкого уровня для работы с экраном; оперирует только такими понятиями, как точки, линии; [2] библиотека фигур: множество определений фигур общего вида (например, прямоугольник, окружность) и стандартные функции для работы с ними; [3] прикладная программа: конкретные определения фигур, относящихся к задаче, и работающие с ними функции. Как правило, эти три части программируются разными людьми в разных организациях и в разное время, причем они обычно создаются в перечисленном порядке. При этом естественно возникают затруднения, поскольку, например, у разработчика монитора нет точного представления о том, для каких задач в конечном счете он будет использоваться. Наш пример будет отражать этот факт. Чтобы пример имел допустимый размер, библиотека фигур весьма ограничена, а прикладная программа тривиальна. Используется совершенно примитивное представление экрана, чтобы даже читатель, на машине которого нет графических средств, сумел поработать с этой программой. Можно легко заменить монитор экрана на более развитую программу, не изменяя при этом библиотеку фигур или прикладную программу. 6.4.1 Монитор экрана Вначале было желание написать монитор экрана на С, чтобы еще больше подчеркнуть разделение между уровнями реализации. Но это оказалось утомительным, и поэтому выбрано компромиссное решение: стиль программирования, принятый в С (нет функций-членов, виртуальных функций, пользовательских операций и т.д.), но используются конструкторы, параметры функций полностью описываются и проверяются и т.д. Этот монитор очень напоминает программу на С, которую модифицировали, чтобы воспользоваться возможностями С++, но полностью переделывать не стали. Экран представлен как двумерный массив символов и управляется функциями put point() и put line(). В них для связи с экраном используется структура point: файл screen.h const int XMAX=4 0; const int YMAX=24; struct point { int x, y; point() { } point(int a,int b) { x=; y=b; } extern void put point(int a, int b); inline void put point(point p) { put point(p.x,p.y); } extern void put line(int, int, int, int); extern void put line(point a, point b) { put line(a.x,a.y,b.x,b.y); } extern void screen init(); extern void screen destroy(); extern void screen refresh(); extern void screen clear(); #include <iostream.h> До вызова функций, выдающих изображение на экран (put ...), необходимо обратиться к функции инициализации экрана screen init(). Изменения в структуре данных, описывающей экран, станут видимы на нем только после вызова функции обновления экрана screen refresh(). Читатель может убедиться, что обновление экрана происходит просто с помощью копирования новых значений в массив, представляющий экран. Приведем функции и определения данных для управления экраном:
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |