|
Программирование >> Инициализация объектов класса, структура
#include <IntA\rray.h> void swap (IntArray &ia, int i, int j) { int temp ia[i]; ia[i] = ia[j] ; ia[j] = temp; ниже идут обращения к функции swap: IntArray ia; IntAArrayRC iarc; IntSortedArray ias; правильно - ia имеет тип IntArray swap (ia,0,10); правильно - iarc является подклассом IntArray swap (iarc,0,10); правильно - ias является подклассом IntArray swap (ias,0,10); ошибка - string не является подклассом IntAArray string str( Это не IntArray! ); swap (str,0,10); Кажд1й из трех классов реализует операцию взятия индекса но-своему. Поэтому важно, чтобы внутри функции swap() вызывалась нужная операция взятия индекса. Так, если swap() вызвана для IntArrayRC: swap (iarc,0,10); то должна вызываться функция взятия индекса для объекта класса IntArrayRC, а для swap (ias,0,10); функция взятия индекса IntSortedArray. Именно это и обеспечивает механизм виртуальных функций С++. Давайте попробуем сделать наш класс IntArray базовым для иерархии подклассов. Что нужно изменить в его описании? Синтаксически - совсем немного. Возможно, придется открыть для производных классов доступ к скрыт1м членам класса. Кроме того, те функции, которые мы собираемся сделать виртуальными, необходимо явно пометить специальным ключевым словом virtual. Основная же трудность состоит в таком изменении реализации базового класса, которая позволит ей лучше отвечать своей новой цели - служить базой для целого семейства подклассов. При простом объектном подходе можно выделить двух разработчиков конечной программы - разработчик класса и пользователь класса (тот, кто использует данный класс в конечной программе), причем последний обращается только к открытому интерфейсу. Для такого случая достаточно двух уровней доступа к членам класса -открытого (public) и закрытого (private). Если используется наследование, то к этим двум группам разработчиков добавляется третья, промежуточная. Производный класс может проектировать совсем не тот человек, который проектировал базовый, и для того чтобы реализовать класс-наследник, совсем не class IntAArray { public: конструкторы explicit IntArray (int sz = DefaultAArraySize); IntAArray (int *array, int array size) ; IntAArray (const IntAArray &rhs); виртуальн деструктор virtual ~IntArray() { delete[] ia; } операции сравнения: bool operator== (const IntAArray&) const; bool operator!= (const IntAArray&) const; операция присваивания: IntAArray& operator= (const IntAArray&) ; int size() const { return size; }; мы убрали проверку индекса... Вот как выглядит модифицированное описание класса IntArray: virtual int& operator[](int index) { return ia[index]; } virtual void sort(); virtual int min() const; virtual int max() const; virtual int find (int value) const; protected: static const int DefaultArraySize = 12; void init (int sz; int *array); int size; int *ia; Открытые функции-члены по-прежнему определяют интерфейс класса, как и в реализации из предыдущего раздела. Но теперь это интерфейс не только базового, но и всех производных от него подклассов. Нужно решить, какие из членов, ранее объявленных как закрытые, сделать защищенными. Для нашего класса IntArray сделаем защищенными все оставшиеся члены. Теперь нам необходимо определить, реализация каких функций-членов базового класса может меняться в подклассах. Такие функции мы объявим виртуальными. Как уже отмечалось выше, реализация операции взятия индекса будет отличаться по крайней мере для подкласса IntArrayRC. Реализация операторов сравнения и функции size() одинакова для всех подклассов, следовательно, они не будут виртуальными. обязательно иметь доступ к реализации базового. И хотя такой доступ может потребоваться при проектировании подкласса, от конечного пользователя обоих классов эта часть по-прежнему должна быть закрыта. К двум уровням доступа добавляется третий, в некотором см1сле промежуточн1й, - защищенный (protected). Члены класса, объявленные как защищенные, могут использоваться классами-потомками, но никем больше. (Закрытые члены класса недоступны даже для его потомков.) void init (IntArray &ia) которого она вызвана. Рассмотрим пример: for (int ix=0; ix<ia.size(); ++ix) ia[ix] = ix; Формальный параметр функции ia может быть ссылкой на IntArray, IntArrayRC или на IntSortedArray. Функция-член size() не является виртуальной и разрешается на этапе компиляции. А вот виртуальный оператор взятия индекса не может быть разрешен на данном этапе, поскольку реальный тип объекта, на который ссылается ia, в этот момент неизвестен. (В главе 17 мы будем говорить о виртуальных функциях более подробно. Там мы рассмотрим также и накладные расходы, которые влечет за собой их использование.) #ifndef IntArrayRC H #define IntArrayRC H #include IntArray.h class IntArrayRC : public IntArray { public: IntArrayRC( int sz = DefaultArraySize ); IntA\rrayRC( const int *array, int array size ); IntArrayRC( const IntArrayRC &rhs ); virtual int& operator[]( int ) const; private: void check range( int ix ); Вот как выглядит определение производного класса IntArrayRC: #endif Этот текст мы поместим в заголовочный файл IntArrayRC.h. Обратите внимание на то, что в наш файл включен заголовочный файл IntArray.h. В классе IntArrayRC мы должны реализовать только те особенности, которые отличают его от IntArray: класс IntArrayRC должен иметь свою собственную реализацию операции взятия индекса; функцию для проверки индекса и собственный набор конструкторов. Все данные и функции-члены класса IntArray можно использовать в классе IntArrayRC так, как будто это его собственные члены. В этом и заключается смысл наследования. Синтаксически наследование выражается строкой class IntArrayRC : public IntArray При вызове невиртуальной функции компилятор определяет все необходимое еще на этапе компиляции. Если же он встречает вызов виртуальной функции, то не пытается сделать этого. Выбор нужной из набора виртуальных функций (разрешение вызова) происходит во время выполнения программы и основывается на типе объекта, из
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |