- •Д. Н. Лясин, с. Г. Саньков
- •Оглавление
- •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.6. Абстрактные классы
При проектировании иерархических систем классов программисты обычно стремятся выделить некоторые обобщающие свойства некоторой группы описываемых сущностей в базовый класс (в предыдущих главах такими классами были Subject, Figure, Shape). Зачастую созданные по такому принципу классы носят абстрактный смысл, не описывая какой-то реально существующий объект, а лишь являясь некоторой основой, на которой строятся действительно необходимые классы. Это приводит к некоторому несоответствию спроектированной системы классов предметной области: оказывается, что программист может определить в программе объекты, реально к предметной области не имеющие никакого отношения. Для того, чтобы предотвратить возможность использования объектов таких классов в программе, их объявляют абстрактными. Абстрактным называется класс, в котором определена хотя бы одна чистая виртуальная функция. Чистая виртуальная функция определяется следующим образом:
virtual тип имя_функции( список_формальных_параметров )=0;
Чистая виртуальная функция не имеет реализации, ее нельзя вызвать в программе, она служит лишь как основа для дальнейшего полиморфного переопределения в производном классе. Соответственно, абстрактный класс не может иметь объектов, так как в нем не определены операции над объектами (или, по крайней мере, хотя бы одна операция, реализуемая чистой виртуальной функцией).
Рассмотрим пример.
//Листинг 32. Использование абстрактных классов
#include <stdio.h>
#include <iostream.h>
#include <string.h>
#include <conio.h>
class File //абстрактный класс
{ protected:
char **str; //адрес буфера для временного хранения информации из файла
char Name[30]; //имя файла
int n; //количество строк в буфере
virtual int ReadFile()=0; //чистая виртуальная функция чтения информации из файла в буфер
public:
File(char*,int);
~File();
void display(); //метод, отображающий содержимое буфера на экране
};
File::File(char * FileName,int k) //конструктор
{ strcpy(Name,FileName);
n=k;
str=new char*[n]; //выделяем память под буфер
for(int i=0;i<n;i++)
str[i]=new char[80];
}
File::~File() //деструктор
{for(int i=0;i<n;i++)
delete []str[i]; //освобождаем память
delete [] str;
}
void File::display()
{ clrscr();
int k=ReadFile(); //считываем информацию из файла в буфер
for(int i=0;i<k;i++)
cout<<str[i]; // построчно выводим содержимое буфера на экран
getch();
}
struct info //тип информации, хранящейся в файле
{char name[20];
char numb[10];
float value;
};
class InfoFile:public File //класс «файл с базой данных»
{ int ReadFile(); //переопределяем функцию чтения информации из файла
public:
InfoFile(char* st,int k):File(st,k) {} //конструктор вызывает конструктор базового класса
void WriteFile(int ); //метод записи информации в файл
};
int InfoFile::ReadFile()
{ FILE *fp;
int i=0;
info x;
if((fp=fopen(Name,"r"))!=NULL) //открываем файл
{ while(!feof(fp)) //пока не конец файла
{ fread(&x,sizeof(info),1,fp); //считываем очередную запись из файла
sprintf(str[i],”Запись N %d %s %s %f\n",i+1,x.name,x.numb,x.value);
//заносим очередную запись в буфер файла
i++;
}
fclose(fp);
return i-1; //функция возвращает количество считанных из файла записей
}
return 0;}
void InfoFile::WriteFile(int k)
{ info x;
FILE *fp;
if((fp=fopen(Name,"w+"))!=NULL)
{ for(int i=0;i<k;i++)
{cout<<”Введите "<<i+1<<" -ю запись";
cin>>x.name>>x.numb>>x.value; //вводим с клавиатуры очередную запись
fwrite(&x,sizeof(info),1,fp); //записываем ее в файл
}
fclose(fp);
}}
class HelpFile:public File //класс «файл с помощью»
{ int ReadFile(); //переопределяем функцию чтения информации из файла
public:
HelpFile():File("help.dat",25){} //имя файла и его размер фиксированы
};
int HelpFile::ReadFile()
{ int i=0;
FILE *fp;
if((fp=fopen(Name,"r"))!=NULL)
{ while(!feof(fp))
{fgets(str[i],80,fp); //считываем содержимое файла в буфер
i++;
}
fclose(fp);
return i-1;
}
return 0;}
main()
{ HelpFile hp; //объект класса «файл с помощью»
hp.display(); //выводим содержимое файла на экран
InfoFile If("info",40); //создаем объект класса «файл с информацией»
If.WriteFile(3); //записываем в файл 3 записи
If.display(); //выводим содержимое файла на экран
}
Программа, приведенная в листинге 32, работает с файлами двух типов – информационным файлом (класс InfoFile), предназначенным для хранения простейшей базы данных – нескольких структур типа info, а также файлом помощи (класс HelpFile), который хранит текстовую информацию, предположительно – справку о самой программе. Общая сущность двух этих типов файлов, выражающаяся в имени файла Name, буфере str для временного хранения информации из файла, размере используемого файлом буфера n, а также метода display вывода считанной из файла информации на экран, выделена в родительский класс (класс File). Схема иерархии классов программы приведена на рис.14.
Метод display класса File считывает информацию из файла методом ReadFile в буфер и построчно выводит ее на экран. Однако, метод ReadFile в самом классе File не может быть полноценно определен, поскольку этот класс в терминах предметной области является абстрактной основой для двух других классов (InfoFile и HelpFile) и для него не известен, например, тип хранящейся в файле информации. Метод ReadFile определен в классах InfoFile и HelpFile , причем в первом данный метод считывает из файла информацию в виде экземпляров структуры info, преобразует ее в текстовую форму и записывает в буфер, а во втором - информация непосредственно считывается в виде текстовых строк, которые записываются в буфер str. Таким образом, метод ReadFile не надо определять в классе File по логике представления предметной области, однако обязательно необходимо определить по правилам синтаксиса языка С++ (так как метод display использует этот метод). При этом необходимо обеспечить полиморфное поведение метода ReadFile для того, чтобы при вызове метода display объектом класса InfoFile в теле метода display была вызвана реализация метода ReadFile для соответствующего класса (аналогичная ситуация рассматривалась в листинге 31). Поэтому метод ReadFile объявлен в классе File как чистая виртуальная функция:
virtual int ReadFile()=0;
Таким образом, класс File является абстрактным классом, для которого запрещено создание объектов, то есть ошибкой будет такое объявление:
File MyFile(“file.txt”,50);
Класс File может быть только основой для дальнейшего наследования другими классами. Подводя итог, можно сказать, что абстрактные классы используются для спецификации интерфейсов операций (методы, реализующие эти операции впоследствии определяются в производных классах абстрактного класса). Абстрактные классы удобны на фазе анализа требований к системе, так как они позволяют выявить аналогию в различных, на первый взгляд, операциях, определенных в анализируемой системе.