Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Основы объектно-ориентированного программирования задач на графах. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
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(); };
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]