какие утверждения верны относительно дружественных функций
Урок №126. Дружественные функции и классы
Обновл. 13 Сен 2021 |
На этом уроке мы рассмотрим использование дружественных функций и дружественных классов в языке С++.
Проблема
На предыдущих уроках мы говорили о том, что данные вашего класса должны быть private. Однако может возникнуть ситуация, когда у вас есть класс и функция, которая работает с этим классом, но которая не находится в его теле. Например, есть класс, в котором хранятся данные, и функция (или другой класс), которая выводит эти данные на экран. Хотя код класса и код функции вывода разделены (для упрощения поддержки кода), код функции вывода тесно связан с данными класса. Следовательно, сделав члены класса private, мы желаемого эффекта не добьёмся.
В таких ситуациях есть два варианта:
Сделать открытыми методы класса и через них функция будет взаимодействовать с классом. Однако здесь есть несколько нюансов. Во-первых, эти открытые методы нужно будет определить, на что потребуется время, и они будут загромождать интерфейс класса. Во-вторых, в классе нужно будет открыть методы, которые не всегда должны быть открытыми и предоставляющими доступ извне.
Использовать дружественные классы и дружественные функции, с помощью которых можно будет предоставить функции вывода доступ к закрытым данным класса. Это позволит функции вывода напрямую обращаться ко всем закрытым переменным-членам и методам класса, сохраняя при этом закрытый доступ к данным класса для всех остальных функций вне тела класса! На этом уроке мы рассмотрим, как это делается.
Дружественные функции
Дружественная функция — это функция, которая имеет доступ к закрытым членам класса, как если бы она сама была членом этого класса. Во всех других отношениях дружественная функция является обычной функцией. Ею может быть, как обычная функция, так и метод другого класса. Для объявления дружественной функции используется ключевое слово friend перед прототипом функции, которую вы хотите сделать дружественной классу. Неважно, объявляете ли вы её в public- или в private-зоне класса. Например:
Дружественные функции
Дружественной функцией класса называется функция, которая, не являясь его компонентом, имеет доступ к его собственным (private) и защищенным (protected) компонентам. Функция не может стать другом класса «без его согласия». Для получения прав друга функция должна быть описана в теле класса со спецификатором friend. Именно при наличии такого описания класс предоставляет функции права доступа к защищенным и собственным компонентам.
Использование механизма дружественных функций позволяет упростить интерфейс между классами. Например, дружественная функция позволит получить доступ к собственным или защищенным компонентам сразу нескольких классов. Тем самым из классов можно иногда убрать компонентные функции, предназначенные только для доступа к этим «скрытым» компонентам.
В качестве примера рассмотрим дружественную функцию двух классов «точка на плоскости» и «прямая на плоскости».
В нижеописанной программе определены классы с общей дружественной функцией, в основной программе введены объекты этих классов и вычислено уклонение от точки до прямой:
Дружественная перегрузка
Дружественная перегрузка.Итак, мы рассмотрели дружественные функции и несколько примеров их применения. Однако одним из основных свойств этих специфических функций является то, что с их помощью можно осуществить перегрузку операторов. Такой тип перегрузки носит название дружественной.
Проиллюстрируем особенности оформления операции-функции в виде дружественной функции класса.
#include using namespace std;
класс реализующий работу с логическим значением class Flag <
Результат выполнения программы:
Допустим, переменные a и b объявлены как объекты класса C. В классе C определен оператор C::operator+(C), поэтому
a+b означает a.operator+(b)
Однако, также возможна глобальная перегрузка оператора +:
Такой вариант перегрузки тоже применим к выражению a+b, где a и b передаются соответственно в первом и втором параметрах функции. Из этих двух форм предпочтительной считается перегрузка в классе. Т. к. вторая форма требует открытого обращения к членам класса, а это отрицательно отражается на строгой эстетике инкапсуляции. Вторая форма может быть более удобной для адаптации классов, которые находятся в библиотеках, где исходный текст невозможно изменить и перекомпилировать.То есть добавить в класс перегрузку в качестве метода класса нереально.
Смешивать эти две формы в программе не рекомендуется. Если для некоторого оператора определены обе формы с одинаковыми типами формальных параметров, то использование оператора может создать двусмысленность, которая, скорее всего, окажется фатальной.
Тем не менее, глобальная перегрузка операторов обеспечивает симметрию, которая также обладает эстетической ценностью. Рассмотрим пример:
#include using namespace std;
класс «точка» class Point < координаты точки
глобальная перегрузка для ситуации int + Point доступ к private-членам через специальные функции Point&operator+(int d,Point&Z)<
Без глобальной перегрузки задача int + Point не решается. Поскольку мы не можем получить доступ к «родному” целому типу (то есть к типу int) и переопределить его операции, обеспечить симметрию простым определением операторов класса не удастся. Потребуется решение с глобальными функциями.
Для ввода информации из потока используется операция извлечения, которой является перегруженная операция сдвига вправо ». Левым операндом операции » является объект класса istream.
Чтобы избежать неожиданностей, ввод-вывод для абстрактных типов данных должен следовать тем же соглашениям, которые используются операциями ввода и вывода для встроенных типов, а именно:
1. Возвращаемым значением для операций ввода и вывода должна являться ссылка на поток, чтобы несколько операций могли быть выполнены в одном выражении.
3. Чтобы разрешить доступ к закрытым данным класса, операции ввода и вывода должны быть объявлены как дружественные функции класса.
4. В операцию вывода необходимо передавать константную ссылку на объект класса, поскольку данная операция не должна модифицировать выводимые объекты.
Итак, рассмотрим пример подобной перегрузки:
Проиллюстрируем особенности оформления операции-функции в виде дружественной функции класса.
Результат выполнения программы:
Глобальная перегрузка.
Допустим, переменные a и b объявлены как объекты класса C. В классе C определен оператор C::operator+(C), поэтому
Однако, также возможна глобальная перегрузка оператора +:
Такой вариант перегрузки тоже применим к выражению a+b, где a и b передаются соответственно в первом и втором параметрах функции. Из этих двух форм предпочтительной считается перегрузка в классе. Т. к. вторая форма требует открытого обращения к членам класса, а это отрицательно отражается на строгой эстетике инкапсуляции. Вторая форма может быть более удобной для адаптации классов, которые находятся в библиотеках, где исходный текст невозможно изменить и перекомпилировать.То есть добавить в класс перегрузку в качестве метода класса нереально.
Смешивать эти две формы в программе не рекомендуется. Если для некоторого оператора определены обе формы с одинаковыми типами формальных параметров, то использование оператора может создать двусмысленность, которая, скорее всего, окажется фатальной.
Тем не менее, глобальная перегрузка операторов обеспечивает симметрию, которая также обладает эстетической ценностью. Рассмотрим пример:
Без глобальной перегрузки задача int + Point не решается. Поскольку мы не можем получить доступ к «родному” целому типу (то есть к типу int) и переопределить его операции, обеспечить симметрию простым определением операторов класса не удастся. Потребуется решение с глобальными функциями.
Перегрузка ввода/вывода данных.
Для ввода информации из потока используется операция извлечения, которой является перегруженная операция сдвига вправо ». Левым операндом операции » является объект класса istream.
Чтобы избежать неожиданностей, ввод-вывод для абстрактных типов данных должен следовать тем же соглашениям, которые используются операциями ввода и вывода для встроенных типов, а именно:
Итак, рассмотрим пример подобной перегрузки:
Дружественные классы
Пора узнать, что «дружить» могут не только функции. Класс тоже может быть дружественным другому классу.
12.15 – Дружественные функции и классы
Большую часть этой главы мы проповедовали достоинства сохранения скрытости ваших данных. Однако иногда вы можете столкнуться с ситуациями, когда обнаружите, что у вас есть классы и функции вне этих классов, которые должны очень тесно работать друг с другом. Например, у вас может быть класс, в котором хранятся данные, и функция (или другой класс), которая отображает эти данные на экране. Хотя класс хранилища и код отображения были разделены для упрощения поддержки, код отображения на самом деле тесно связан с деталями класса хранилища. Следовательно, скрытие сведений о классах хранения от кода отображения не дает особой выгоды.
В подобных ситуациях есть два варианта:
Дружественные функции
Дружественная функция – это функция, которая может получить доступ к закрытым членам класса, как если бы она была членом этого класса. Во всем остальном дружественная функция похожа на обычную функцию. Дружественная функция может быть либо обычной функцией, либо функцией-членом другого класса. Чтобы объявить дружественную функцию, просто используйте ключевое слово friend перед прототипом функции, которую вы хотите сделать другом класса. Не имеет значения, объявляете ли вы дружественную функцию в закрытом или открытом разделе класса.
Вот пример использования дружественной функции:
Вот еще один пример:
Хотя оба приведенных выше примера довольно надуманы, последний пример очень похож на случаи, с которыми мы столкнемся позже, когда будем обсуждать перегрузку операторов!
Несколько друзей
Функция может быть другом для более чем одного класса одновременно. Например, рассмотрим следующий пример:
В этом примере стоит отметить две вещи. Во-первых, поскольку printWeather является другом обоих классов, она может получить доступ к закрытым данным из объектов обоих классов. Во-вторых, обратите внимание на следующую строку в верхней части примера:
Дружественные классы
Также целый класс можно сделать другом другого класса. Это дает всем членам дружественного класса доступ к закрытым членам другого класса. Вот пример:
Будьте осторожны при использовании дружественных функций и классов, потому что это позволяет дружественной функции или классу нарушать инкапсуляцию. Если детали реализации класса изменятся, то детали реализации друга также должны будут быть изменены. Следовательно, ограничьте использование дружественных функций и классов до минимума.
Дружественные функции-члены
Вместо того чтобы делать другом весь класс, вы можете сделать другом только одну функцию-член. Это делается аналогично тому, как сделать дружественной обычную функцию, за исключением использования имени функции-члена с включенным префиксом ИмяКласса:: (например, Display::displayItem ).
Однако на самом деле это может быть немного сложнее, чем ожидалось. Давайте, преобразуем предыдущий пример, чтобы сделать Display::displayItem дружественной функцией-членом. Вы можете попробовать сделать что-то вроде этого:
Вот как это выглядит:
Резюме
Дружественная функция или класс – это функция или класс, которые могут получить доступ к закрытым членам другого класса, как если бы они были членами этого класса. Это позволяет дружественной функции или классу тесно работать с другим классом, не заставляя другой класс открывать свои закрытые члены (например, через функции доступа).
Объявление друзьями обычно используется при определении перегруженных операторов (о которых мы поговорим в следующей главе) или, реже, когда два или более класса должны тесно взаимодействовать друг с другом.
Обратите внимание, что для того, чтобы сделать конкретную функцию-член другом, необходимо сначала увидеть полное определение класса функции-члена.
Небольшой тест
Вопрос 1
В геометрии точка – это позиция в пространстве. Мы можем определить точку в трехмерном пространстве как набор координат x, y и z. Например, Point(2.0, 1.0, 0.0) будет точкой в пространстве с координатами x = 2.0, y = 1.0 и z = 0.0.
В физике вектор – это величина, которая имеет величину (длину) и направление (но не положение). Мы можем определить вектор в трехмерном пространстве как значения x, y и z, представляющие направление вектора вдоль осей x, y и z (длина может быть получена из них). Например, Vector(2.0, 0.0, 0.0) будет вектором, представляющим направление вдоль (только) положительной оси x, с длиной 2.0.
Вектор можно применить к точке, чтобы переместить точку в новое положение. Это делается путем добавления направления вектора к положению точки, чтобы получить новое положение. Например, Point(2.0, 1.0, 0.0) + Vector(2.0, 0.0, 0.0) даст точку (4.0, 1.0, 0.0).
Точки и векторы часто используются в компьютерной графике (точки представляют вершины фигуры, а векторы представляют движение фигуры).
Учитывая следующую программу:
Преимущество дружественных функций?
Когда нужно обеспечить некий доступ к внутрянке, при этом не выставляя её полностью наружу. Функция же может быть определена пользователем. Тем самым мы можем несколько повлиять на логику класса, не изменяя его интерфейсов и не вмешиваясь в бинарный код. Где такое может пригодиться? На вскидку:
1. Юнит-тестирование
2. При определении операторов (особенно всяких сложений, вычитаний)
3. Собственно, изменение, в определённых пределах, поведения класса без наследования (но можно получить палкой, когда внутренняя структура поменяется).
Но вообще, как это не гуглится? Гуглится!
friend and Encapsulation
Some people believe that the idea of having friend classes violates the principle of encapsulation because it means that one class can get at the internals of another. One way to think about this, however, is that friend is simply part of a class’s overall interface that it shows the world. Just like an elevator repairman has access to a different interface than an elevator rider, some classes or functions require expanded access to the internals of another class. Moreover, using friend allows a class to present a more restrictive interface to the outside world by hiding more details than may be needed by anything but the friends of the class.
Finally, friends are particularly common in cases of operator overloading because it is often necessary for an overloaded operator to have access to the internals of the classes that are arguments to the operator.
Typical use cases of friend functions are operations that are conducted between two different classes accessing private or protected members of both.
Дружественные функции и классы в C++
В этом руководстве мы научимся создавать дружественные функции и классы на C++ с помощью примеров. Скрытие данных – фундаментальная концепция объектно-ориентированного программирования, которая ограничивает доступ частных членов извне класса. Точно так же защищенные члены могут быть доступны только производным классам и недоступны извне. Например:
Однако в С++ есть функция, называемая дружественной, которая нарушает это правило и позволяет нам получать доступ к функциям-членам извне класса.
Точно так же есть дружественный класс, о котором мы узнаем позже в этом руководстве.
Что такое дружественная функция в C++?
Дружественная функция в C++ может получить доступ к личным и защищенным данным класса. Мы объявляем функцию, используя ключевое слово friend внутри тела класса.
Пример 1: работа функции
Здесь addFive() – это дружественная функция, которая может обращаться как к частным, так и к публичным элементам данных.
Хотя этот пример дает нам представление о концепции функции, он не показывает какого-либо значимого использования.
Более осмысленное использование будет работать с объектами двух разных классов. В этом случае функция friend может оказаться очень полезной.
Пример 2: добавление членов двух разных классов
В этой программе ClassA и ClassB объявили add(), как дружественную функцию. Таким образом, эта функция может получить доступ к приватным данным обоих классов.
Здесь следует отметить одну вещь: функция внутри ClassA использует ClassB. Однако мы еще не определили ClassB.
Чтобы это работало, нам нужно предварительное объявление ClassB в нашей программе.
Дружественный класс в C++
Мы также можем использовать Friend Class (дружественный класс) в С++, используя ключевое слово friend. Например:
Когда класс объявляется дружественным классом, все функции-члены дружественного класса становятся дружественными функциями.
Поскольку classB является дружественным классом, мы можем получить доступ ко всем членам classA изнутри classB.
Однако мы не можем получить доступ к членам ClassB изнутри classA. Это потому, что дружеские отношения в C++ только предоставляются, но не принимаются.