- •Овсянник в.Н. Лабораторные работы по курсу «Объектно-ориентированное программирование»
- •1.Интегрированная среда mvs-2010
- •1.1.Методические указания
- •2.Массивушки и подпрограммочки
- •Прочитайте это задание и методические указания до конца, прежде чем терзать клавиатуру, соседа, преподавателя и пр. Сущности
- •Попробуйте сдать работу, предварительно тщательно протестировав ее на предмет отсутствия хомутов
- •2.1.Методические указания
- •3.Поиск экстремумов с ограничениями или «Брачное агенство»
- •Постановка задачи
- •Задание
- •Листинг файла mAgency.Cpp (с главной функцией)
- •Листинг файла Lib.H
- •Листинг файла Lib.Cpp
- •4.Задача «куча камней»
- •4.1.Постановка задачи.
- •4.2.Методические указания.
- •Некоторые примеры разделения камней на две кучи, которые должна решать ваша программа
- •5.Разработка простого класса
- •5.1.Задание
- •5.2.Описание вариантов заданий
- •5.3.Драгоценные методические указания
- •6.Класс вектор
- •6.1.Пример класса tVector
- •6.2.Класс tVector с перегруженными операциями
- •7.Наследование классов
- •7.1.Задание
- •7.2.Методическая помощь
- •7.3.Описание самых легких вариантов заданий
- •7.4.Контроль качества выполненной работы
- •8.Приложение с окном вида
- •9.Абстрактные классы и виртуальные функции
- •9.1.Задание
- •9.2.Методические указания
- •Void PrintClassNamе1(cBasе *pb) // параметр-указатель
- •Void PrintClassNamе2(cBasе &b) // параметр-ссылка
- •Void PrintClassNamе3(cFirst f) // параметр-значение
- •9.3.Варианты заданий
- •10.Разработка класса контейнера
- •10.1.Задание
- •10.2.Описание вариантов заданий
- •10.3.Методические указания
- •12.Перегрузка векторных и матричных операций
- •12.1.Задание
- •12.2.Бесценные методические указания
- •12.3.Некоторые особенности перегрузки операций
- •12.4.Варианты заданий
- •13.Программирование односвязного списка
- •13.1.Задание
- •13.2.Описание вариантов заданий
- •14.Приложение, основанное на модальном диалоговом окне
- •14.1.Нудные методические указания Модальные и немодальные диалоговые окна
- •Ресурсы и элементы управления
- •Сценарий создания приложения
- •Разбор приложения
- •Усовершенствование приложения
- •15.Приложения
- •15.1.Виды сортировок
- •Классификация сортировок
- •Сортировка массивов
- •15.2.Алгоритм сортировки включением
- •Пример сортировки с помощью прямого включения
- •15.3.Сортировка Шелла
- •Список литературы
9.Абстрактные классы и виртуальные функции
Цель работы – освоить разработку иерархии классов с виртуальными функциями, основанной на абстрактном базовом классе (4 час.).
9.1.Задание
По конспекту лекций освежите в памяти абстрактные и виртуальные функции, а также абстрактные классы.
Ознакомьтесь с приведенными ниже методическими указаниями.
Создайте заготовку консольного приложения.
В соответствии с вашим вариантом задания разработайте иерархию классов, добавьте их в приложение. Базовый класс вашей иерархии должен содержать конструктор с параметрами и минимум одну абстрактную функцию; производные классы должны содержать реализацию абстрактной функции (функций) и конструкторы с параметрами.
Опишите указатели на абстрактный класс, создайте с их использованием объекты производных классов иерархии, вызовите методы классов и убедитесь в том, что механизм виртуальных функций работает корректно и объекты действительно полиморфны. Методы полиморфных объектов вызывайте в глобальных функциях, как показано ниже в методических указаниях.
9.2.Методические указания
В качестве иллюстрации разработаем иерархию классов (рис. 1) и поместим ее реализацию в файлы Base.h и Base.cpp (проект ACVF).
|
#pragma once
class CBase
{
public:
virtual char * getName()=0;
};
class CFirst: public CBase
{
double *dFoo;
public:
virtual char * getName();
CFirst();
~CFirst();
};
class CSecond: public CFirst
{
int *iFoo;
public:
CSecond();
~CSecond();
virtual char * getName();
};
Рис. 1. Иерархия классов
Реализацию методов класса поместим в файл Base.cpp и добавим в конструкторы и деструкторы вывод сообщений об их вызове. Для вывода сообщений можно было бы использовать макросы TRACE, но в данном случае мне показалось удобнее использовать для вывода cout.
// Файл Base.cpp
#include "StdAfx.h"
#include "Base.h"
using namespace std;
CBase::~CBase()
{
cout<<"CBase::~CBase"<<endl;
}
CFirst::CFirst()
{
dFoo=new double [100];
cout<<"CFirst::CFirst"<<endl;
}
CFirst::~CFirst()
{
cout<<"CFirst::~CFirst"<<endl;
delete [] dFoo;
}
CSecond::CSecond()
{
cout<<"CSecond::CSecond"<<endl;
iFoo=new int [100];
}
CSecond::~CSecond()
{
cout<<"CSecond::~CSecond"<<endl;
delete [] iFoo;
}
char * CFirst::getName()
{return "CFirst";}
char * CSecond::getName()
{return "CSecond";}
Для проверки виртуальных функций разработаем глобальные функции, которые получают объекты классов иерархии в качестве параметров:
Void PrintClassNamе1(cBasе *pb) // параметр-указатель
{
сout<<pb->gеtNamе()<<еndl;
}
Void PrintClassNamе2(cBasе &b) // параметр-ссылка
{
сout<<b.gеtNamе()<<еndl;
}
Void PrintClassNamе3(cFirst f) // параметр-значение
{
сout<<f.gеtNamе()<<еndl;
}
Теперь поместим в главную функцию код, который позволит испытать полиморфные объекты иерархии:
int _tmain(int argс, TCHAR* argv[], TCHAR* еnvp[])
{
//...
CBase *obj;
obj=new CFirst; // вывод CFirst::CFirst
cout<<endl;
PrintClassName1(obj); // выводит CFirst
cout<<endl;
PrintClassName2(*obj); // выводит CFirst
cout<<endl;
delete obj; //Вывод CBase::~CBase!! Деструктор ~CFirst не вызван!
cout<<endl;
obj=new CSecond; // вывод CFirst::CFirst CSecond::CSecond
PrintClassName1(obj); // выводит CSecond
cout<<endl;
delete obj; //Вывод CBase::~CBase!! Деструкторы ~CSecond и ~CFirst
// не вызваны!
CFirst * fobj=new CSecond; // вывод CFirst::CFirst CSecond::CSecond
cout<<endl;
PrintClassName1(fobj); // выводит CSecond
cout<<endl;
PrintClassName2(*fobj); // выводит CSecond
cout<<endl;
PrintClassName3(*fobj); // выводит CFirst, затем CFirst::~CFirst
// и CBase::~CBase
cout<<endl;
delete fobj; /* Вывод CFirst::~CFirst, после чего программа снимается
по ошибке с указанием в качестве возможной причины на
разрушение heap */
_gеtсh();
rеturn nRеtCodе;
}
Намотайте на ус, что в операторе obj=new CFirst указатель на объект obj объявлен как имеющий тип CBase, но создается объект производного типа CFirst! Подобным образом должны действовать и вы при выполнении своего индивидуального задания.
С этим все понятно, так? Но вот первая проблема: создается объект класса CSecond с помощью оператора CFirst * fobj=new CSecond, как и положено, вызываются конструкторы CFirst и CSecond (именно в этой последовательности, в соответствии со спецификацией языка), но вот при выполнении оператора PrintClassName3(*fobj) вызываются только деструкторы ~CFirst и ~CBase, а вот деструктор ~CSecond не вызывается, хотя должен бы, уродина! То, что при этом не вызываются конструкторы при передаче объекта как параметра функции – песнь отдельная (см. методический материал л.р. «Перегрузка векторных и матричных операций»).
Как печальный результат, выполнение оператора delete fobj приводит к (повторному!) вызову деструктора класса ~CFirst и снятию программы по ошибке при выполнении оператора освобождения памяти в этом деструкторе. Если же убрать оператор PrintClassName3(*fobj), то после завершения программы обнаружатся утечки памяти.
В чем же причина или как следует обеспечить корректный вызов деструкторов в том случае, когда объект объявляется как имеющий тип базового класса, а создается с помощью конструктора производного, как в программе:
CBase *obj;
obj=new CFirst;
Выход очень простой: надо объявить конструкторы классов иерархии виртуальными, причем достаточно это сделать только в базовом классе. Проверьте!
