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

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

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
5.2. Переопределение функций
41
В классе могут быть несколько виртуальных функций. Необходимо
отметить, что при создании объектов классов, в которых определены вир­туальные функции, код этих виртуальных функций класса в объект класса не войдет. Для каждого из классов, члены которых образуют объект, со­здаются таблицы виртуальных функций, которые будут содержать ссылки на виртуальные функции этого класса. Выбор соответствующей виртуаль­ной функций определяется при выполнении программы, а не при компиля­ции, как это производится в случае обычной перегрузки функций.
5.2. Переопределение функций
В нашем примере виртуальная функция объявлена в классе Graph
следующим образом:
virtual void adjacency(int,int);
Функция adjacency(int,int) определена вне класса Graph. Изменим предыдущий пример. Переопределим в производном клас-
се My_Class функцию adjacency() так, чтобы она определяла верши­ны графа, смежные одновременно каждой из заданных вершин графа (пе­ресечение множеств вершин, смежных каждой из двух заданных вершин графа).
Для этого добавим в описание класса My_Class объявление пе-
реопределяемой виртуальной функции adjacency() из класса Graph:
class My_Class : public Graph { public: int adjacent_nodes(int, int); My_Class(int a ):Graph(a){}
void adjacency(int,int);
};
Затем напишем текст переопределямой виртуальной функции вне класса
My_Class:
void My_Class::adjacency(int x11, int x22) { bool *mas = new bool[N];
5. Полиморфизм при разработке классов для решения задач на графах
42
for(int i=0; i<N; i++) if(Matr[x11-1][i] && Matr[x22-1][i]) mas[i]=1; else mas[i]=0; mas[x11-1]=0; mas[x22-1]=0; cout<<"\n Tops "<<x11<<" AND "<<x22<<" adjacency tops : "; for(int i=0; i<N; i++) if(mas[i]) cout<< i+1<<” ”; delete[] mas; }
Для того чтобы написать такую функцию, достаточно в функции ad­jacency() класса Graph в условии оператора if поменять операцию отношения «ИЛИ» (||) на операцию отношения «И» (&&).
5.3. Вызов виртуальных функций
Добавлять в главную функцию вызов перегруженной виртуальной функции adjacency() класса My_Class нет необходимости, так как обращение, которое уже есть в главной функции:
graph->adjacency(x1,x2);
и вызовет эту функцию класса My_Class. В результате ее вызова и будет выведен на экран список вершины графа, смежных одновременно каждой из двух заданных вершин графа.
Почему будет вызвана функция adjacency() класса My_Class, а не функция adjacency() класса Graph?
Это является особенностью вызова и использования виртуальных функций. Вызывается виртуальная функция самого последнего (нижнего) класса в иерархии классов. В нашем примере такой функцией будет функ­ция adjacency(x1,x2) класса My_Class, который является произ­водным от класса Graph. Класс My_Class – самый нижний класс в иерархии классов Graph и My_Class. Поэтому и будет вызвана функция
adjacency() класса My_Clas.
5.4. Пример выполнения программы
43
В этом и заключается смысл перегрузки виртуальных функций. Хо-
тите изменять поведение вашей функции – объявляйте ее вирутальной, а затем перегружайте ее в производном классе.
А как быть, если требуется выполнить одну из виртуальных функций
из базового класса, например, функцию adjacency() класса Graph? В этом случае просто добавьте перед именем функции имя класса, в котором эта функция объявлена, и операцию разрешения области видимости (::):
graph->Graph::adjacency(x1,x2);
Полный текст программы приведен в прил. 3.
5.4. Пример выполнения программы
На рис. 10 приведен результат работы программы для графа, приве­денного на рис. 6, на котором видно, что вершин, одновременно смежных вершине х1 и вершине x2, нет.
Рис. 10. Результат работы программы для графа, приведенного на рис. 6
5. Полиморфизм при разработке классов для решения задач на графах
44
Вопросы для самоконтроля
1. Каково смысловое значение понятия «полиморфизм"?
2. Каково назначение виртуальной функции базового класса?
3. Какое практическое значение имеет переопределение в производных
класса виртуальных функций базовых классов?
4. Что такое статическое и динамическое связывание?
5. В чем отличие аппарата перегрузки функций от аппарата перегрузки
виртуальных функций?
6. Могут ли перегруженные функции одновременно быть и виртуальны-
ми?
7. Будет ли объект класса, содержащего виртуальную функцию, включать
в себя машинный код этой функции?
8. Каково назначение таблицы чистых виртуальных функций класса?
9. Как можно при необходимости вызвать виртуальную функцию базового
класса?
10. Чем отличаются заголовки перегруженных функций от заголовков вир-
туальных функций классов?
11. Можно ли виртуальную функцию переопределить в том же классе, в
котором она определена?
45
6. ЗАДАЧИ НА ГРАФАХ И ПЕРЕГРУЗКА ОПЕРАЦИЙ
6.1. Функции-операции
Практически ни один разрабатываемый класс не обходится без пере­грузки операций. Перегрузка операций – это использование встроенных операций язы­ка С++ для объектов классов. Встроенные операции – это операции: +,
-, *, >>, [], !, <<, new, sizeof и т.д. Эти операции используются в С++ при составлении выражений из встроенных типов данных. Встроенные типы данных – это известные вам типы данных: int, float, char, bool и т.д.
Перегрузить операцию для объектов класса означает, что необходимо создать специальную функцию-операцию. Имя функции-операции форми­руется по определенному правилу. Оно должно начинаться с ключевого слова operator, за которым должен следовать знак перегружаемой опера­ции.
Например, если перегружаем встроенную операцию сложения (+), то имя функции-операции должно начинаться с ключевого слова operator, после которого без пробела должен стоять выбранный знак (+):
operator+().
При вызове этой функции-операции имя ее опускается, и вместо него используется только знак перегруженной операции. Это значительно упрощает запись обращения к функциям класса. Выбор знака операции произвольный и связан только с вашими ассоциациями с действиями, кото­рые должна выполнять функция-операция, и выбранным знаком.
Встроенные операции С++ в зависимости от количества операндов бывают унарные и бинарные. Для унарной операции достаточно одного операнда, а для бинарных операндов требуется два операнда. Например, операция сложения (+) – это бинарная операция, а логическаая операция отрицания «НЕ» (!) – унарная.
Аналогично для объектов одного класса встроенные операции могут перегружаться, как унарные или как бинарные операции. При этом совсем
6. Задачи на графах и перегрузка операций
46
не обязательно, чтобы унарная встроенная операция перегружалась только как унарная операция для объектов класса, а бинарная встроенная опера­ция – только как бинарная. Важен только знак операции. Некоторые знаки операций С++ могут быть перегружены как для унарных операций над объектами класса, так и для бинарных. Чтобы избежать проблем, позна­комьтесь вначале с правилами перегрузки операций для конкретной систе­мы программирования.
Из начального курса объектно-ориентированного программирования на С++ вы знаете, что функция-операция может быть функцией класса, или внешней дружественной этому классу функцией.
Для классов, предназначенных для работы с графами, будет рассмот­рено только использование функций-операций, которые являются функци­ями класса.
Отметим также, что функции-операции могут возвращать значение, могут быть виртуальными и могут наследоваться.
6.2. Унарные функции-операции для ввода-вывода графов
Если функция-операция является функцией класса и перегружается как у н а р н а я операция для объектов класса, то она не должна иметь ар­гументов.
Используем перегрузку операций для функций ввода матрицы смеж­ности графа input_Graph() и вывода матрицы смежности графа out_Graph(). Эти функции предназначены для ввода-вывода только од­ного графа, т.е. для работы только с одним объектом класса Graph, поэто­му их достаточно представить унарными функциями-операциями. Как по­добрать знаки для этих функций-операций? Для этого представьте, с каки­ми операциями С++ у вас возникают ассоциации, когда Вы думаете о вво­де-выводе данных. Для ввода хотелось бы перегрузить операцию сдвига вправо (>>), а для вывода – операцию сдвига влево (<<), как это сделано для объектов cin, cout из библотеки iostream.h. Но это в нашем случае это не удастся, так как эти операции могут быть перегружены толь­ко для бинарных операций. По той же причине нельзя использовать и опе­рации сравнения (>) и (<). Необходимо иметь два объекта класса Graph.
6.2. Унарные функции-операции для ввода-вывода графов
47
Не будем долго выбирать и перегрузим операцию (+) для ввода и
операцию (-) для вывода матрицы смежности. Эти операции можно ис­пользовать как для унарных, так и для бинарных перегруженных операций.
Изменим заголовки выбранных функций в описании класса Graph:
//----------------------------------------------------
class Graph { protected: int N; bool **Matr; public: Graph(int); ~Graph(); void operator+(); //ввод графа void operator-(); //вывод графа virtual void adjacency(int,int); };
а также в самих определениях функций:
//------- функция-операция ввода графа ---------------
void Graph::operator+() { for(int i=0; i<N; i++) for(int j=0; j<N; j++) { cout << "Matr["<< i <<"]["<< j <<"]="; cin >> Matr[i][j]; } }
//-------- функция-операция вывода графа -------------
void Graph::operator-() { for(int i=0; i<N; i++)
6. Задачи на графах и перегрузка операций
48
for(int j=0; j<N; j++) { cout << "Matr["<< i <<"]["<< j <<"]="; cout << Matr[i][j] <<endl; } } //----------------------------------------------------
В главной функции изменим операторы для вызова функций ввода­вывода графа:
int main() { int number; int x1,x2; cin>>number; My_Class * graph = new My_Class(number); +(*graph); // ввод матрицы смежности графа
-(*graph); // вывод матрицы смежности графа
cout<<" Input number of two tops: "; cin>>x1>>x2; graph->adjacency(x1,x2); graph->Graph::adjacency(x1,x2); cout<< endl<<" Tops "<<x1<<" and "<<x2; if(graph->adjacent_nodes(x1,x2)) cout<<" Adjacent"; else cout<< " No Adjacent"; delete graph; getch(); return 0; }
Объект класса My_Class создан в динамической памяти и задан указателем graph. Перегруженную операцию можно использовать только для объектов класса, а не для указателей на объект. Поэтому, прежде чем использовать перегруженную операцию, необходимо применить к адресу объекта операцию взятия содержимого по адресу:
+(*graph);
6.3. Унарная функция-операция для проверки смежности двух вершин графа
49
Если бы объект класса был создан в статической памяти, то этого не
потребовалось, и использование знака перегружаемой операции (+) было бы таким:
My_Class graph (number);
+graph;
6.3. Унарная функция-операция для проверки смежности двух вершин графа
Можно ли использовать перегрузку операций для проверки смежно-
сти двух вершин? Нет, нельзя, так как в нашем примере для этой функции требуются два аргумента. Но если очень хочется использовать перегрузку операций и в этом случае, то необходимо исключить аргументы. Каким об­разом это можно сделать? Во-первых, можно передать в эту функцию но­мера двух вершин, как внешние данные. Во-вторых, можно ввести в описа­ние класса дополнительно две переменные для задания номеров двух лю­бых вершин, и задавать им значения при вызове функции операции, или с помощью других функций класса. Наконец, можно просто объявить в функции-операции номера вершин, как автоматические данные, и вводить их значения при ее вызове.
Воспользуемся вторым вариантом. Добавим в описание класса Graph
описание двух целых переменных, соответствующих номерам двух любых вершин графа:
class Graph { protected: int N; bool **Matr; int x1,x2; public: Graph(int); ~Graph(); void operator+(); //ввод графа void operator-(); //вывод графа virtual void adjacency(int,int); };
6. Задачи на графах и перегрузка операций
50
Введем в описание класса My_Class новую функцию-операцию для
ввода значений номеров двух любых вершин графа. Для этого перегрузим операцию (*) для объектов класса My_Class. Перегрузим также опера­цию (!) для определения смежности двух вершин графа:
class My_Class : public Graph { public: void operator*(); //ввод номеров двух вершин
int operator!(); //проверка смежности двух // вершин
My_Class(int a):Graph(a){} virtual void adjacency(int,int); };
//------- функция проверки смежности двух вершин -----
int My_Class::operator!() { if(Matr[x1-1][x2-1]) return 1; else return 0; }
//-------- функция ввода номеров двух вершин ---------
void My_Class::operator*() { cout<<" Input number of two tops: "; cin>>x1>>x2; }
//---------------------------------------------------
Обратите внимание на имена переменных, соответствующих номе-
рам вершин графа:
int x1,x2;
Эти переменные являются данными класса Graph и видны во всех функ­циях этого класса и производного от него класса My_Class. Следователь-
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]