|
|||||||
АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомДругоеЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция |
Полиморфизм и виртуальные функции. Раннее и позднее связывание. Виртуальные и невиртуальные функцииВиртуальный означает видимый, но не существующий в реальности. Когда ис- пользуются виртуальные функции, программа, которая, казалось бы, вызывает функцию одного класса, может в этот момент вызывать функцию совсем другого класса. А для чего вообще нужны виртуальные функции? Представьте, что име- ется набор объектов разных классов, но вам хочется, чтобы они все были в одном массиве и вызывались с помощью одного и того же выражения. Например, в гра- фической программе MULTSHAP из главы 9 «Наследование» есть разные геомет- рические фигуры: треугольник, шар, квадрат и т. д. В каждом из этих классов есть функция draw(), которая прорисовывает на экране фигуры. Теперь, допустим, вам захотелось создать картинку, сгруппировав некоторые из этих элементов. Как бы сделать это без лишних сложностей? Подход к решению этой задачи таков: создайте массив указателей на все неповторяющиеся элементыкартинки: shape* ptarr[100]; // массив из 100 указателей на фигуры Если в этом массиве содержатся указатели на все необходимые геометриче- ские фигуры, то вся картинка может быть нарисована в обычном цикле: for(int j=0; j<N; j++) ptarr[j]->draw(); Это просто потрясающая возможность! Абсолютно разные функции выпол- няются с помощью одного и того же вызова! Если указатель в массиве ptarr указывает на шарик, вызовется функция, рисующая шарик, если он указывает на треугольник, то рисуется треугольник. Вот это и есть полиморфизм, то есть различные формы. Функции выглядят одинаково — это выражение draw(), но реально вызываются разные функции, в зависимости от значения ptarr[j]. Поли- морфизм — одна из ключевых особенностей объектно-ориентированного про- граммирования (ООП) наряду с классами и наследованием. Чтобы использовать полиморфизм, необходимо выполнять некоторые усло- вия. Во-первых, все классы (все эти треугольнички, шарики и т. д.) должны яв- ляться наследниками одного и того же базового класса. В MULTSHAP этот класс называется shape. Во-вторых, функция draw() должна быть объявлена виртуаль- ной (virtual) в базовом классе. Все это выглядит абстрактно, поэтому давайте напишем несколько неболь- ших программ, которые выявят некоторые практические вопросы, чтобы потом можно было собрать их воедино.
Позднее связывание Любознательный читатель может удивиться, как же компилятор узнает, какую именно функцию ему компилировать? В программе NOTVIRT у компилятора нет проблем с выражением ptr->show(); Он всегда компилирует вызов функции show() из базового класса. Однако в программе VIRT компилятор не знает, к какому классу относится содержимое ptr. Ведь это может быть адрес объекта как класса Derv1, так и класса Derv2. Какую именно версию draw() вызывает компилятор — тоже загадка. На самом деле компилятор не очень понимает, что ему делать, поэтому откладывает принятие решения до фактического запуска программы. А когда программа уже поставле- на на выполнение, когда известно, на что указывает ptr, тогда будет запущена соответствующая версия draw. Такой подход называется поздним связыванием или динамическим связыванием. (Выбор функций в обычном порядке, во время компиляции, называется ранним связыванием или статическим связыванием.Позднее связывание требует больше ресурсов, но дает выигрыш в возможностях и гибкости. Вскоре мы претворим эти идеи в жизнь, а сейчас вернемся к виртуальным функциям.
Чистая виртуальная функция — это функция, после объявления которой добавлено выражение =0. Продемонстрируем сказанное в примере VIRTPURE: Листинг 11.3. Программа VIRTPURE // virtpure.cpp // Чистая виртуальная функция #include <iostream> using namespace std; class Base //базовый класс { public: virtual void show() = 0; //чистая виртуальная функция }; //////////////////////////////////////////////////
class Derv1: public Base //порожденный класс 1 { public: void show() { cout << "Derv1\n"; } }; ////////////////////////////////////////////////// class Derv2: public Base //порожденный класс 2 { public: void show() { cout << "Derv2\n"; } }; ///////////////////////////////////////////////// int main() { // Base bad; //невозможно создать объект //из абстрактного класса Base* arr[2]; //массив указателей на //базовый класс Derv1 dv1; //Объект производного класса 1 Derv2 dv2; //Объект производного класса 2 arr[0] = &dv1; //Занести адрес dv1 в массив arr[1] = &dv2; //Занести адрес dv2 в массив arr[0]->show(); //Выполнить функцию show() arr[1]->show(); //над обоими объектами return 0; } Здесь виртуальная функция show() объявляется так: virtual void show() = 0; //чистая виртуальная функция Знак равенства не имеет ничего общего с операцией присваивания. Нулевое значение ничему не присваивается. Конструкция =0 — это просто способ сооб- щить компилятору, что функция будет чистой виртуальной. Если теперь в main() попытаться создать объект класса Base, то компилятор будет недоволен тем, что объект абстрактного класса пытаются реализовать. Он выдаст даже имя чистой виртуальной функции, которая делает класс абстрактным. Помните, что хотя это только объявление, но определение функции show() базового класса не явля- ется обязательным. Впрочем, если вам надо его написать, это можно сделать. Как только в базовом классе окажется чистая виртуальная функция, необхо- димо будет позаботиться о том, чтобы избежать ее употребления во всех произ- водных классах, которые вы собираетесь реализовать. Если класс использует чистую виртуальную функцию, он сам становится абстрактным, никакие объектыиз него реализовать не удастся (производные от него классы, впрочем, уже не имеют этого ограничения). Более из эстетических соображений, нежели из каких- либо иных, можно все виртуальные функции базового класса сделать чистыми. Между прочим, мы внесли еще одно, не связанное с предыдущими, измене- ние в VIRTPURE: теперь адреса методов хранятся в массиве указателей и доступ- ны как элементы этого массива. Обработка этого, впрочем, ничем не отличается от использования единственного указателя. VIRTPURE выдает результат, не отли- чающийся от VIRT: Derv1 Derv2 Поиск по сайту: |
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Студалл.Орг (0.005 сек.) |