Министерство образования Республики Беларусь Министерство образования и науки Российской Федерации ГУВПО “Белорусско-Российский университет” Кафедра “Автоматизированные cистемы управления” Дисциплина “Объектно-ориентированное программирование и проектирование” Лабораторная работа № 3 Виртуальные функции и абстрактные классы Время выполнения работы – 4 часа 2014 2 1 Цель работы Получение навыков в разработке программ с использованием виртуальных функций и абстрактных классов. 2 Техническое обеспечение 1.1 1.2 1.3 1.4 Персональная ЭВМ IBM PC/486 и более поздних моделей. Клавиатура. Дисплей. Печатающее устройство. 3 Программное обеспечение 3.1 Операционная система Windows 98 и более поздние версии. 3.2 Система программирования Visual C++ версия 6.0 и более поздние версии. 4 Постановка задачи Выполнить задание согласно заданному варианту. приведены в конце методических указаний в разделе 7. Варианты задач 5 Содержание отчета 5.1 Тема и цель работы. 5.2 Текст программы. 5.3 Результаты выполнения программы. 6 Общие сведения 6.1 Виртуальные функции Связывание это сопоставление вызова функции с телом. Для обычных методов связывание выполняется на этапе трансляции до запуска программы. Такое связывание называют «ранним», или статическим. При наследовании обычного метода его поведение не меняется в наследнике. Однако бывает необходимо, чтобы поведение некоторых методов базового класса и классов-наследников отличались. Чтобы добиться разного поведения в зависимости от типа, необходимо объявить функцию-метод виртуальной; в С++ это делается с помощью ключевого слова virtual. Виртуальные функции в совокупности с принципом подстановки обеспечивают механизм «позднего» (отложенного) или динамического связывания, которое работает во время выполнения программы (листинг 1). Листинг 1. Виртуальные методы сlass Base { public: virtual void print() const { сout << "Base!" << endl; } }; // базовый класс class Derive : public Base { public: virtual void print() const // производный класс // базовый метод // переопределенный метод 3 { cout << "Derive!" << endl; } }; void F(Base &d) { d.print(); } // предполагается вызов метода базового класса Base W; // обьект базового класса F(W); // работает метод базового класса Derive U; // объект производного класса F(U); // работает метод производного класса Base *c1 = &W; // адрес объекта базового класса c1 -> print(); // вызов базового метода c1 = &U; // адрес объекта производного типа вместо базового с1 -> print(); // вызывается производный метод Класс, в котором определены виртуальные функции (хотя бы одна), называется полиморфным классом. Ключевое слово virtual можно писать только в базовом классе это достаточно сделать в объявлении функции. Даже если определение писать без слова virtual, функция все равно будет считаться виртуальной. Правила описания и использования виртуальных функций-методов следующие. 1) Виртуальная функция может быть только методом класса. 2) Любую перегружаемую операцию-метод класса можно сделать виртуальной, например операцию присваивания или операцию преобразования типа. 3) Виртуальная функция наследуется. 4) Виртуальная функция может быть константной. 5) Если в базовом классе определена виртуальная функция, то метод произвол-нетто класса с таким же именем и прототипом (включая и тип возвращаемого значения, и константность метода) автоматически является виртуальным (слово virtual указывать необязательно) и замещает функцию-метод базового класса. 6) Статические методы не могут быть виртуальными. 7) Конструкторы не могут быть виртуальными. 8) Деструкторы могут (чаще должны) быть виртуальными это гарантирует корректное освобождение памяти через указатель базового класса. Внутри конструкторов и деструкторов динамическое связывание не работает, хотя вызов виртуальных функций не запрещен. В конструкторах и деструкторах всегда вызывается «родная» функция класса. Виртуальные функции-методы можно перегружать и переопределять (в наследниках) с другим списком аргументов. Если виртуальная функция переопределена с другим списком аргументов, она замещает (скрывает) родительские методы. Константный метод считается отличным от неконстантного метода с таким же прототипом (листинг 2). Листинг 2. Перегрузка и переопределение виртуальных методов class Base { public: // перегрузка виртуальных методов virtual int f( ) const { cout << "Base::f( )"<< endl; return 0; } 4 virtual void f(const string &s) const { cout << "Base::f(string)"<< endl; } }: class Derive: public Base { public: virtual int f(int) const // переопределение виртуальной функции { cout << "Derive::f(int)"<< endl; return 0; } }: Base b, *pb; Derive d, *pd = &d; pb = &d; pb->f(); pb->f("name"); pb->f(1); pd->f(1); // объекты базового типа // объекты производного типа // здесь нужна виртуальность // вызывается базовый метод // вызывается базовый метод // ошибка! // нормально - вызов метода наследника Через указатель базового класса нельзя вызвать новые виртуальные методы, определенные только в производном классе. Если это необходимо, требуется явное приведение типа: ((Derive *)pb) -> f(1); Родительские методы можно сделать доступными в классе-наследнике при помощи using-объявления. class Derive : public Base { public: virtual int f(int) const { cout << "Derived::f (int)"<< endl; return 0; } using Base::f; // разрешение использовать скрытые базовые методы Разрешается при переопределении виртуальной функции изменить только тин возвращаемого значения, если это указатель пли ссылка. Виртуальную функции) можно вызвать невиртуально, если указать квалификатор класса: Base *c1 = new Derive(); c1 -> print(); c1 -> Base::print(); // адресуется объект производного класса // вызов метода-наследника // явно вызывается базовый метод Такой статический вызов бывает нужен, когда в базовом классе реализуются общие действия, которые должны выполняться во всех классах-наследниках. При наличии хотя бы одной виртуальной функции размер класса без полей равен 4 это размер указателя. Количество виртуальных функций роли не играет размер класса остается равен 4. class One { virtual void f(void) { } }; class Two { virtual void f(void) { } virtual void g(void) { } }; // . . . 5 cout << sizeof(One) << endl; cout << sizeof(Two) << endl; // размер = 4 // размер = 4 6.2 Чистые виртуальные функции и абстрактные классы Виртуальная функция, не имеющая определении тела, называется чистой (pure) и обьявляется следующим образом: virtual тип имя( параметры ) = 0; Предполагается, что данная функция будет реализована в классахнаследниках. Класс, в котором есть хоть одна чистая виртуальная функция, называется абстрактным классом. Объекты абстрактного класса создавать запрещено даже операцией new. И при передаче параметра в функцию невозможно передать объект абстрактного класса по значению. Однако указатели (и ссылки) определять можно (вспомните объявление класса). При наследовании абстрактность сохраняется: если класс-наследник не реализует унаследованную чистую виртуальную функцию, то он тоже является абстрактным. В С++ абстрактный класс определяет понятие интерфейса. Наследование от абстрактного класса – это наследование интерфейса. 6.3 Виртуальные деструкторы Деструктор класса может быть объявлен виртуальным (листинг 3). Когда деструктор базового класса виртуальный, то и деструкторы всех наследников такие же. Это необходимо, если доступ к динамическому объекту производного класса выполняется через указатель базового класса. В этом при уничтожении объекта через указатель базового класса вызывается деструктор производного класса, а он вызывает деструктор базового класса. Если деструктор базового класса не виртуальный, то при уничтожении объекта производного класса через указатель базового класса вызывается деструктор только базового класса. Листинг 3. Виртуальные деструкторы class Base { public: ~Base( ) // деструктор не виртуальный { cout << "Base: :Not virtual destructor!"<< endl; } }; class Derived : public Base // наследник { public: ~Derived( ) // деструктор не виртуальный { cout << "Derived: :Not virtual destructor!"<< endl; } }; class VBase { public: virtual ~VBase( ) // деструктор виртуальный { cout << "Base::virtual destructor!"<< endl; } }; class VDerived : public VBase 6 { public: // деструктор виртуальный ~VDenved() // virtual можно не писать { cout << "Derived::virtual destructor!"<<endl; } }; // . . . Base *bp = new Derived( ); delete bp; VBase *vbp = new VDerived( ); delete vbp; // подстановка - повышающее приведение типа // вызывается только базовый деструктор // подстановка // вызывается производный, а потом базовый // деструктор Деструктор может быть объявлен как чистый: virtual ~VBase( ) = 0; Класс, в котором определен чистый виртуальный деструктор, является абстрактным, и создавать объекты этого класса запрещено. Однако класс-наследник не является абстрактным классом, поскольку деструктор не наследуется, и в наследнике при отсутствии явно определенного деструктора он создается автоматически. При объявлении чисто виртуального деструктора нужно написать и ею определение. class Abstract { public: virtual ~Abstract( ) = 0; }; // абстрактный класс // чистый виртуальный деструктор Abstract::~Abstract( ) // определение чистого деструктора { cout << "Abstract"'<< endl; } // наследник не абстрактный класс class NotAbstract: public Abstract { }; Определять можно не только чистые деструкторы, но и чистые виртуальные методы. Класс, для которого это определение написано, по-прежнему является абстрактным классом. В отличие от чистых виртуальных деструкторов, даже определенная в базовом классе чистая виртуальная функция наследуется как чистая, поэтому производный класс тоже будет абстрактным при отсутствии собственного определения. сlass Base { public: { virtual void f( ) = 0; }; void Base::f( ) { cout << “Base::f( )” << endl; } class Derive : public Base { }; // абстрактный класс // чистый виртуальный метод // определение чистого метода // тоже абстрактный класс В определении чистого виртуального метода базового класса задается общий код, который могут использовать классы-наследники в своих реализациях этой функции. При этом чистый метод базового класса вызывается апатически с префиксом класса. 7 7 Варианты заданий 1. Создать абстрактный базовый класс Figure с виртуальными методами вычисления площади и периметра. Создать производные классы: Rectangle (прямоугольник), Circle (круг), Trapezium (трапеция) со своими функциями площади и периметра. Самостоятельно определить, какие поля необходимы, какие из них можно задать в базовом классе, а какие в производных. Площадь трапеции: S = (а + b) h / 2. 2. Создать абстрактный базовый класс Number с виртуальными методами арифметическими операциями. Создать производные классы Integer (целое) и Real (действительное). 3. Создать абстрактный базовый класс Body (тело) с виртуальными функциями вычисления площади поверхности и объема. Создать производные классы: Parallelepiped (параллелепипед) и Ball (шар) со своими функциями площади поверхности и объема. 4. Создать абстрактный класс Currency (валюта) для работы с денежными суммами. Определить виртуальные функции перевода в рубли и вывода на экран. Реализовать производные классы Dollar (доллар) и Euro (евро) со своими функциями перевода и вывода на экран. 5. Создать абстрактный базовый класс Triangle для представления треугольника с виртуальными функциями вычисления площади и периметра. Поля данных должны включать две стороны и угол между ними. Определить классы-наследники: прямоугольный треугольник, равнобедренный треугольник, равносторонний треугольник со своими функциями вычисления площади и периметра. 6. Создать абстрактный базовый класс Root (корень) с виртуальными методами вычисления корней и вывода результата на экран. Определить производные классы Linear (линейное уравнение) и Square (квадратное уравнение) с собственными методами вычисления корней и вывода на экран. 7. Создать абстрактный базовый класс Function (функция) с виртуальными методами вычисления значения функции у = f(x) в заданной точке х и вывода результата на экран. Определить производные классы Ellipse (эллипс), Hyperbolа (гипербола) с собственными функциями вычисления у в зависимости от входного параметра x. Уравнение эллипса x2/a2 + y2/b2 = 1; гиперболы: x2/a2 y2/b2 = 1. 8. Создать абстрактный базовый класс Integer (целое) с виртуальными арифметическими операциями и функцией вывода на экран. Определить производные классы Decimal (десятичное) и Binary (двоичное), реализующие собственные арифметические операции и функцию вывода на экран. Число представляется массивом, каждый элемент которого цифра. 9. Создать абстрактный базовый класс Series (прогрессия) с виртуальными функциями вычисления j-го элемента прогрессии и суммы прогрессии. Определить производные классы: Linear (арифметическая) и Exponential (геометрическая). (Арифметическая прогрессия aj = a0 + jd, j = 0, 1, 2, ... Сумма арифметической прогрессии: sn= (n+1) (a0 + аn) / 2. Геометрическая прогрессия: аj = a0rj, j = 0, 1, 2,... Сумма геометрической прогрессии: sn = (a0 anr) / (1 r).). 8 10. Создать абстрактный класс Norm с виртуальной функцией вычисления нормы и модуля. Определить производные классы Complex, Vector3D с собственными функциями вычисления нормы и модуля. (Модуль для комплексного числа вычисляется как корень из суммы квадратов действительной и мнимой частей; норма для комплексных чисел вычисляется как модуль в квадрате. Модуль вектора вычисляется как корень квадратный из суммы квадратов координат; норма вектора вычисляется как максимальное из абсолютных значений координат). 11. Создать абстрактный базовый класс Container с виртуальными методами sort() и поэлементной обработки контейнера foreach(). Разработать производные классы Bubble (пузырек) и Choice (выбор). В первом классе сортировка реализуется методом пузырька, а поэлементная обработка состоит в извлечении квадратного корня. Во втором классе сортировка реализуется методом выбора, а поэлементная обработка вычисление логарифма. 12. Создать абстрактный базовый класс Array с виртуальными методами сложения и поэлементной обработки массива foreach(). Разработать производные классы SortArray и ХогАггау. В первом классе операция сложения реализуется как объединение множеств, а поэлементная обработка сортировка. Во втором классе операция сложения реализуется как исключающее ИЛИ, а поэлементная обработка вычисление корня. 13. Создать абстрактный базовый класс Array с виртуальными методами сложения н поэлементной обработки массива foreach(). Разработать производные классы AndArray к OrArray (выбор). В первом классе операция сложения реализуется как пересечение множеств, а поэлементная обработка представляет собой извлечение квадратного корня. Во втором классе операция сложения реализуется как объединение, а поэлементная обработка вычисление логарифма. 14. Создать абстрактный базовый класс Pair с виртуальными арифметическими операциями и операциями сравнения. Реализовать производные классы Complex (комплексное число) и Rational (рациональная дробь). В классе Rational предусмотреть сокращение дроби, используя алгоритм Евклида. Комплексное число в классе Comlex представляются парой действительных чисел (а,b), где а действительная часть, b мнимая часть. Обязательно должны присутствовать операции: сложения add, (а,b) + (с,d) = (а+c,b+d); вычитания sub, (а,b) (с,d) = (аc,bd); умножения mul, (а,b) * (с,d) = (ас bd, ad + bc); деления div, (а,b) / (с,d) = (ас + bd, bc ad) / (с2 + d2). сравнения equal, greate, less. Рациональная дробь в классе Rational представляется парой целых чисел (а,b), где а числитель, b знаменатель. Обязательно должны быть реализованы операции: сложения add, (а,b) + (с,d) = (ad + bc,bd); вычитания sub, (a,b) (с,d) = (ad bc,bd); умножения mul, (a,b) *(c,d) = (ac,bd); деления div, (a,b) / (с,d) = (ad,bc); сравнения equal, greate, less. 9 15. Создать абстрактный базовый класс Triad с виртуальными методами увеличения на 1 каждого элемента класса, виртуальными методами сравнения объектов класса и виртуальным методом вычисления разности двух объектов класса. Создать производные классы Date для работы с датами в формате «день.месяц.год» и Time для работы со временем в формате «часы:минуты:секунды. 16. Создать абстрактный базовый класс Pair с виртуальными арифметическими операциями и операциями сравнения. Создать производные классы Complex (см. вариант 14) и Fraction (дробное число). Реализовать для этих классов операции сложения, вычитания, умножения и сравнения. Дробное число в классе Fraction представлено двумя полями: целая часть – длинное целое число со знаком, дробная часть – беззнаковое короткое целое. 17. Создать абстрактный базовый класс Pair с виртуальными арифметическими операциями и операциями сравнения. Создать производные классы Money и Fraction (вариант 16). Число в классе Money представлено двумя полями: типа long для рублей и типа unsigned char для копеек.