OOP / Лекция 5
.pdfWindow(int xb=10, int yb=10, int a=60,int b=20) //конструктор { x=xb;y=yb;dx=a;dy=b;
backcolor=1;color=15;}
void draw(); //функция изображения окна на экране
};
void Window::draw()
{textbackground(backcolor); for(int i=x;i<x+dx;i++) for (int j=y;j<y+dy;j++)
{ |
|
|
|
|
gotoxy(i,j); |
|
|
|
|
cprintf(" "); |
|
|
|
|
}} |
//класс «текстовый буфер» |
|
||
class Text |
|
|||
{ protected: |
|
|
строк в буфере, UsedN – количество |
реально |
int n,UsedN; //n-количество |
||||
используемых |
|
|
|
|
//строк буфера |
|
|
|
|
char **str; |
//указатель на начало буфера в памяти |
|
||
public: |
//конструктор |
|
|
|
Text(int ); |
|
|
||
~Text(); |
//деструктор |
//функция чтения информации из |
файла в |
|
void Read(char *filename); |
||||
буфер |
|
//функция вывода информации из буфера на экран |
|
|
void Write(); |
|
|||
}; |
|
//конструктор динамически выделяет память под k |
||
Text::Text(int k) |
||||
строк… |
|
|
|
|
{ n=k;UsedN=0; |
|
|
|
|
str=new char*[n]; |
|
|
|
|
for(int i=0;i<n;i++) |
//…в каждой строке 80 символов |
|
||
str[i]=new char[80]; |
|
|||
} |
//деструктор освобождает динамическую память из под буфера |
|||
Text::~Text() |
{for(int i=0;i<n;i++) delete [] str[i];
delete [] str; |
|
|
|
|
} |
|
|
|
|
void Text::Read(char * filename) |
|
|
|
|
{ UsedN=0; |
|
|
|
|
FILE * fp; |
|
//открываем файл |
|
|
if ((fp=fopen(filename,"r"))!=NULL) |
|
|||
{ while(!feof(fp)&&UsedN<n) |
//пока |
не конец файла или не заполнен |
||
весь буфер… |
|
|
//считываем очередную строку |
из |
{ fgets(str[UsedN],80,fp); |
|
|||
файла в буфер |
|
|
|
|
UsedN++; |
|
|
|
|
} |
|
|
|
|
for(int i=0;i<UsedN;i++) |
|
|
|
|
for (int j=0;j<80;j++) |
|
|
|
|
if (str[i][j]=='\n') |
|
|
|
|
{for(int k=j;k<80;k++) |
|
|
|
|
str[i][k]=' '; |
//пробелами заполняем неиспользуемую часть буфера |
|
||
break;} |
|
|
|
|
fclose(fp); |
|
|
|
|
} |
|
|
|
|
else {strcpy(str[0],"Ошибка открытия файла"); //если открыть указанный |
||||
файл |
|
|
|
|
//не удалось записываем информацию об этом в буфер |
|
|||
for(int k=strlen(str[0]);k<80;k++) |
|
|
|
|
str[0][k]=' '; |
|
|
|
|
UsedN=1; |
|
|
|
|
}} |
//функция постраничного |
вывода информации из буфера |
на |
|
void Text::Write() |
экран
{if (UsedN) {clrscr(); int i=0,ii=1; while(i<UsedN)
{for(int j=1;j<80;j++) {gotoxy(j,ii); printf("%c",str[i][j-1]);
}
ii++;i++;
if (ii==25){getch(); clrscr();
ii=1;}
}
getch();
}}
class WinText:public Window ,public Text //класс «окно для отображения
текста» |
|
//величина прокрутки текста в |
окне по |
вертикали |
||
{ int DeltaX,DeltaY; |
||||||
и горизонтали |
|
|
|
|
|
|
public: |
numb=25,int |
xb=10,int |
yb=10,int |
a=60,int |
b=20): |
|
WinText(int |
||||||
//конструктор |
|
|
|
|
|
|
Window(xb,yb,a,b),Text(numb) //вызов конструкторов базовых классов {DeltaX=0;DeltaY=0;}
void draw(); //переопределяем функцию отображения текста так, чтобы текст
//отображался в окне
char Control(); //функция, реализующая реакцию на нажатия клавиш };
void WinText::draw()
{wind::draw();
textcolor(color);
for(int i=0;i<dy&&i+DeltaY<UsedN;i++) for (int j=0;j<dx;j++) {gotoxy(j+y,i+x);
printf("%c",str[i+DeltaY][j+DeltaX]); |
//отображаем |
текст в |
окне с |
||
учетом прокрутки |
|
|
|
|
|
}} |
|
|
|
|
|
char WinText::Control() |
|
|
|
|
|
{ char ch; |
|
|
|
|
|
ch=getch(); |
|
|
|
|
|
if (!ch) |
|
|
|
|
|
{ ch=getch(); |
|
|
|
|
|
switch(ch) |
//обработка нажатия клавиш-стрелок |
|
|
||
{case 72:if (DeltaY>0) {DeltaY--;draw();}break; |
|
|
|||
case 80:if (DeltaY<UsedN){DeltaY++;draw();}break; |
|
|
|||
case 75:if (DeltaX>0) {DeltaX--;draw();}break; |
|
|
|||
case 77:if (DeltaX<80-dy) {DeltaX++;draw();} |
|
|
|||
} } |
|
|
|
|
|
return ch; |
|
|
|
|
|
} |
|
|
|
|
|
main() |
|
|
|
|
|
{textbackground(0); |
|
|
|
|
|
clrscr(); |
|
//определяем |
объект – окно с буфером на 50 |
||
WinText w(50,2,2,10,15); |
|||||
строк |
|
|
|
|
|
// размером 10 на 15 с координатами верхнего левого угла 2,2 |
файла |
||||
w.Read("lect9.cpp"); |
//считываем в |
буфер объекта |
содержимое |
||
lect9.cpp |
//отображаем содержимое буфера в окне |
|
|
||
w.draw(); |
|
|
4.Виртуальные классы
Внекоторых случаях дублирование компонент непрямого базового класса необходимо устранить. Например, перед нами стоит задача определить классы, описывающие поведение шахматных фигур.
//Листинг 27. Определение классов «шахматные фигуры»
class Figure |
//класс «фигура» |
{ protected: |
//позиция фигуры по горизонтали |
int hor; |
|
char vert; |
//позиция фигуры по вертикали |
int color; |
//цвет фигуры |
public:
Figure(char x, int y, int z) //конструктор
: vert(x), hor(y), color(z) |
|
|
{} |
|
|
}; |
|
//класс ладья |
class Castle : public Figure |
||
{ public: |
|
|
Сastle(char x, int y, int z): |
|
|
Figure (x, y, z) |
//конструктор |
|
{} |
int y) |
//функция, реализующая ход ладьи на поле [x |
int Move(char x, |
y ]
{if ( ((x == vert)
&&(y != hor)) || ((x != vert)
&&(y == hor)))
{
hor = y; vert = x; return 1;
} |
|
|
return 0; |
|
|
} |
|
|
}; |
|
//класс слон |
class Bishop : public Figure |
||
{ public: |
|
|
Bishop(char x, int y, int z): |
|
|
Figure (x, y, z) |
//конструктор |
|
{} |
int y) |
//функция, реализующая ход слона на поле [x |
int Move(char x, |
y ]
{ if (abs((x - vert)
==abs(y - hor))
&&(x != vert)) { vert= x; y=hor; return 1;
}
return 0;
}
};
class Queen: public Bishop, public Castle
{
public:
Queen(char x, int y, int z): //конструктор Castle (x, y, z), Bishop (x, y, z)
{}
int Move(char x, int y) {
return Castle::Move(x,y) || Bishop::Move (x,y); }};
В программе определены 4 класса. Класс Figure является абстрактным обобщением свойств всех шахматных фигур, поэтому он содержит такие компонентные данные, как позиция фигуры на доске, определяемая по вертикали буквой vert и по
горизонтали цифрой hor, а также цвет фигуры color. Классы Castle и Bishop описывают, соответственно, ладью и слона. Для этих классов определена функция
int Move(char x, int y),
проверяющая, может ли данная фигура пойти на поле с указанными в параметрах функции координатами, и если может – сделать этот ход. Самым интересным классом является класс Queen, описывающий поведение ферзя. Каждый, кто знаком с правилами шахмат, знает, что ферзь объединяет в себе свойства ладьи и слона (в том смысле, что может ходить как по диагонали, как слон, так и по
Рисунок 10. - Схема иерархии классов программы «Шахматы»
вертикали и горизонтали, как ладья). Поэтому класс Queen объявлен потомком двух классов: Castle и Bishop. Таким образом, имеется иерархия классов, изображенная на рис.10.
Приведенный пример реализации класса Queen содержит ошибку. Дело в том, что для класса Queen дублируются компонентные данные vert, hor и color, в то время как у реального ферзя всего одна позиция на доске и один цвет. Таким образом, встала задача предотвратить дублирование компонент непрямого базового класса в производном. Решить эту проблему можно, объявив класс Figure виртуальным. Для того, чтобы определить непрямой базовый класс виртуальным, необходимо при объявлении этого класса базовым в списке порождения указать ключевое слово virtual. Спецификатор virtual способствует минимизации структуры производного класса. Главная особенность виртуальных базовых классов - они не тиражируются. Изменим определение классов шахматных фигур.
class Figure {…};
class Castle: public virtual Figure {…}; class Bishop: public virtual Figure {…}; class Castle: public Castle, public Bishop{…};
Теперь схема иерархии классов выглядит так, как показано на рис.11, и компонентные данные vert, hor, color не будут дублироваться в объектах класса Queen.
Рисунок 11. - При использовании виртуальных классов схема иерархии классов принимает ромбовидную форму
При использовании виртуальных классов необходимо обратить внимание на особенность вызова конструкторов базовых классов. Конструктор класса Queen был определен следующим образом:
Queen(char x, int y, int z): Castle (x, y, z), Bishop (x, y, z) {}
В конструкторе предусмотрен вызов конструкторов прямых базовых классов, которые создают в памяти экземпляры классов Castle и Bishop . В свою очередь, конструкторы классов Castle и Bishop вызывают конструктор своего базового класса (Figure), вследствие чего и создавалось два экземпляра класса Figure. Если класс Figure объявлен виртуальным, конструктор класса Queen необходимо переопределить:
Queen(char x, int y, int z): Castle (x, y, z), Bishop (x, y, z), Figure(x,y,z)
{}
Если Figure – виртуальный класс, то конструкторы классов Castle и Bishop не будут вызывать конструктор класса Figure, а для того, чтобы один экземпляр этого объекта все таки был создан, вызов конструктора класса Figure необходимо поместить непосредственно в определение класса Queen. При множественном наследовании существует возможность определять один и тот же базовый класс как косвенный базовый для другого класса несколько раз, причем и как виртуальный, и как невиртуальный. Рассмотрим следующий пример:
class A {…};
class B: virtual public A {…}; class C: virtual public A {…}; class D: public A{…};
class E: public A {…};
class F: public B, public C, public D, public E {…};
Схема иерархии для такой системы классов в графической форме приведена на рис.12.
Рисунок 12. - Использование одного и того же класса как виртуальной, так и невиртуальной базы
В данном случае объект класса F будет включать три экземпляра класса A: один виртуальный, совместно используемый классами B и С, и два невиртуальных, относящихся к классам E и D.