Мы рассмотрели и исследовали объявленные в начале основополагающие концепции ООП – объект, класс, инкапсуляция, наследование, полиморфизм, абстракция типов. Теперь после изучения возможностей концепции наследования классов (простое, множественное, управление режимами доступа, виртуальные функции, абстрактные классы) на демонстрационных примерах полезно рассмотреть обобщающий пример.
Постановка задачи.
Рассмотрим разработку полного графического примера, в котором выводятся на экран монитора геометрические фигуры, с которыми можно совершать некоторые действия, например, создавать, уничтожать, перемещать по экрану и т. д. Прообразом этой задачи является пример 28.
Вместо ограниченного по своим возможностям класса Location в качестве базового класса опишем свойства и функции "некоторой фигуры" (Figure). Местоположение ее определяется двумя координатами экрана (X, Y), инициализация которых задается встроенным конструктором, а для получения значений текущих координат используются встроенные методы класса GetX(), GetY(). Параметры X, Y объявлены защищенными элементами базового класса, чтобы производные классы могли их наследовать и использовать.
В качестве основных действий, которые можно совершать с этой фигурой, могут быть: показ на экране (Show), удаление (Hide), перемещение (Drag). Описание этих действий представлено чистыми виртуальными функциями без их тел, что делает класс абстрактным. Для этого класса нельзя создать реальный объект на экране, но можно определить указатель или массив указателей на базовый тип и использовать их при создании объектов производных классов. При этом могут применяться только наследуемые виртуальные функции, которые видимы через базовый указатель из производного класса.
Базовому классу наследует производный класс (Figure <- Point), который описывает простейший реальный объект "точка", координаты которой наследуются из базового класса (X, Y), а также задается свой параметр видимости точки на экране как защищенный элемент Visible с булевыми значениями. Объявлены обычные методы: функция Isvisible(), определяющая видимость точки, и функция Moveto(), задающая новые координаты точки. Как невиртуальные методы они могут наследоваться производными классами. Их можно сделать виртуальными, объявив их как virtual, и тогда в производных от Point классах необходимо замещать их "своими" виртуальными методами.
Чистые виртуальные функции класса Figure замещаются конкретными виртуальными функциями (Show(), Hide(), Drag()) в классе Point. Виртуальная функция Drag() использует обычный метод своего класса Moveto(), который определяет новые координаты объекта, а также обычную функцию общего назначения (т.е. не метод класса) Getdelta(). Последняя определяет направление смещения точки (или другой реальной фигуры) на экране по нажатию пользователем клавиш-стрелок клавиатуры (вправо, влево, вверх, вниз) и окончание процесса смещения объекта на экране при нажатии клавиши <Enter>.
В главной функции программы демонстрируется создание и перемещение точки по экрану, создание "звездного" неба с использованием стандартной функции получения случайных чисел X, Y для координат точки random(max), где max – максимальное число для координаты экрана с установленной разрешающей способностью. Обработка программы выполняется в среде компилятора Borlandc 3.1.
Пример 40.
Рассмотрим программу для демонстрации описанной выше постановки задачи.
#include<iostream.h>
#include<conio.h>
#include<graphics.h> // для графической библиотеки
#include<stdlib.h> // для функций random(), srand(), kbhit()
#include<dos.h> // для функции delay()
const char* path= "c:\\borlandc\\bgi"; // путь к библиотеке bgi-файлов
enum Boolean { false, true }; // булевы значения
// прототип обычной функции (не метод), определенной ниже:
Boolean Getdelta (int& Deltax, int& Deltay);
class Figure // абстрактный базовый класс "фигура"
{ protected: // защищенные элементы, доступные в производных классах
int X, Y; // координаты объекта на экране
public:
Figure (int Initx, int Inity) // конструктор базового класса Figure
{ X = Initx, Y = Inity; } // инициализация координат
// встроенные функции-методы получения текущих координат объекта:
int Getx () { return X; }
int Gety () { return Y; }
// чистые виртуальные функции-методы базового класса:
virtual void Show() = 0; // изображение объекта
virtual void Hide() = 0; // стирание объекта с экрана
virtual void Drag (int Dragby) = 0; // перемещение объекта
};
// производный класс Point с открытым доступом к базовому классу Figure:
class Point: public Figure // класс "точка"
{ protected:
Boolean Visible; // параметр видимости точки
public:
Point (int Initx, int Inity); // конструктор класса Point
// виртуальные функции-методы класса Point:
void Show(); // изображение точки на экране
void Hide(); // стирание точки с экрана
void Drag (int Dragby); // перемещение точки по экрану
// невиртуальные методы класса Point:
Boolean Isvisible() { return Visible;} // проверка точки на видимость
void Moveto (int Newx, int Newy); // смещение точки на новое место
};
// методы класса Point:
Point:: Point (int Initx, int Inity): // конструктор класса Point
Figure (Initx, Inity) // передача аргументов базовому классу
{ Visible = false; // инициализация параметра Point
}
void Point:: Show() // метод изображения точки на экране
{ if (! Visible) // если точка невидима, то
{ Visible = true; // установка видимости точки
putpixel (X, Y, getcolor()); // изображение цветной точки
}
}
void Point:: Hide() // метод стирания точки с экрана
{ if (Visible) // если точка видима, то
{ Visible = false; // установка невидимости точки
putpixel (X, Y, getbkcolor()); // закрашивание точки цветом фона
}
}
void Point:: Moveto (int Newx, int Newy) // метод смещения точки
{ if (Visible) Hide (); // стирание точки
X = Newx; // новые координаты точки X и Y
Y = Newy;
Show (); // изображение точки
}
void Point:: Drag(int Dragby) // метод смещения фигуры на величину Dragby
{ int Deltax, Deltay; // смещение по X, Y
int Figurex, Figurey; // координаты по X, Y
Show(); // изображение точки
while (Getdelta (Deltax, Deltay)) // цикл перемещения точки:
{ Figurex = Getx()+(Deltax * Dragby); // смещение по X
Figurey = Gety()+(Deltay * Dragby); // смещение по Y
Moveto (Figurex, Figurey); // перемещение точки на новое место
}
}
/* обычная функция (вне классов) определения направления смещения объекта,
которая анализирует код нажатой управляющей клавиши и возвращает
разрешение на смещение (true) в заданном направлении через параметры
ссылки (&), либо отмену смещения (false) при нажатии клавиши <Enter>;
используется в методе Point:: Drag():
*/
Boolean Getdelta (int& Deltax, int& Deltay) // & - ссылки на смещение
{ char Keychar; // переменная символа клавиши
Boolean Quit; // переменная смещения
Deltax = 0; // начальные значения смещений
Deltay = 0;
do { Keychar = getch (); // цикл анализа кода нажатой клавиши:
if (Keychar == 13) // если код = 13 (<Enter>), то
return (false); // конец смещения объекта;
if (Keychar == 0) //если код = 0, то это скэн-код клавиши
{ Quit = true; // подтверждение смещения
Keychar = getch (); // чтение 2- го байта кода
switch (Keychar) // анализ кода управления:
{ case 72: Deltay = -1; break; // стрелка вниз
case 80: Deltay = 1; break; // стрелка вверх
case 75: Deltax = -1; break; // стрелка влево
case 77: Deltax = 1; break; // стрелка вправо
default: Quit = false; // неправильная клавиша
}
}
} while (! Quit); // продолжение цикла чтения кода клавиш
return (true); // разрешение смещения объекта
}
void main() // главная функция:
{ int graphdriver = DETECT, graphmode; // данные режима графики
initgraph (&graphdriver, &graphmode, path); // инициализация графики
int n, i, x, y, maxx, maxy, maxcolor, seed, color; // рабочие переменные
int DelayTime; // временная задержка
x=100; // координаты экрана (х,у)
y=200;
Point Apoint (x, y); // создание и инициализация точки
Apoint.Show(); // изображение точки
Apoint.Drag(20); // перемещение точки по экрану
getch (); // задержка образа экрана
/*
// изображение статического "звездного неба":
cout<< "Enter number of points (<32000): ";
cin >> n; // ввод количества точек
for (i=0; i<n; i++) // цикл создания точек
{ x = random(639); // случайные коодинаты точки (х, у)
y = random(479);
Point Apoint (x,y); // создание и инициализация точки
Apoint.Show(); // изображение точки
}
*/
/*
// изображение мерцающих цветных точек
maxx = getmaxx(); // количество пикселей по оси Х экрана
maxy = getmaxy(); // количество пикселей по оси Y экрана
maxcolor = getmaxcolor(); // количество цветов
cout<< "Enter number of points (<2000): ";
cin >> n; // ввод количества точек
cout<<"Enter DelayTime (millisecond) = ";
cin >> DelayTime; // ввод временной задержки
while (! kbhit()) // цикл пока не нажата любая клавиша
{ seed = random (RAND_MAX); // начальное случайное число
srand (seed); // запуск генератора случайных чисел
for (i=0; i<n; i++) // цикл создания точек
{ x = random(maxx); // случайные координаты по x
y = random(maxy); // и по y
color = random(maxcolor); // случайное значение цвета
setcolor(color); // установка цвета
Point Apoint (x, y); // создание и инициализация точки
Apoint.Show(); // изображение точки
}
delay (DelayTime); // задержка по времени
srand (seed); // запуск генератора случайных чисел
for (i=0; i<n; i++) // цикл создания точек
{ x = random (maxx); // случайные коодинаты точки (х, у)
y = random (maxy);
color = random (maxcolor); // случайное значение цвета
if (color == getpixel (x, y)) // если цвет точки = случайному, то
{ Apoint.Moveto (x, y); // смещение точки и
Apoint.Hide(); // стирание
}
}
}
getch (); // задержка экрана
closegraph(); // закрытие графического режима
}
Модульность классов
Для разработки связанных наследованием классов целесообразно использование принципов модульного программирования, когда программа состоит из файлов заголовков, файлов отдельно компилируемых модулей, соединяемых в единое целое с помощью файла проекта программы.
Суть в том, что, имея встроенные элементы данных, методы и права доступа, класс обладает прирожденной модульностью. Поэтому при разработке программ следует выделять объявления каждого класса или группы взаимосвязанных классов в файл заголовков, с возможностью его дальнейшего расширения, либо добавления новых файлов заголовков при разработке новых классов. Описания невстроенных в класс методов при этом выделяются в другой файл (или файлы) реализации методов. После их компиляции образуются объектные файлы. Возможно группирование нескольких объектных файлов, содержащих классы, в библиотеку классов и расширение этой библиотеки. При этом алгоритмы методов остаются скрытыми, но они в объектной форме становятся доступными другим программистам. Последние могут использовать полученную библиотеку классов для разработки своих новых специализированных классов, не прибегая к исходному тексту программ.
Большая часть усилий по разработке приложений в С++ концентрируется на построении иерархии классов и трансформации полученного дерева наследования в код программы.
Размещение нескольких классов в одном модуле (файле)
Пример 41.
Рассмотрим разработку независимо компилируемого модуля, в состав которого входят классы Figure и Point на основе программы примера 40, то есть тела классов и функций должны быть скопированы из примера 40. Объявления этих двух классов поместим в файл "figures.h":
enum Boolean { false, true }; // булевы значения
class Figure // абстрактный базовый класс "фигура"
{ // тело класса Figure
};
// производный класс Point с открытым доступом к базовому классу Figure:
class Point: public Figure // класс "точка"
{ // тело класса Point
};
Этот процесс может продолжаться неопределенно долго – допускается определять другие производные классы от Figure, Point и т. д. Более того, как известно, допускается множественное наследование от более чем одного базового класса.
Определения всех невстроенных методов указанных двух классов образуют файл figpoint.cpp:
#include "figures.h" // подключение файла объявления классов
#include<graphics.h> // подключение библиотеки графики
#include<conio.h> // для функции getch()
// прототип обычной функции (не метод):
Boolean Getdelta (int& Deltax, int& Deltay);
// методы класса Point:
Point:: Point (int Initx, int Init): // конструктор класса Point
Figure (Initx, Inity) // передача аргументов базовому классу
{ Visible = false; // инициализация параметра Point
}
void Point:: Show() // метод изображения точки на экране
{ // тело метода Show
}
void Point:: Hide() // метод стирания точки с экрана
{ // тело метода Hide
}
void Point:: Moveto (int Newx, int Newy) // метод смещения точки
{ // тело метода Moveto
}
void Point::Drag(int Dragby) //метод перемещения точки с дискретом Dragby
{ // тело метода Drag
}
// обычная функция Getdelta:
Boolean Getdelta (int& Deltax, int& Deltay) // & - ссылки на смещение
{ // тело функции Getdelta
}
Рассмотрим головную программу, демонстрирующую возможности классов Figure и Point, которая сохраняется в файле, например, mainpoin.cpp:
#include "figures.h" // подключение файла объявления классов
#include<graphics.h> // подключение библиотеки графики
#include<conio.h> // для функции getch()
#include<iostream.h> // для ввода-вывода данных
#include<stdlib.h> // для функций random(), srand(), kbhit()
#include<dos.h> // для функции delay()
const char* path= "c:\\borlandc\\bgi"; // путь к bgi-библиотеке
void main() // главная функция:
{ // тело функции main
}
Создание проекта программы.
Для того чтобы разработать единую программу из нескольких разнотипных файлов, необходимо создать проект программы и откомпилировать его в среде BORLANDC3.1. Последовательность разработки проекта программы включает следующие этапы.
1. Создать папку (каталог), где будут размещаться все файлы проекта, например, под именем FIGPOINT.
2. Записать в папку проекта разработанные текстовые файлы (срр-файлы, h-файлы): figpoint.cpp, mainpoin.cpp, figures.h.
3. Выполнить команду меню Project-Open project. В окне Open-Project File перейти в поле Files и, начиная с нужного диска, найти свою папку. Перейти в поле Open-Project File, где вместо символа * набрать имя файла проекта, например, figpoint.prj и щелкнуть кнопку ОК. Внизу экрана появится окно Project:Figpoint с высвеченной строкой.
4. Выполнить команду меню Project-Add item. В окне Add item to Project List перейти в поле Files. Выделяя срр-файлы в любом порядке, кнопкой Add включить их в файл проекта и закрыть окно кнопкой Done.
5. Запустить файл проекта на компиляцию и выполнение командой Run-Run или клавишами <Ctrl-Enter>.
6. Выполнить отладку исходных файлов, выделяя в окне Project строку файла и нажав <Enter>.
7. После отладки проекта программы получить результаты работы exe-файла.
Проект программы может включать следующие файлы: 1) исходные срр-файлы; 2) файлы заголовков разработчика программы (h-файлы); 3) объектные obj-файлы; 4) файл проекта prj-файл; 5) исполняемый ехе-файл; 6) файл настройки среды dsk-файл, 7) копии файлов (bak-файлы).