Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Повторное использование классов: наследование и агрегирование



 

Наследование - один из основополагающих принципов объектно-ориентированного программирования. Под наследованием понимают возможность объявления производных типов на основе ранее объявленных типов. Свойство наследования присуще только классам. Как известно, в C++ существует фиксированное множество элементарных типов. Это абсолютно независимые типы и объявление одного элементарного типа на основе другого в принципе невозможно.

Спецификации объявления unsigned int или long double нельзя рассматривать как модификации элементарных типов int и double. Это полноправные элементарные типы данных со своим собственным набором свойств. В C++ также невозможно определить одну функцию на основе другой ранее определённой.

Для класса в C++ реализуется возможность наследования. Наследование прежде всего является эффективным механизмом повторного использования классов, когда новые классы строятся при необходимости на базе уже существующих, а не с нуля. При этом необходимо различать понятия наследования и агрегирования. Наследование отражает отношения между классами «это есть». Примеры наследования: автомобиль есть транспортное средство, клиент банка есть человек, прямоугольник есть геометрическая фигура. Таким образом, при наследовании базовый и производный классы выступают как, соответственно, обобщение и конкретизация некоторого объекта реального мира. В случае наследования новый класс в буквальном смысле создаётся на основе ранее объявленного класса, наследует, а возможно и модифицирует его данные и функции. Объявленный класс может служить основой (базовым классом) для новых производных классов. Производные классы наследуют данные и функции своих базовых классов и добавляют собственные компоненты.

Агрегирование предполагает возможность объявления в классе отдельных членов класса на основе ранее объявленных классов. Таким образом, агрегирование отражает отношение между классами “быть частью”. Примеры агрегирования: двигатель есть часть автомобиля, лепесток есть часть цветка, цветок есть часть растения. Ранее мы уже встречали примеры агрегирования классов – когда класс bank содержал спи- сок объектов класса client. При агрегировании классов агрегирующий класс также как и при наследовании получает возможность доступа к компонентным данным и методам агрегируемого класса (безусловно, с ограничениями, накладываемыми их областями видимости), но эти данные и методы не становятся собственностью объектов этого класса. Агрегируемый класс остается автономным объектом, что накладывает ряд ограничений на права доступа к его внутренней реализации (например, при наследовании защищенные компоненты базового класса доступны в производном, а при агрегировании – нет).

Возможность повторного использования классов важна не только и зачастую не столько из-за возможности уменьшения размера исходного текста программ. Построение систем классов с использованием механизмов наследования и агрегирования позволяет точнее описать в программе предметную область поставленной задачи, быстрее модифицировать код программы при необходимости, ускорить процесс проектирования и программирования. Любое понятие предметной области не существует изолированно, оно существует во взаимосвязи с другими понятиями, и мощность данного понятия во многом определяется наличием таких связей. Раз класс служит для представления понятий, встает вопрос, как представить взаимосвязь понятий. Понятие производного класса и поддерживающие его языковые средства служат для представления иерархических связей, иными словами, для выражения общности между классами. Например, понятия окружности и треугольника связаны между собой, так как оба они представляют еще понятие фигуры, то есть содержат более общее понятие. Чтобы представлять в программе окружности и треугольники и при этом не упускать из вида, что они являются фигурами, надо явно определять классы «окружность» и «треугольник» так, чтобы было видно, что у них есть общий класс – «фигура». Это можно сделать, объявив класс «фигура» базовым, а классы «окружность» и «треугольник»-унаследовать от него.

 

4.2. Объявление наследования классов в С++

 

Определение класса, наследуемого от некоторых, уже существующих классов, производится следующим образом:

class имя_класса: список_базовых_классов

{//определение собственных компонент

//переопределение унаследованных компонент базовых классов

};

 

Здесь список_базовых_классов – это перечень (через запятую) тех классов, от которых будет унаследован определяемый класс. Эти классы к моменту определения производного класса должны быть определены. После подобного объявления все общедоступные и защищенные компоненты базовых классов становятся компонентами производного класса без дополнительного определения.

В некоторых источниках базовый класс называют суперклассом, а производный - подчиненным классом.

Общие правила порождения классов:

1) количество базовых классов в списке порождения может быть любым;

2) один и тот же класс не может быть задан в списке порождения дважды;

3) базовый класс к моменту определения производного должен быть определен или описан;

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

Рассмотрим использование механизма наследования в С++ на конкретном примере. Определим классы А, В и С, находящиеся в отношениях наследования:

 

//Листинг 20. Пример простого наследования классов

struct A

{int a1; public: int a2;

void funcA()

};

struct B: A //наследуем класс В от А

{int b1;

public:

int b2;

void funcB()

};

struct C: B //наследуем класс С от В

{int c1; public: int c2;

void funcC()

};

 

В приведенном примере класс С унаследован от класса В, а тот в свою очередь унаследован от класса А. При наследовании различают прямые и косвенные базовые классы. Прямой базовый класс упоминается в списке баз производного класса. Косвенным базовым классом считается класс, который является базовым для одного из классов, упомянутых в списке прямых баз данного производного класса. Класс А является прямым базовым классом для В и косвенным базовым (непрямым базовым) для С. Для удобства анализа иерархических систем классов, их отображают графически с использованием направленных ациклических графов, в вершинах которых располагаются названия классов, а ребра отображают отношения классов «базовый»-«производный», причем стрелка всегда направлена от производного класса к базовому.

В результате сделанных в программе объявлений получаем структуру

 

А

 

В

 

С

 

Рисунок 3.- Пример графического отображения иерархии классов класса С, отображенную на рисунке 4.

A() int a2 funcA()

 

B() int b2 funcB()
C() int c1 int c2 funcC()

 

Рисунок 4. - Структура производного класса при наследовании

 

Класс С можно разделить на 3 части – часть, косвенно унаследованную от А, часть, унаследованную от В, а также собственные компоненты класса С.

Соответственно, структура класса В состоит из двух частей – унаследованной от А и собственных компонент класса. Объект-представитель класса C является единым блоком объектов и включает собственные данные-члены класса C, а также данные-члены классов B и A. При создании объектов класса С в памяти будет выделяться 8 байт под компонентные данные объекта (4 компонента типа int). Для объектов класса С будут доступны методы базовых классов, при создании этих объектов будут вызываться конструктор как непосредственно класса С, так и объектов его базовых классов. При этом вызов конструкторов строго регламентирован – сначала вызываются конструкторы базовых классов, затем – конструкторы агрегированных в класс объектов (объектов других классов, которые являются компонентами данного класса) и в последнюю очередь – конструктор производного. Если конструкторы базовых классов имеют формальные параметры, то при определении конструктора производного класса необходимо предусмотреть вызов конструкторов базовых классовых с необходимыми фактическими параметрами:

конструктор (список_форм_параметров): конструктор_базового_класса_1

(список_факт_параметров), …, конструктор_базового_класса_n (список_

факт _параметров)

{тело_конструктора}

При вызове деструктора производного класса вызываются деструкторы всех базовых классов, причем вызов производится в порядке, обратном вызову конструкторов.

Рассмотрим содержимое функции funcC:

 

void C:: funcC()

{a2=0; //также возможные варианты обращения A:: a2=0; B:: a2=0; B:: A:: a2=0; C:: a2=0

b2=0; //можно также B:: b2=0; C:: b2=0; Однако, нельзя A:: b2=0 c2=0; // можно также C:: c2=0; Однако, нельзя A:: c2=0; B:: c2=0 funcA(); //можно A:: funcA(); B:: funcA(); C:: funcA()

}

 

Последний пример иллюстрирует способы обращения к унаследованным компонентам класса. К ним можно обращаться как к собственным компонентам класса, а можно использовать так называемое квалифицированное имя компонента, которое в общем виде записывается следующим образом:

имя_класса :: имя_компонента

Использование квалифицированного имени удобно тогда, когда в базовом и производном классах определены одноименные компоненты. Например, если бы в классе А было определено компонентное данное c2, то в функции funcC() выражение с2=5 изменяло бы значение компонента, определенного непосредственно в классе С, а для того, чтобы изменить значение унаследованного от А компонента, необходимо было бы использовать выражение A:: c2=5 или B:: c2=5.

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

Квалифицированное имя компонента класса может быть использовано и вне его компонентных функций. Для доступа в таком случае можно использовать выражение вида

имя_объекта . имя_класса :: имя_компонента

 

//Листинг 21. Пример использования квалифицированного имени компонент класса

из

//его окружения:

main()

{ C c; c.C:: c2=0; c.B:: funcB(); c.A:: a2=2; c.C:: funcA();

}

 

Поиск компонента при обращении к нему всегда идет " снизу-вверх". Для вызова с.С:: funcA() транслятор сначала проверит наличие функции funcA в классе С и, если такой имеется, занесет адрес метода этого класса для вызова. Если в классе С нет метода с таким названием, будет рассмотрен прямой базовый класс для С (в нашем случае – класс В) и поиск метода продолжится в нем. Если метод с таким именем будет найден – его адрес будет помещен на место вызова, иначе – поиск будет продолжен на следующем уровне иерархии классов (в классе А для рассматриваемого примера).

Еще одно важное свойство базовых и производных классов иллюстрируется в листинге 22.

 

//Листинг 22. Приведение указателей производного класса к базовому

main()

{A *pta; C c; pta=& c;

pta-> funcA();

}.

 

В программе определен указатель на базовый класс A и объект производного класса С. При этом присвоение указателю pta адреса объекта c не потребовало операций приведения типа. Данный пример показывает, что указатель на базовый класс может ссылаться на объекты производных классов.

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

main()

{A *pta; C c; pta=& c;

// pta-> funcС(); Ошибка!!! Указатель pta адресует только ту часть объекта с, которая

//унаследована от класса А

((С*)pta)-> funcC(); //правильный вызов

}

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

A *mas[100];

можно заносить в него адреса объектов классов A, В, С. Более подробно организация подобных массивов будет рассматриваться в главе 4.5.

При наследовании классов компоненты базового класса становятся доступны из производного. При этом компонентам базового класса в производном присваивается некоторая область видимости, которая может и не совпадать с областью видимости, заданной для него при определении базового класса. Это иллюстрирует следующий пример.

//Листинг 23. Изменение области видимости компонент при наследовании

class A

{ … public: int x;

};

class B: A

{…

};

main()

{A a; B b;

a.x=5; //в классе А комп. данное х общедоступное

//b.x=1; ошибка!!! В классе B унаследованный от А компонент х – частный

}

 

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

1) области видимости компонента в базовом классе;

2) способа определения производного класса (через class или struct );

3) спецификации доступа, указанной в списке базовых классов при объявлении наследования.

Пояснения требует третий пункт: при объявлении списка наследования для каждого базового класса можно указать спецификатор доступа в виде уже известных нам ключевых слов public, protected, private. В этом случае список наследования может выглядеть, например, следующим образом:

class A: public B, private C, protected D

{…};

Правила, по которым изменяются области видимости компонент класса при наследовании, приведены в таблице 1.

 

Таблица 1.

Изменение области видимости компонент базового класса в производном

 

Область видимости в базовом классе Спецификатор доступа в списке порождения Область видимости в производном классе
производный класс объявлен через struct производный класс объявлен через class
public нет public private
public public public public
public protected protected protected
public private private private
protected нет public private
protected public protected protected

 

protected protected protected protected
protected private private private
private * не доступны

Следующий пример иллюстрирует приведенные в таблице правила трансформации области видимости компонент при наследовании.

 

// Листинг 24. Примеры изменения области видимости компонент при наследовании

class A

{ public:

int x;

};

class B

{protected:

int y;

};

class C

{public:

int z;

};

class D: public A, private B, C

{…

};

main()

{D d;

d.x=5; //компонент х в классе D имеет область видимости public (2-е правило

//в таблице 1)

//d.y=1; Ошибка!!! компонент y в классе D имеет область видимости private

// (8-е правило в таблице 1)

//d.z=0; Ошибка!!! компонент z в классе D имеет область видимости private

// (1-е правило в таблице 1)

}

 

Рассмотрим пример создания и использования иерархии классов с использованием механизма наследования.

 

//Листинг 25. Программа “база данных по учету студентов”, использующая механизм

//наследования

#include < iostream.h>

#include < string.h>

class Subject //класс, описывающий свойства некоторого субъекта

{ protected:

char name[20]; //имя субъекта

int age; //возраст char adress[30]; //адрес public:

void Read(); //функция ввода информации о субъекте с клавиатуры

void Write(); //функция вывода информации о субъекте на экран

};

class Student: public Subject //класс, описывающий свойства студента

{ char group[7]; //название группы, в которой учится студент

char numb[8]; //номер его зачетной книжки

int balls[10]; //оценки, полученные на экзамене static int n; //количество экзаменов в сессию protected:

float rait; //рейтинг студента (среднее по баллам, полученным на экзаменах)

public:

void Exam(); //функция ввода баллов по предметам

void CalcRait(); //функция вычисления рейтинга студента

void ReadSt(); // функция ввода информации о студенте с клавиатуры

void WriteSt(); // функция вывода информации о студенте на экран

};

class DayStud: public Student //класс, описывающий свойства студента дневной

// формы обучения

{int stip; //стипендия студента

public:

void CalcStip(); //функция вычисления стипендии студента

void WriteSt(); //переопределенная функция вывода информации о студенте

};

// Определение методов классов

void Subject:: Read()

{ cout< < " Введите информацию\n Имя";

cin> > name; cout< < " \n Возраст"; cin> > age; cout< < " \nАдрес"; cin> > adress;

}

void Subject:: Write()

{ cout< < " Имя " < < name< < " Возраст " < < age< < " Адрес " < < adress; }

int Student:: n=4;

void Student:: ReadSt()

{ Read();

cout< < " \nНомер зач.книжки";

cin> > numb; cout< < " \nГруппа"; cin> > group;

}

void Student:: WriteSt()

{ Write();

cout< < " Номер зач.книжки " < < numb< < " Группа " < < group< < " Рейтинг " < < rait< < " \n";

}

void Student:: CalcRait()

{ rait=0;

for(int i=0; i< n; i++)

rait+=balls[i];

rait/=n;

}

void Student:: Exam()

{ for (int j=0; j< n; j++)

{ cout< < " \nПредмет N" < < j+1;

cin> > balls[j];

}}

void DayStud:: CalcStip()

{if (rait> =90) stip=300; else if (rait> =76) stip=200;

else stip=0;

}

void DayStud:: WriteSt()

{ Student:: WriteSt();

cout< < " Стипендия" < < stip;

}

//пример использования определенных выше классов

main()

{ const int m=10; //будем работать с 10-ю студентами

int i;

DayStud gr[m];

for(i=0; i< m; i++)

gr[i].ReadSt(); //вводим информацию о каждом студенте

for(i=0; i< m; i++)

{ cout< < " Экзамены" < < i+1< < " студента";

gr[i].Exam(); //проводим экзамены (вводим информацию о баллах, полученных

//каждым студентом на экзаменах

}

for(i=0; i< m; i++)

gr[i].CalcRait(); //вычисляем рейтинг каждого студента

for(i=0; i< m; i++)

gr[i].CalcStip(); //вычисляем стипендию каждого из студентов

for(i=0; i< m; i++)

gr[i].WriteSt(); //выводим информацию о каждом студенте на экран

}

В приведенном выше примере определены три класса. Класс Subject является корнем всей системы классов, в нем объединены свойства и методы, описывающие «субъекта», то есть свойства, присущие каждому человеку: имя, возраст, адрес, и методы, позволяющие обрабатывать эту информацию (вводить с клавиатуры, выводить на экран).

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

Третий класс называется DayStud является конкретизацией класса Student в плане описания свойств студента дневного отделения. В частности, для студента дневного отделения определено компонентное данное stip (стипендия), значение которого вычисляется в компонентной функции этого же класса CalcStip в зависимости от текущего рейтинга студента. Схема иерархии классов программы изображена на рис.5.

 

Subject

 

Student

 

DayStud

Рисунок 5.- Иерархия классов программы ”база данных по учету студентов”.

 

Может показаться несущественным отличие класса DayStud от класса Student, и возникнуть желание объединить их в одном классе. Однако, предложенная схема иерархии классов позволяет легко модифицировать программу, добавлять в нее новые классы, отличающиеся от уже определенных небольшими деталями реализации без значительных усилий со стороны программиста. Так, например, можно определить класс EvnStud , описывающий студента-вечерника просто унас ледовав его от класса Student, так как все компоненты этого класса в полной мере относятся и к студентам вечерней формы обучения. При этом в класс EvnStud можно добавить некоторые компонентные данные, присущие только студентам - вечерникам (например, место постоянной работы). Можно пойти дальше и определить класс Teacher, описывающий преподавателя, и опять этот класс может появиться не на «ровном месте», а быть унаследован от класса Subject, так как все перечисленные для «субъекта» свойства и методы имеют отношение и к преподавателям. Возможная схема иерархии спроектированной нами (хоть и поверхностно) информационной системы ВУЗа приведена на рис. 6.

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

 

 

Subject

 

Student

 


Teacher EvnStud DayStud

Рисунок 6. -Возможная иерархия классов для программы ”информационная система ВУЗа ”.


 

 

При проектировании систем классов, подобных приведенной на рис.6, необходимо помнить о природе отношения наследования. Для всех приведенных классов это отношение соблюдается – мы можем сказать, что студент и преподаватель

– это субъекты, а студент-дневник и студент-вечерник являются студентами, то есть описываемые классами сущности вступают в отношения обобщения- конкретизации. Однако, если мы захотим добавить в программу класс Group, описывающий учебную группу, то в отношение наследования с классом Student такой класс вступить не сможет: мы не можем сказать, что группа-это студент. Класс Student правильно будет агрегировать в класс Group (напомним – для агрегирования отношения классов проверяются словосочетанием «состоит из»: группа состоит из студентов). Отношения классов Group и Student похожи на отношения классов client и bank, рассмотренных в листингах 16 и 17, и правильным будет включить в класс Group массив или динамический список объектов класса Student.

 

Множественное наследование

 

 

Как уже отмечалось, в С++ производный класс может быть порождён из любого числа непосредственных базовых классов. Наличие у производного класса более чем одного непосредственного базового класса называется множественным наследием. Синтаксически множественное наследование отличается от единичного наследования списком порождения, состоящим более чем из одного класса.

//Листинг26. Пример множественного наследования

class A

{int a1; public: int a2;

void funcA()

};

class B

{int b1; public: int b2;

void funcB()

};

class C: public A, public B //наследуем класс С от A и B

{int c1;

public:

int c2;

void funcC()

};

Схема иерархии классов, определенных в последнем примере, изображена на рис.7

 

A B

 

 

С

 

Рисунок 7. - Множественное наследование классов

 

Структура объекта класса будет аналогична изображенной на рис.4. Однако, если в списке базовых классов поменять местами объявление классов А и В, то есть определить класс С следующим образом:

class C: public B, public A

{ … };

то порядок следования компонент в объекте класса С изменится – в младших адресах будут располагаться компоненты объекта класса В, затем – объекта класса А.

При множественном наследовании один и тот же класс не может быть дважды указан как прямой базовый, однако, косвенным базовым классом один и тот же класс может быть и более одного раза.

class A {public: int x; void funcA(); …};

class B: public A {…};

class D: public A{…};

class C: public B, public D {…};

 

А А

 

В D

 

С

 

Рисунок 8. - Множественное наследование с дублированием косвенного базового класса

 

Дублирование косвенного базового класса приводит к включению в производный класс нескольких объектов базового класса. Для класса С в последнем примере это означает, что компонентное данное x будет существовать в объектах данного класса в двух экземплярах – один унаследован через класс В, другой – через класс D. Структура объекта класса С изображена на рис. 9.

 

 

объект класса А, унаследованный через класс В int x
объект класса В
объект класса А, унаследованный через класс D int x
объект класса D
объект класса С

Рисунок 9. - Структура производного класса при множественном наследовании с дублированием косвенного базового класса.

 

При множественном наследовании зачастую возникает проблема неоднозначности при доступе к дублирующимся компонентам класса: неясно, какой из одноименных компонент изменится при следующем обращении

main()

{ C c;

c.x=6; // Ошибка!!!

}

 

Попытка доступа к члену данных x для объекта с приводит к ошибке транслятора “Member is ambiguous A:: x and A:: x”. Эта ошибка означает, что транслятор не может определить, какому из двух компонент x класса необходимо присвоить новое значение. Неразрешимыми именами для транслятора будут также следующие с.C:: x и c.A:: x. Решением проблемы является использование квалифицированных имен компонент с использованием имен классов B и D. Для транслятора однозначно различаются следующие имена компонент: с.B:: x (компонента, унаследованная через класс В) и c.D:: x (компонента, унаследованная через класс D).

Рассмотрим пример программы, реализующей множественное наследование. В программе реализованы класс Window, описывающий окно в текстовом режиме, и класс Text, описывающий буфер для хранения текстовой информации, а на их основе определен класс WinText, описывающий окно в текстовом режиме с возможностью отображения в нем текста.

//Листинг 27. Программа, использующая множественное наследование классов

#include< stdio.h>

#include< string.h>

#include < conio.h>

class Window //класс «окно в текстовом режиме»

{ protected:

int x, y; //координаты левого верхнего угла окна

int dx, dy; //размеры окна

int color, backcolor; //основной и фоновый цвета окна

public:

Window(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:

int n, UsedN; //n-количество строк в буфере, UsedN – количество реально используемых

//строк буфера

char **str; //указатель на начало буфера в памяти

public:

Text(int ); //конструктор

~Text(); //деструктор

void Read(char *filename); //функция чтения информации из файла в буфер

void Write(); //функция вывода информации из буфера на экран

};

Text:: Text(int k) //конструктор динамически выделяет память под k строк…

{ n=k; UsedN=0; str=new char*[n]; for(int i=0; i< n; i++)

str[i]=new char[80]; //…в каждой строке 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:

WinText(int numb=25, int xb=10, int yb=10, int a=60, int b=20): //конструктор

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();

WinText w(50, 2, 2, 10, 15); //определяем объект – окно с буфером на 50 строк

// размером 10 на 15 с координатами верхнего левого угла 2, 2 w.Read(" lect9.cpp" ); //считываем в буфер объекта содержимое файла lect9.cpp w.draw(); //отображаем содержимое буфера в окне


while(w.Control()! =27); //пока не нажата клавиша ESC, просматриваем текст в окне

}

 

Виртуальные классы

 

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

 

//Листинг 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 Move(char x, int y) //функция, реализующая ход ладьи на поле [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 Move(char x, int y) //функция, реализующая ход слона на поле [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, описывающий поведение ферзя. Каждый, кто знаком с правилами шахмат, знает, что ферзь объединяет в себе свойства ладьи и слона (в том смысле, что может ходить как по диагонали, как слон, так и повертикали и горизонтали, как ладья). Поэтому класс Queen объявлен потомком двух классов: Castle и Bishop. Таким образом, имеется иерархия классов, изображенная на рис.10.

 

 


Figure Figure

 

Bishop Castle

 

Queen

 

Рисунок 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.

 

Figure

 

Вishop Castle

 

Queen

 

Рисунок 11. - При использовании виртуальных классов схема иерархии классов принимает ромбовидную форму

 

При использовании виртуальных классов необходимо обратить внимание наособенность вызова конструкторов базовых классов. Конструктор класса Queen

был определен следующим образом:

Queen(char x, int y, int z): Castle (x, y, z), Bishop (x, y, z)

{}

 


Поделиться:



Популярное:

  1. III. Практическое использование водорослей.
  2. IV. Виды бланков документов, их изготовление и использование
  3. VI. Переведите на английский язык предложения с использованием форм простого будущего времени, либо других способов выражения будущего.
  4. Алгоритм выполнения чертежей с использованием
  5. Билет 21. Связи: ассоциация, зависимость, наследование, конкретизация
  6. Более сложное использование вторичного подкрепления
  7. Вопрос 117. Наследование по закону и завещанию. Право на обязательную долю в наследстве.
  8. Вопрос 40. Использование индексов в экономико-статистических расчетах. Индекс потребительских цен. Индекс цен производителей промышленной продукции.
  9. Вопрос Использование наступательных стратегий для сохранения конкурентного преимущества
  10. Выполнение задачи с использованием СУБД ACCESS.
  11. ГИДРАВЛИЧЕСКИЙ РАСЧЕТ ВОДОПРОВОДНОЙ СЕТИ С ИСПОЛЬЗОВАНИЕМ ЭВМ
  12. Глава 23. Использование специальных познаний


Последнее изменение этой страницы: 2016-07-14; Просмотров: 624; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.355 с.)
Главная | Случайная страница | Обратная связь