Getimage(xl,у1,х2,у2,pnt)

Else

0 0 1 1 2 4 3 9 4 16 5 25 6 36 7 49

A 1 B 2 С 3 D 4 E

В программе шаблон семейства классов с общим именем vector используется для формирования двух классов с массивами целого и символьного типов. В соответствии с требованием синтаксиса имя параметризованного класса, определенное в шаблоне (в примере Vector), используется в программе только с последующим конкретным фактическим параметром (аргументом), заключенным в угловые скобки. Параметром может быть имя стандартного или определенного пользователем типа. В данном примере использованы стандартные типы int и char. Использовать имя Vector без указания фактического параметра шаблона нельзя - никакое умалчиваемое значение при этом не предусматривается.

В списке параметров шаблона могут присутствовать формальные параметры, не определяющие тип, точнее - это параметры, для которых тип фиксирован:

#include <iostream.h>

template <class T, int size = 64>

class row {

T *data;

int length;

public: row() { length = size; data = new T[size]; }

~row() { deleted data; }

T& operator [] (int i) { return data[i]; }

};

void main () {

row <float,8> rf;

row <int,8> ri;

for (int i = 0; i < 8; i++) { rf[i] = i; ri[i] = i * i; }

for (i = 0; i < 8; i++) cout<<« " " << rf[i] << ' ' << ri[i];

}

Результат выполнения программы:

В качестве аргумента, заменяющего при обращении к шаблону параметр size, взята константа. В общем случае может быть использовано константное выражение, однако выражения, содержащие переменные, использовать в качестве фактических параметров шаблонов нельзя.

 

// class Matr

//-------------------------------------------------------------------

template <class T >

class Matr

{

long line;

long col;

T ** matr;

 

public:

virtual ~Matr(); //Деструктор

Matr(long l, long c); // Конструктор

Matr():line(0), col(0),matr(NULL){} //Конструктор по умолчанию

Matr(Matr<T >& A); //Конструктор копии

 

T * operator[](long i){return matr[i];}

template <class T>

friend Matr<T > operator*( Matr<T >& A, Matr<T >& B);

const Matr<T >& operator=(const Matr<T >& A);

template <class T>

friend Matr<T > operator*(double K, const Matr<T >& A);

Matr<T > operator+(const Matr<T >& A);

Matr<T > operator-(const Matr<T >& A);

void display();

};

 

// Matr<T >::Matr()

//-------------------------------------------------------------------

template <class T >

Matr<T >::Matr(long l1, long col1)

{

line = l1;

col = col1;

matr = new T *[line];

if(matr == NULL) cout<< "Out of memory";

for (int i = 0; i < line; i++){

matr[i] = new T [col];

if(matr[i] == NULL) cout<<"Out of memory";

}

}

// Matr<T >::Matr(Matr<T >& A)

//-------------------------------------------------------------------

template <class T >

Matr<T >::Matr(Matr<T >& A):line(A.line), col(A.col)

{

matr = new T *[line];

int i;

if(matr == NULL) cout<< "Out of memory";

for (i = 0; i < line; i++){

matr[i] = new T [col];

if(matr[i] == NULL) cout<<"Out of memory";

}

for(long j = 0; j<line; j++){

for( i = 0; i<col; i++){

matr[j][i] = A.matr[j][i];

}

}

}

// Matr<T >::~Matr()

//-------------------------------------------------------------------

template <class T >

Matr<T >::~Matr()

{

for (int i = 0; i < line; i++) delete matr[i];

delete matr;

}

// void display(const Matr<T >& A)

//-------------------------------------------------------------------

template <class T >

void Matr<T >::display(){

cout<<"\n";

for(int i = 0; i<line;i++){

cout<<"\n";

for(int j = 0; j < col; j++)

cout<<matr[i][j]<<"\t";

}

}

// Matr<T >::operator*()

//-------------------------------------------------------------------

template <class T >

Matr<T > operator*( Matr<T >& A,

Matr<T >& B)

{

if(!(A.col == B.line)) cout<<"\n A*B A.col != B.line";

Matr<T > arMatr(A.line, B.col);

long l1 = A.line;

long col1 = A.col;

long col2 = B.col;

for(long i = 0; i < l1; i++){

for(long j = 0; j < col2; j++){

arMatr[i][j] = 0;

for(long k = 0; k < col1; k++) {

arMatr[i][j]+=A[i][k]*B[k][j];

}

}

}

return arMatr;

}

// Matr::operator=()

//-------------------------------------------------------------------

template <class T >

const Matr<T >& Matr<T >::operator=(const Matr<T >& A)

{

if(this == &A) return *this;

line = A.line;

col = A.col;

for(long i = 0; i<A.line; i++){

for(long j = 0; j<A.col; j++){

matr[i][j] = A.matr[i][j];

}

}

return *this;

}

// Matr<T >::operator*()

//-------------------------------------------------------------------

template <class T >

Matr<T > operator*(double K, const Matr<T >& A)

{

Matr<T > M(A.line, A.col);

for(long i = 0; i<A.line; i++){

for(long j = 0; j<A.col; j++){

M.matr[i][j] = K * A.matr[i][j];

}

}

return M;

}

// Matr<T >::operator+()

//-------------------------------------------------------------------

template <class T >

Matr<T > Matr<T >::operator+(const Matr<T >& A)

{

if(line != A.line || col != A.col) {

cout<<"\n A != B";

Matr<T > M(0,0);

return M;

}

Matr<T > M(A.line, A.col);

for(long i = 0; i<A.line; i++){

for(long j = 0; j<A.col; j++){

M.matr[i][j] = matr[i][j] + A.matr[i][j];

}

}

return M;

}

// Matr<T >::operator-()

//-------------------------------------------------------------------

template <class T >

Matr<T > Matr<T >::operator-(const Matr<T >& A)

{

if(line != A.line) {

cout<<"\n - no A.line = B.line";

Matr<T > M(0,0);

return M;

}

if(col != A.col) {

cout<<"\n - no A.line = B.line";

Matr<T > M(0,0);

return M;

}

Matr<T > M(A.line, A.col);

for(long i = 0; i<A.line; i++){

for(long j = 0; j<A.col; j++){

M.matr[i][j] = matr[i][j] - A.matr[i][j];

}

}

return M;

}

// TMatr()

//-------------------------------------------------------------------

template <class T >

Matr<T > TMatr(Matr<T >& M){

Matr<T > TM(M.col, M.line);

for(int i = 0; i < M.line; i++)

for(int j = 0; j < M.col; j++)

TM[j][i] = M[i][j];

return TM;

}

 

void main(){

Matr<double> A(2,2), B(2,2);

A[0][0]=A[0][1]=A[1][0]=A[1][1] = 1;

B[0][0]=B[0][1]=B[1][0]=B[1][1] = 2;

A.display();

B.display();

A=(2.5*A-A+B)*B;

A.display();

getchar();

}

 

НАСЛЕДОВАНИЕ И ДРУГИЕ ВОЗМОЖНОСТИ КЛАССОВ

Наследование классов

Начиная рассматривать вопросы наследования, нужно отметить, что обоснованно введенный в программу объект призван моделировать свойства и поведение некоторого фрагмента решаемой задачи, связывая в единое целое данные и методы, относящиеся к этому фрагменту. В терминах объектно-ориентированной методологии объекты взаимодействуют между собой и с другими частями программы с помощью сообщений. В каждом сообщении объекту передается некоторая информация. В ответ на сообщение объект выполняет некоторое действие, предусмотренное набором компонентных функций того класса, которому он принадлежит. Таким действием может быть изменение внутреннего состояния (изменение данных) объекта либо передача сообщения другому объекту.

Каждый объект является конкретным представителем класса. Объекты одного класса имеют разные имена, но одинаковые по типам и внутренним именам данные. Объектам одного класса для обработки своих данных доступны одинаковые компонентные функции класса и одинаковые операции, настроенные на работу с объектами класса. Таким образом, класс выступает в роли типа, позволяющего вводить нужное количество объектов, имена (названия) которых программист выбирает по своему усмотрению.

Объекты разных классов и сами классы могут находиться в отношении наследования, при котором формируется иерархия объектов, соответствующая заранее предусмотренной иерархии классов.

Иерархия классов позволяет определять новые классы на основе уже имеющихся. Имеющиеся классы обычно называют базовыми (иногда порождающими), а новые классы, формируемые на основе базовых, - производными (порожденными), иногда классами-потомками или наследниками. Производные классы "получают наследство" -данные и методы своих базовых классов - и, кроме того, могут пополняться собственными компонентами (данными и собственными методами). Наследуемые компоненты не перемещаются в производ­ный класс, а остаются в базовых классах. Сообщение, обработку ко­торого не могут выполнить методы производного класса, автоматически передается в базовый класс. Если для обработки сооб­щения нужны данные, отсутствующие в производном классе, то их пытаются отыскать автоматически и незаметно для программиста в базовом классе (рис. 10.1).

Рис. 10.1. Схема обработки сообщений в иерархии объектов;

1 - обработка сообщения методами производного класса;

2 - обработка сообщения методами базового класса.

Если класс "точка (позиция) на экране" считать базовым классом, то на его основе можно построить класс "окно на экране". Данными этого класса будут две точки:

• точка, определяющая левый верхний угол;

• точка, определяющая размеры окна, т.е. смещения вдоль коор­динатных осей относительно левого верхнего угла.

Методы класса "окно на экране":

• сместить окно вдоль оси х на dx;

• сместить окно вдоль оси y на dy;

• сообщить значение координаты х левого верхнего угла;

• сообщить значение координаты y левого верхнего угла;

• сообщить размер окна вдоль оси х;

• сообщить размер окна вдоль оси Y.

Конструктор окна на экране:

• создать окно на экране с заданным именем по двум точкам, определяющим левый верхний угол окна и его размеры.

Деструктор окна на экране:

• уничтожить окно с заданным именем.

Обратите внимание, что две точки по-разному используются в классе "окно на экране". Первая из них - это абсолютные координаты точки на экране, вторая - интерпретируется просто как пара чисел, определяющая размеры окна. Таким образом, если первая точка имеет координаты (4,3), а вторая (0,0), то это соответствует пустому окну (окну с нулевыми размерами). Наименьшее окно, в которое можно вывести один символ (или один пиксель в графическом режиме), должно иметь размеры (1,1) независимо от положения левого верхнего угла.

При наследовании некоторые имена методов (компонентных функций) и (или) компонентных данных базового класса могут быть по-новому определены в производном классе. В этом случае соответствующие компоненты базового класса становятся недоступными из производного класса. Для доступа из производного класса к компонентам базового класса, имена которых повторно определены в производном, используется операция ' :: ' указания (уточнения) области видимости.

Любой производный класс может, в свою очередь, становиться базовым для других классов, и таким образом формируется направленный граф иерархии классов и объектов. В иерархии производный объект наследует разрешенные для наследования компоненты всех базовых объектов. Другими словами, у объекта имеется возможность доступа к данным и методам всех своих базовых классов.

Наследование в иерархии классов может отображаться и в виде дерева, и в виде более общего направленного ациклического графа. Допускается множественное наследование - возможность для некоторого класса наследовать компоненты нескольких никак не связанных между собой базовых классов. Например, класс "окно на экране" и класс "сообщение" совместно могут формировать новый класс объектов "сообщение в окне".

При наследовании классов важную роль играет статус доступа (статус внешней видимости) компонентов. Для любого класса все его компоненты лежат в области его действия. Тем самым любая принадлежащая классу функция может использовать любые компонентные данные и вызывать любые принадлежащие классу функции. Вне класса в общем случае доступны только те его компоненты, которые имеют статус public.

В иерархии классов соглашение относительно доступности компонентов класса следующее.

Собственные (private) методы и данные доступны только внутри того класса, где они определены.

Защищенные (protected) компоненты доступны внутри класса, в котором они определены, и дополнительно доступны во всех производных классах.

Общедоступные (public) компоненты класса видимы из любой точки программы, т.е. являются глобальными.

Если считать, что объекты, т.е. конкретные представители классов, обмениваются сообщениями и обрабатывают их, используя методы и данные классов, то при обработке сообщения используются, во-первых, общедоступные члены всех классов программы; во-вторых, защищенные компоненты базовых и рассматриваемого классов и, наконец, собственные компоненты рассматриваемого класса. Собственные компоненты базовых и производных классов, а также защищенные компоненты производных классов недоступны для сообщения и не могут участвовать в его обработке.

Еще раз отметим, что на доступность компонентов класса влияет не только явное использование спецификаторов доступа (служебных слов) - private (собственный), protected (защищенный), public (общедоступный), но и выбор ключевого слова class, struct, union, с помощью которого объявлен класс.

Определение производного класса. В определении и описании производного класса приводится список базовых классов, из которых он непосредственно наследует данные и методы. Между именем вводимого (нового) класса и списком базовых классов помещается двоеточие. Например, при таком определении

class S: X, Y, Z { ... };

класс s порожден классами x,y,z, откуда он наследует компоненты. Наследование компонента не выполняется, если его имя будет использовано в качестве имени компонента в определении производного класса s. Как уже говорилось, по умолчанию из базовых классов наследуются методы и данные со спецификаторами доступа - public (общедоступные) и protected (защищенные).

В порожденном классе эти унаследованные компоненты получают статус доступа private, если новый класс определен с помощью ключевого слова class, и статус доступа public, если новый класс определен как структура, т.е. с помощью ключевого слова struct. Таким образом, при определении класса struct J: x, z { ... ); любые наследуе­мые компоненты классов х, z будут иметь в классе J статус общедо­ступных (public). Пример:

clasa B { protected: int t;

public: char u; };

class E: В { ... }; // t, u наследуются как private

struct S: В { ... ); // t, u наследуются как public

Явно изменить умалчиваемый статус доступа при наследовании можно с помощью спецификаторов доступа - private, protected и public. Эти спецификаторы доступа указываются в описании производного класса непосредственно перед нужными именами базовых классов. Если класс в определен так, как показано выше, то можно ввести сле­дующие производные классы:

class M: protected В { ... }; // t, u наследуются как

// protected

class P: public В {...}; // t - protected, и – public

class D: private В { ... }; // t, и наследуются как

// private

struct F: private В {...}; // t, и наследуются как

// private

struct G: public В {...); // t - protected, и - public

Соглашения о статусах доступа при разных сочетаниях базового и производного классов иллюстрирует табл. 10.1.

Обратите внимание на тот факт, что ни базовый класс, ни произ­водный не могут быть объявлены с помощью ключевого слова union. Таким образом, объединения не могут использоваться при построе­нии иерархии классов.

Чтобы проиллюстрировать некоторые особенности механизма на­следования, построим на основе класса point (см. п. 9.4) производный класс spot (пятно). Наследуемые компоненты класса point:

• int x, у - координаты точки на экране;

• point () - конструктор;

• givex (), givey () - доступ к координатам точки;

• show () - изобразить точку;

• move () - переместить точку.

Дополнительно к наследуемым компонентам в класс spot введем: радиус пятна (гad); его видимость на экране (vis — 0, когда изображения нет на экране, vis == 1 - изображение есть на экране); признак сохранения образа в оперативной памяти (tag == 0 - битовый образ не хранится, tag == 1 - битовый образ хранится в памяти); указатель pspot на область памяти, выделенную для хранения битового образа изображе­ния.

 

Таблица 10.1 Статусы доступа при наследовании

 

 

Доступ в базовом классе Спецификатор доступа перед базовым классом Доступ в производном классе
struct class
public отсутствует public private
protected отсутствует public private
private отсутствует. недоступны недоступны
public public public public
protected public protected protected
private public недоступны недоступны
public protected protected protected
protected protected protected protected
private protected недоступны недоступны
public private private private
protected private private private
private private недоступны недоступны

// SPOT.CPP - класс, наследующий данные и методы

// класса POINT

#ifndef SPOT

#define SPOT 1

#include "point.cpp" // Определение класса point

class spot:

// 'public1 позволит сохранить статусы доступа для

// наследуемых компонентов класса POINT: public point

{ // Статус доступности данных в производных классах: protected:

int rad; // Радиус пятна (изображения)

int vis; // Видимость пятна на экране

int tag; // Признак сохранения образа в памяти

void *pspot; // Указатель на область памяти для

// изображения (для битового образа)

Доступ в базовом Спецификатор классе доступа перед Доступ в производном классе базовым классом struct class publicотсутствует public private protectedотсутствует public private privateотсутствует. недоступны недоступны public public public public protected public protected protected private publicнедоступны недоступны publicprotected protected protected protectedprotected protected protected privateprotected недоступны недоступны public private private private protected private private private private privateнедоступны недоступны

public:

// Конструктор класса SPOT:

spot(int xi, int yi, int ri):

// Вызов конструктора базового класса:

point(xi,yi) { int size;

vis = 0; tag =0; rad = ri; // Определить размеры битового образа:

size = image size (xi-ri,yi-ri,xi+ri, yi+ri) ; // Выделить память для битового образа:

pspot = new char[size]; }

~spot() // Деструктор класса

SPOT { hide(); // Убрать с экрана изображение пятна

tag =0; // Сбросить признак сохранения в памяти

delete pspot; // Освободить память, где находился

// битовый образ }

void show() // Изобразить пятно на экране дисплея

{ // Если битового образа нет в памяти:

if (tag = = 0)

{ // Нарисовать окружность на экране:

circle(х,у,rad); // Закрасить пятно

floodfill(x,y,getcolor()); // Запомнить битовый образ в памяти:

getimage(x-rad,y-rad,x+rad,y+rad,pspot); tag = 1; }

// Перенести изображение из памяти на экран:

putimage (x-rad,y-rad,pspot,XOR_PUT) ;

vis = 1; }

void hide() // Убрать с экрана изображение пятка

{ if (via = = 0) // Нечего убирать return;

// Стереть изображение с экрана:

putimage(x-rad,y-rad,pspot,XOR_PUT);

vis = 0; }

// Переместить изображение:

void move(int xn, int yn) { hide(); // Убрать старое изображение с экрана

// Изменить координаты центра пятна:

х = хп; у = уп;

show () ; // Вывести изображение в новом месте

}

// Изменить размер изображения пятна:

void vary(float dr) { float a;

int size;

hide(); // Убрать старое изображение с экрана

tag = 0;

// Освободить память битового образа:

delete pspot;

// Вычислить новый радиус:

а = dr * rad;

if (а <= 0) rad = 0; else rad = (int)a;

// Определить размеры битового образа:

size = imagesize(x-rad,y-rad,x+rad,y+rad);

// Выделить память для нового образа:

new char[size];

show(); // Изобразить пятно на экране

}

int& giver(void) // Доступ к радиусу пятна

{ return rad; }

};

#endif

В классе spot явно определены конструктор, деструктор ~spot() и пять методов: show () вывести на экран изображение пятна, затем перенести его битовый образ в память; hide () убрать с экрана изображение пятна; move () переместить изображение в другое место на экране;

vary () изменить (уменьшить или увеличить) изображение на экране; giver () обеспечить доступ к радиусу пятна.

Из класса point класс spot наследует координаты (х, у) точки (центра пятна) и методы givex(), givey(). Методы point: :show(), point :: move () заменены в классе spot новыми функциями с такими же именами, а функция point : : hide () не наследуется, так как в классе point она имеет статус собственного компонента (private).

Конструктор spot () имеет три параметра - координаты центра (xi, yi) и радиус пятна на экране (ri). При создании объекта класса spot вначале вызывается конструктор класса point, который по зна­чениям фактических параметров, соответствующих xi, yi, определяет точку - центр пятна. Эта точка создается как безымянный объект класса point. (Конструктор базового класса всегда вызывается и вы­полняется до конструктора производного класса.) Затем выполняются операторы конструктора spot (). Здесь устанавливаются начальные значения признаков vis, tag, и по значению фактического параметра, соответствующего формальному параметру ri, определяется радиус пятна rad. С помощью стандартной функции image size () из графи­ческой библиотеки graphics, lib вычисляется объем памяти (вспомогательная переменная size), требуемый для сохранения пря­моугольного (квадратного) участка экрана, на котором предполагает­ся изобразить пятно. Выделение участка основной памяти нужного объема выполняет стандартная операция new, операнд которой - это массив типа char из size элементов. Выделенная память связывается с указателем pspot, имеющим в классе spot статус protected. На этом работа конструктора заканчивается.

В функциях show () - изобразить пятно на экране, varyO - изме­нить размер изображения и hide {) - убрать изображение пятна с эк­рана используются возможности графических функций: circle(х,у,rad)

нарисовать окружность с центром в точке с координатами (х, у) и радиусом rad; floodfill(x,y,c)

закрасить ограниченную область, которой принадлежит точ­ка с координатами (х, у), цветом, определенным параметром с, getcolor() определить текущий цвет изображений;

поместить в заранее выделенный участок основной памяти, связанный с указателем pnt, битовый образ прямоугольного участка экрана, выделенного координатами левого верхнего (xl, yl) и правого нижнего (х2, у2) углов;