- •Д. Н. Лясин, с. Г. Саньков
- •Оглавление
- •1.Обзор стилей программирования
- •1.1. Процедурное программирование
- •Структурное программирование
- •Функциональное программирование
- •Логическое программирование
- •1.5. Объектно-ориентированное программирование
- •Основные принципы объектно-ориентированного программирования
- •3.1. Объявление классов и объектов
- •3.2. Конструкторы и деструкторы
- •3.3. Область видимости компонент класса
- •3.4. Определение компонентных функций класса
- •3.5. Статические компоненты классов
- •Дружественные функции
- •3.7. Перегрузка операций
- •4. Наследование классов
- •4.1. Повторное использование классов: наследование и агрегирование
- •4.3. Множественное наследование
- •4.4. Виртуальные классы
- •4.5. Виртуальные функции. Полиморфизм
- •4.6. Абстрактные классы
- •Список литературы
4.3. Множественное наследование
Как уже отмечалось, в С++ производный класс может быть порождён из любого числа непосредственных базовых классов. Наличие у производного класса более чем одного непосредственного базового класса называется множественным наследием. Синтаксически множественное наследование отличается от единичного наследования списком порождения, состоящим более чем из одного класса.
//Листинг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
Структура объекта класса будет аналогична изображенной на рис.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 {…};
Д ублирование косвенного базового класса приводит к включению в производный класс нескольких объектов базового класса. Для класса С в последнем примере это означает, что компонентное данное 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, просматриваем текст в окне
}