Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Основы объектно-ориентированного программирования задач на графах. Учебное пособие
.pdf
6.9. Пример выполнения программы для бинарных функций-операций
61
Окно начала выполнения программы для графов (см. рис. 12,14) по-
казано на рис.18, а окончания на рис. 19.
1
1
0
1
1
0
0
1
1
1 2 3
1
2
3
1 2 3
1 1 0
0
0
0
0
1
1
1 2 3
1
2
3 1 2
3
0
0
0
1
1
0
0
0
0
1 2 3
1
2
3 1 2
3
Рис. 15. Матрица смежности графа G2
Рис. 14. Граф G2
Рис. 12. Граф G1 Рис. 13. Матрифа смежности графа G
1
Рис. 16. Объединение графов G1 и G
2
Рис. 17. Матрица смежности

6. Задачи на графах и перегрузка операций
62
6.10. Контрольные вопросы
Рис. 18. Начала выполнения программы для графов (см. рис. 12,14)
Рис. 19. Окончание выполнения программы для графов (см. рис. 12,14)

Вопросы для самокононтроля
63
Вопросы для самокононтроля
1. Каково отличие перегрузки функций от перегрузки операций?
2. Сколько аргументов должна содержать функция класса, чтобы для нее
можно было использовать перегрузку операций?
3. Можно ли в имени функции класса при перегрузке операций для объ-
ектов классов вместо слова operator использовать другое слово?
4. Можно ли для виртуальных функций классов использовать перегрузку
операций?
5. Можно ли для конструкторов классов использовать перегрузку опера-
ций?
6. Выдаст компилятор или нет сообщение об ошибке при следующем
коде:
class A
{ public: int b;
void operator!(){cin>>b;}
void operator-(){cout<<b;}
};
main()
{ A a1;
a1.operator!();
-a1;
}
7. Выдаст компилятор или нет сообщение об ошибке при следующем
коде:
class A
{ public: int b, c;
void operator+(int b1, int c1)
{b=b1; c=c1;}
void operator-(){cout<<b<<c;}
};
main()
{ A a1;
a1.operator+(5,7);
-a1;
}

6. Задачи на графах и перегрузка операций
64
8. Выдаст компилятор или нет сообщение об ошибке при следующем
коде:
class A
{ public: int b, c;
void operator+(int b1)
{b=b1; c=7;}
void operator-(){cout<<b<<c;}
};
main()
{ A *a1=new A;
a1+5;
-a1;
delete a1;
}
9. Как по умолчанию для объектов любого класса перегружается
встроенная операция присваивания (=)?

65
7. ИЕРАРХИИ КЛАССОВ ДЛЯ РЕШЕНИЯ ЗАДАЧ НА ГРАФАХ
7.1. Структура и назначение классов
В предыдущих разделах была разработана простая программа для работы с графами, в которой использовалось только простое наследование.
Разработанные классы предназначены только для графов, заданных матрицей смежности. Но, как уже отмечалось, графы могут быть заданы матрицей инцидентности, а также в списочной форме. Кроме того, графы могут
быть ориентированными, неориентированными, симметрическими, с кратными ребрами, с весами на ребрах, с весами на вершинах и т.д.
Многообразие типов графов и их представлений требует создания
сложной иерархии классов. В данном учебном пособии не ставится задача
разработки полной иерархии классов. Наша цель – получить представление
о подходах к разработке иерархии классов для решения задач на графах.
Поэтому в данном разделе создадим простую иерархию классов, основанную на различных способах представления графов. При этом будут рассматриваться только неориентированные графы без петель и кратных ребер. Как уже отмечалось, графы можно задать матрицей смежности, матрицей инцидентности или в списочной форме. Ограничимся заданием графов
матрицей смежности и в списочной форме. Простая иерархия классов
представлена на рис. 20.
Иерархия классов (см. рис. 20) состоит из трех классов: базового
класса Graph и производных от него классов Matr и List. При создании
иерархии классов базовый класс должен описывать данные и функции, об-
Рис. 20. Иерархия классов
Graph
List
Matr

7. Иерархии классов для решения задач на графах
66
щие для всех производных от него классов, а производные классы дожны
дополнять данные и функции базовых классов. Поэтому класс Graph
должен отражать операции и параметры, общие для решения всех графовых задач, а классы Matr и List должны дополнять его даными и функциями, необходимыми для работы с графами, заданными соответственно
матрицей смежности и списком.
Для всех типов графов общим данным является число вершин графа.
Поэтому в классе Graph определим личное данное – целую переменную
int N;
соответствующую количеству вершин графа.
Какие действия необходимо будет выполнять с числом вершин?
Прежде всего, необходимо ввести это данное. Для этого можно определить
функцию
void input_N(){cin>>N;},
которая не возвращает значение и вводит значение числа вершин с клавиатуры.
Может потребоваться вывести значение числа вершин на экран. Для
этого введем в класс Graph функцию:
void output_N(){cout<<N;}.
Число вершин графа является личным данным класса. Поэтому, если
потребуется воспользоваться непосредственно значением переменной N, то
целесообразно предусмотреть в классе Graph функцию, которая возвращает значение числа вершин графа:
int return_N(){return N;}.
Если какие-то операции с числом вершин графа мы пока упустили, то
всегда есть возможность вернуться к классу Graph и добавить в его описание новую функцию.

7.2. Абстрактные классы и чистые виртуальные функции
67
Какие операции над графами являются общими для всех типов графов,
и их следует включить в описание класса Graph?
Конечно, необходимо будет ввести с клавиатуры информацию о
смежности вершин графа, поэтому объявим функцию input_graph()
класса Graph.
Объявить функцию класса – это означает разместить в теле класса
прототип этой функции, а определить функцию – это значит написать операторы тела функции. А как можно написать функцию ввода графа, если,
создавая класс Graph, описывая общие операции и параметры для всех
типов графов, мы пока не знаем, в какой форме будет представлен граф,
какая будет использоваться структура данных? Можно только назвать операцию, дать ей имя, объявить ее, но реализовать (определить) ее пока не
получится. Необходимо прежде уточнить, какой структурой данных будет
задаваться граф, например, табличной или списочной.
Но объявить функцию в С++ даже, если мы не знаем, как ее реализовать, мы можем. Для этого в С++ предусмотрен аппарат абстрактных классов и чистых виртуальных функций.
7.2. Абстрактные классы и чистые виртуальные функции
Разработка иерархий классов при объектно-ориентированном проектировании основано на классификации понятий. Понятия образуют многоуровневые (иерархические) схемы. Понятия более низких уровней дополняют, конкретизируют понятия более высоких уровней. На высоких уровнях классификации появляются абстрактные понятия, для которых невозможно ни показать, ни создать соответствующий этому понятию объект.
Так, в реальном мире нельзя показать объект для понятия «Дом». Обязательно придется уточнять – какой именно «Дом»: каменный или деревянный, одноэтажный или многоэтажный и т.д.
Граф – это абстрактная модель. Но даже в абстрактном мире понятие
«Граф» в «чистом виде» тоже не существует. Понятие «Граф» порождает
при классификации подклассы неориентированных, ориентированных,
симметрических графов и т.д. Граф обязательно какого-то вида, например,
ориентированный или неориентированный. Граф обязательно представлен
в определенной форме, например, табличной или списочной. «Граф» –
абстрактное понятие, самое общее понятие в классификации графов.

7. Иерархии классов для решения задач на графах
68
Объектно-ориентированное программирование – это объектно-
ориентированное проектирование программы. Создавая иерархию классов
для решения задач на графах, мы, по существу, занимаемся классификацией графов. При разработке нашего класса Graph мы пытались описать
класс, соответствующий понятию «Граф». Но пока не будет уточнена в
производных классах Matr и List форма представления графа, определить функции для выполнения операций над графами мы не сможем, но
можем их объявить.
Объявить функцию ввода графа в классе Graph можно, как чистую
виртуальную функцию:
virtual void input_graph() = 0;
Чистая виртуальная функция – это функция, тело которой не опреде-
ленно в классе, в котором она объявлена. Пристывка (=0) после имени
функции является признаком того, что виртуальная функция
input_graph() является чистой виртуальной функцией. Чистая вирту-
альная функция обязательно должна быть определена в производных классах. Поэтому функция input_graph() необходимо определить в производных от класса Graph классах.
Класс, который содержит хотя бы одну чистую виртуальную функ-
цию, является абстрактным классом. Класс Graph автоматически стано-
вится абстрактным классом после того, как в нем обявлена функция
input_graph().
Так как вирутальные функции не имеют тела, то для абстрактного
класса нельзя создать объект. Абстрактные классы можно наследовать.
Если в классе, производном от абстрактного класса, не будут опреде-
лены тела всех чистых виртуальных функций, то такой производный класс
будет также абстрактным. Например, если в классе Matr не определить тело функции input_graph(), то класс Matr тоже станет абстрактным, и
для него нельзя будет создать объект. Только для класса, в котором известны тела всех чистых виртуальных функций, можно создать объект.
Использование абстрактных классов и чистых виртуальных функций
позволяет создавать класс, проектировать иерархии классов, использовать
имена виртуальных функций, но нужно при этом помнить, что чистые витруальные функции должны быть определены в производных классах.

7.3. Определение чистых виртуальных функций в классе Маtr
69
Продолжим формирование класса Graph. Аналогично функции in-
put_graph() можно объявить в классе Graph и другие чистые вирту-
альные функции, соответствующие операциям на графах.
Объявим функцию
virtual void output_graph () = 0;
для операции вывода графа на экран и функцию
virtual int adjacent (int,int) = 0;
для операции проверки смежности двух вершин.
Конкретная реализация этих функций зависит от структуры данных и
может быть выполнена только в последующих в иерархии классах.
Получим следующее описание абстрактного класса Graph:
class Graph
{
protected: int N;
public: void output_N(){cout<<N;}
int return_N(){return N;}
virtual void input_graph() = 0;
virtual void output_graph() = 0;
virtual int adjacent(int,int)= 0;
Graph(int n1){N=n1;}
};
Для абстрактных классов можно создавать конструкторы и деструк-
торы. В описании класса функция ввода числа вершин отсутствует. Ввод
числа вершин выполнен в конструкторе класса.
7.3. Определение чистых виртуальных функций в классе Маtr
Перейдем теперь к созданию производных классов Matr и List. В
этих классах необходимо определить чистые виртуальные функции, объявленные в классе Graph, а также определить данные для представления
матрицы смежности и списка.

7. Иерархии классов для решения задач на графах
70
Сокращенные описания этих классов с одной виртуальной функцией
ввода графа input_graph() приведены на рис. 21.
Класс Matr предназначен для работы с графами, заданными матрицей смежности, а класс List – в списочной форме. В классе Matr дополним базовый класс Graph и объявим данное класса – указатель на двумерный массив:
bool **matr;
для задания матрицы смежности графа, как это было сделано в разделе 3.
В классе Matr можно определить чистую виртуальную функцию
input_graph(), так как форма представления графа уже определена –
матрица смежности. Для этого в классе Matr необходимо определить конкретную реализацию виртуальной функции input_graph().
Добавьте в описание класса Matr прототип этой функции:
void input_graph();
Рис. 21. Сокращенные описания классов
class Graph
{ protected: int N;
public:
virtual void input_graph()=0;
};
Graph
List
Matr
class Matr
{
protected: bool **matr;
public:
void input_graph();
};
class List
{
protected: int **list;
public: void input_graph();
};
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
