friend (C++)
В некоторых случаях класс полезен для предоставления доступа на уровне члена к функциям, которые не являются членами класса, или всем членам в отдельном классе. Эти бесплатные функции и классы называются друзьями, отмеченными friend ключевое слово. Только реализатор класса может объявить, что является для него дружественным элементом. Функция или класс не может объявить себя другом любого класса. В определении класса используйте friend ключевое слово и имя немемберной функции или другого класса, чтобы предоставить ему доступ к частным и защищенным членам класса. В определении шаблона параметр типа может быть объявлен как . friend
Синтаксис
friend-declaration :
friend function-declaration
friend function-definition
friend elaborated-type-specifier ; ;
friend simple-type-specifier ;
friend typename-specifier ;
friend Объявления
Если вы объявляете friend функцию, которая не была объявлена ранее, эта функция экспортируется в заключающую неклассовую область.
Функции, объявленные в friend объявлении, рассматриваются как если бы они были объявлены с помощью extern ключевое слово. Дополнительные сведения см. в разделе extern .
Хотя функции с глобальными область могут быть объявлены как friend функции до их прототипов, функции-члены не могут быть объявлены как friend функции до появления их полного объявления класса. В следующем коде показано, как такое объявление завершается ошибкой:
class ForwardDeclared; // Class name is known. class HasFriends < friend int ForwardDeclared::IsAFriend(); // C2039 error expected >;
В предыдущем примере имя ForwardDeclared класса вводится в область, но полное объявление (в частности, часть, объявляющая функцию IsAFriend ) не известна. friend Объявление в классе HasFriends создает ошибку.
В C++11 существует две формы объявлений друзей для класса:
friend class F; friend F;
Первая форма представляет новый класс F, если существующий класс по имени не найден в самом внутреннем пространстве имен. C++11: вторая форма не вводит новый класс; его можно использовать, когда класс уже объявлен, и его необходимо использовать при объявлении параметра типа шаблона или typedef в качестве friend .
Используйте friend class F , когда указанный тип еще не объявлен:
namespace NS < class M < friend class F; // Introduces F but doesn't define it >; >
Ошибка возникает, если вы используете friend с типом класса, который не был объявлен:
namespace NS < class M < friend F; // error C2433: 'NS::F': 'friend' not permitted on data declarations >; >
В следующем примере friend F относится к F классу, объявленному за пределами область NS.
class F <>; namespace NS < class M < friend F; // OK >; >
Используется friend F для объявления параметра шаблона в качестве друга:
template class my_class < friend T; //. >;
Используется friend F для объявления типа в качестве друга:
class Foo <>; typedef Foo F; class G < friend F; // OK friend class F // Error C2371 -- redefinition >;
Чтобы объявить два класса как дружественные друг другу, весь второй класс должен быть указан как дружественный для первого класса. Причина такого ограничения заключается в том, что компилятор получает достаточные сведения для объявления отдельных дружественных функций только в момент объявления второго класса.
Хотя весь второй класс должен быть дружественным для первого класса, можно выбрать, какие функции первого класса будут дружественными для второго класса.
дружественные функции
friend Функция — это функция, которая не является членом класса, но имеет доступ к частным и защищенным элементам класса. Другие функции не считаются членами класса; они являются обычными внешними функциями, которым предоставляются специальные привилегии доступа. Друзья не входят в область класса, и они не вызываются с помощью операторов выбора членов ( и —>), если они не являются членами другого класса. Функция friend объявляется классом, предоставляющим доступ. Объявление friend можно поместить в любое место в объявлении класса. Это не влияет на ключевое слово управления доступом.
В следующем примере показан класс Point и дружественная функция ChangePrivate . Функция friend имеет доступ к члену частных данных объекта, который Point он получает в качестве параметра.
// friend_functions.cpp // compile with: /EHsc #include using namespace std; class Point < friend void ChangePrivate( Point & ); public: Point( void ) : m_i(0) <>void PrintPrivate( void ) private: int m_i; >; void ChangePrivate ( Point &i ) < i.m_i++; >int main() < Point sPoint; sPoint.PrintPrivate(); ChangePrivate(sPoint); sPoint.PrintPrivate(); // Output: 0 1 >
Члены класса как дружественные элементы
Функции-члены класса могут быть объявлены в других классах как дружественные. Рассмотрим следующий пример:
// classes_as_friends1.cpp // compile with: /c class B; class A < public: int Func1( B& b ); private: int Func2( B& b ); >; class B < private: int _b; // A::Func1 is a friend function to class B // so A::Func1 has access to all members of B friend int A::Func1( B& ); >; int A::Func1( B& b ) < return b._b; >// OK int A::Func2( B& b ) < return b._b; >// C2248
В предыдущем примере доступ к классу B предоставляется friend только функция A::Func1( B& ) . Поэтому доступ к частному члену _b является правильным в Func1 классе A , но не в Func2 .
Класс friend — это класс, все функции-члены которого являются friend функциями класса, т. е. функции-члены которых имеют доступ к частным и защищенным членам другого класса. Предположим, что в классе friend было следующее объявление B :
friend class A;
В этом случае все функции-члены в классе A получили friend бы доступ к классу B . Следующий код является примером friend класса:
// classes_as_friends2.cpp // compile with: /EHsc #include using namespace std; class YourClass < friend class YourOtherClass; // Declare a friend class public: YourClass() : topSecret(0)<>void printMember() < cout private: int topSecret; >; class YourOtherClass < public: void change( YourClass& yc, int x )>; int main()
Дружба не является взаимной, если только явно не указано как таковое. В приведенном выше примере функции-члены YourClass не могут получить доступ к частным членам YourOtherClass .
Управляемый тип (в C++/CLI) не может иметь функций friend , friend классов или friend интерфейсов.
Дружба не наследуется, то есть классы, производные от YourOtherClass частных членов, не могут получить доступ YourClass к частным членам. Дружба не является транзитивной, поэтому классы, которые являются друзьями YourOtherClass не могут получить доступ к YourClass частным членам.
На следующем рисунке показаны объявления 4 классов: Base , Derived , aFriend и anotherFriend . Только класс aFriend имеет прямой доступ к закрытым членам класса Base (и к любым возможным унаследованным членам класса Base ).
На схеме показано, что класс anotherFriend не имеет отношения друг с базой классов, которую друзья класс aFriend. Класс aFriend друг от класса Base, но у него нет отношения друг с классом Производное, даже если класс Производно наследует от Base. Это показывает, что наследование не означает, что производный класс имеет те же друзья, что и базовый класс.
Встроенные friend определения
Пользовательские функции можно определить (в соответствии с телом функции) внутри объявлений классов. Эти функции являются встроенными функциями. Как и встроенные функции-члены, они ведут себя так, как будто они были определены сразу после того, как все члены класса были замечены, но до закрытия класса область (в конце объявления класса). Функции-друзья, определенные внутри объявлений классов, находятся в область заключенного класса.
Зачем нужны друзья класса c
Дружественные функции — это функции, которые не являются членами класса, однако имеют доступ к его закрытым членам — переменным и функциям, которые имеют спецификатор private.
Для определения дружественных функций используется ключевое слово friend . Например, определим следующую программу:
#include class Auto < friend void drive(const Auto&); friend void setPrice(Auto&, unsigned); public: Auto(std::string autoName, unsigned autoPrice) < name = autoName; price = autoPrice; >void print() < std::cout private: std::string name; // название автомобиля unsigned price; // цена автомобиля >; void drive(const Auto &car) < std::cout void setPrice(Auto &car, unsigned price) < car.price = price; >int main() < Auto tesla("Tesla", 5000); tesla.print(); // drive(tesla); setPrice(tesla, 8000); tesla.print(); // >
Здесь определен класс Auto, который представляет автомобиль. У этого класса определены приватные закрытые переменные name (название автомобиля) и price (цена автомобиля). Также в классе объявлены две дружественные функции: drive (функция вождения автомобиля) и setPrice (функция назначения цены). Обе этих функции принимают в качестве параметра ссылку на объект Auto.
Когда мы объявляем дружественные функции, то фактически мы говорим компилятору, что это друзья класса и они имеют доступ ко всем членам этого класса, в том числе закрытым.
При этом для дружественных функций не важно, определяются они под спецификатором public или private. Для них это не имеет значения.
Определение этих функций производится вне класса. И поскольку эти функции являются дружественными, то внутри этих функций мы можем через переданную ссылку Auto обратиться ко всем его закрытым переменным.
Консольный вывод программы:
Tesla : 5000 Tesla is driven Tesla : 8000
Определение дружественных функций в классе
Дружественные функции могут определяться в другом классе. Например, определим класс Person, который использует объект Auto:
#include class Auto; // объявление класса Auto, чтобы Person видел этот класс class Person < public: Person(std::string p_name) < name = p_name; >void drive(const Auto&); void setPrice(Auto&, unsigned); private: std::string name; >; class Auto < // объявление дружественных функций friend void Person::drive(const Auto&); friend void Person::setPrice(Auto&, unsigned); public: Auto(std::string a_name, unsigned a_price) < name = a_name; price = a_price; >void print() < std::cout private: std::string name; // название автомобиля unsigned price; // цена автомобиля >; void Person::drive(const Auto &car) < std::cout void Person::setPrice(Auto &car, unsigned price) < car.price = price; >int main() < Auto tesla; Person tom; tom.drive(tesla); tom.setPrice(tesla, 8000); tesla.print(); >
Вначале определен класс Person, который представляет человека. Однако поскольку класс Person использует класс Auto, то перед классом Person идет объявление класса Auto.
Две функции из класса Person принимают ссылку на объект Auto:
void drive(const Auto&); void setPrice(Auto&, unsigned);
То есть фигурально говоря, человек водит автомобиль и назначает ему цену с помощью этих функциий.
Класс Auto определяет дружественные функции с той же сигнатурой:
friend void Person::drive(Auto&); friend void Person::setPrice(Auto&, unsigned);
Причем поскольку данные функции будут определены в классе Person, то названия этих функций предваряются префиксом «Person::».
И поскольку в этих функциях предполагается использовать объект Auto, то ко времени определения этих функций все члены объекта Auto должны быть известны, поэтому определения функций находятся не в самом классе Person, а после класса Auto. И так как эти функции определены в классе Auto как дружественные, мы можем обратиться в этих функциях к закрытым членам класса Auto.
Консольный вывод программы:
Tom drives Tesla Tesla : 8000
Дружественные классы
В случае выше класс Person использует только две функции из класса Auto. Но допустим впоследствии возникла необходимость добавить в класс Auto еще ряд дружественных функций, которые будут определены в классе Person. Либо мы можем предполагать, что класс Person будет активно использовать объекты Auto. И в этом случае целесообразно определять не отдельные дружественные функции, а определить дружественным весь класс Person:
#include class Auto; // объявление класса Auto, чтобы Person видел этот класс class Person < public: Person(std::string p_name) < name = p_name; >void drive(const Auto&); void setPrice(Auto&, unsigned); private: std::string name; >; class Auto < // объявление дружественного класса friend class Person; public: Auto(std::string a_name, unsigned a_price) < name = a_name; price = a_price; >void print() < std::cout private: std::string name; // название автомобиля unsigned price; // цена автомобиля >; void Person::drive(const Auto& car) < std::cout void Person::setPrice(Auto& car, unsigned price) < car.price = price; >int main() < Auto tesla; Person tom; tom.drive(tesla); tom.setPrice(tesla, 8000); tesla.print(); >
Единственное, что в данном случае изменилось по сравнению с предыдущим примером — это то, что в классе Auto определение дружественных функций было заменено определением дружественного класса:
friend class Person;
То есть тем самым мы опять же говорим, что класс Person — это друг класса Auto, поэтому объекты Person могут обращаться к приватным переменным класса Auto. После этого в классе Person можно обращаться к закрытым членам класса Auto из любых функций.
Дружественные функции С++
Самым важным, но и, скорее всего, самым непонятным для вас сейчас станет определение дружественной функции. Дружественная функция — это функция, которая не является членом класса, но имеет доступ к членам класса, объявленным в полях private или protected . Долго не вникайте в суть этого определения, а лучше сразу переходите к следующему абзацу. Обещаю, что после прочтения статьи вы вернетесь к этому определению и вас посетит мысль: «Ну да — так и есть! Тут все понятно!»
Приступим к основному занятию программиста — практике! В примере запрограммируем следующее: создадим класс Woman25 , который, используя дружественные функции и обычные методы класса, будет получать данные об объекте (имя и вес) и выводить их на экран. Методы и friend -функции будут выполнять аналогичные действия. В этом и есть особенная польза данного примера — вы сможете посмотреть отличия в объявлении и определении дружественных функций от обычных методов класса. На основании полученных данных, программа даст пользователю совет относительно корректировки его веса. Ну что-же, лучше один раз увидеть…
#include #include using namespace std; class Woman25 < private: char *name;//имя int weight;//вес friend void setData(char *, int, Woman25&);//объявление дружественных функций friend void getData(Woman25&); public: Woman25()//конструктор < name = new char [20]; strcpy(name, "Норма"); weight = 60; >~Woman25()//деструктор < delete [] name; cout void setData(char*, int);//объявление методов класса void getData(); void advise(); >; void setData(char *n, int w, Woman25& object)//определяем friend-функцию setData < strcpy(object.name, n);//////////// object.weight = w; >void getData(Woman25& object)//определяем friend-функцию getData < cout void Woman25::setData(char *n, int w)//определяем set-метод класса < strcpy(name, n); weight = w; >void Woman25::getData()//определяем get-метод класса < cout void Woman25::advise()//определяем метод класса Совет (advise) < if(weight < 55)< //если вес меньше 55 кг cout else if(weight >= 55 && weight else < //если вес >65 кг cout > int main() < setlocale(LC_ALL, "rus"); Woman25 Norm; //создаем объект Norm, сработает конструктор и weight будет = 60, name - Норма Norm.getData(); //вызов метода класса cout
Комментарии к исходному коду. В строках 6 — 30 создаем класс Woman25 . Он содержит два приватных элемента класса: char *name; и int weight; . В private также объявим дружественные функции friend void setData(char *, int, Woman25&); и friend void getData(Woman25&); . Хотя их можно объявлять и определять в любом поле класса, будь то private , public или protected , но об этом поговорим позже. Так мы сделали, чтобы показать, что несмотря на объявление дружественных функций в поле private , к ним можно обращаться напрямую из функции main() . Тогда, когда методы класса должны располагаться в поле public всегда , если мы собираемся вызывать их вне класса . Объявление дружественных функций отличается от объявления методов класса еще и тем, что перед типом возвращаемого функцией значения используется зарезервированное слово friend . Обратите внимание, что в виде параметра мы передаем этим функциям ссылку на объект нашего класса — Woman25& . В обычных методах этого не будет. В поле public расположился конструктор класса — строки 15 — 20 . В нем мы задаем значение нормального веса — 60 кг, а так же, с помощью функции strcpy(name, "Норма"); , вносим данные в элемент класса name . Затем в строках 21 — 25 определяем деструктор класса ~Woman25() . Его задача, освободить, выделенную конструктором, динамическую память. А для того, чтобы убедиться, что он сработал и удалил динамическую память всех созданных объектов класса при завершении работы программы, мы добавили в него строку cout Строки 27 — 29 — объявление обычных методов класса.
Теперь начинается, пожалуй, самое интересное — определение вне класса наших дружественных функций и обычных методов класса. Все это располагается в строках 32 — 66. Смотрите, когда мы определяем дружественные функции , строки 32 — 36 и 38 — 41, мы не используем оператор :: двойное двоеточие (область видимости метода). Это уже говорит нам о том, что дружественная функция не принадлежит классу, не является его компонентом. А при определении остальных методов использование оператора :: является обязательным. Метод класса advise() , на основании полученных данных о весе, дает пользователю один из советов либо сообщает, что вес в норме.
Переходим в главную функцию — строки 68 — 87. Тут мы создаем объект класса Woman25 Norm; , при создании которого сработает конструктор и инициализирует элементы name и weight . Вызываем метод класса Norm.getData(); чтобы вывести на экран значение нормы. Со вторым созданным объектом Woman25 Anna; работаем, вызывая обычные set и get — методы класса, а с третьим объектом Woman25 Inna; — вызывая дружественные функции. Как видите, вызываются они как функции, которые не принадлежат классу. Объект класса мы передаем, как параметр.
Запускаем программу и видим следующее.
CppStudio.com
. Деструктор .
. Деструктор .
. Деструктор .
Для продолжения нажмите любую клавишу . . .
И friend-функции, и методы класса справились со своими задачами, а мы с вами увидели отличия в их объявлении и определении. Так же мы увидели, что деструктор класса, как положено, сработал 3 раза (т.е. при уничтожении каждого созданного объекта). Если у вас возникает вопрос «Собственно, а зачем все это, если методы класса и так прекрасно работают?» — это хорошо! Ответ такой — «Представьте, что у нас есть еще десяток классов. Например Girl6_7 , Girl8_9 , Man25 и т.д., Используя дружественные функции, нам не придется для каждого класса определять set и get -методы. Это в нашей программе они короткие! А если бы они занимали 20 — 30 строк? А так достаточно определить метод в одном из классов или вообще определить функцию, как глобальную, а в остальные классы прописать ее прототип, как дружественной функции (используя слово friend ). Мы экономим массу времени и наш код становится намного короче.»
Теперь, как всегда, немного теории, для закрепления материала:
- Дружественная функция может располагаться в любом поле класса – private , public или protected . Она при любых обстоятельствах будет иметь доступ к private -элементам класса и, даже если она сама находится в поле private (как в нашем примере), к ней можно будет обратиться вне класса, не используя специальных методов.
- Когда мы определяем дружественную функцию, элементы класса необходимо явно передавать в нее в виде параметров функции. Так как она не является компонентом класса, она не получает указатель this .
- В виде параметра, в дружественную функцию так же надо передать указатель или ссылку на объект класса. Иначе она не увидит данные какого класса ей принять и обработать.
- Функция может использоваться, как дружественная к нескольким классам.
- Вызываются дружественные функции, как обычные функции. Т.е не используется такой способ — Объект_класса.функция() . После внесения всех необходимых параметров в нее при вызове, она сама увидит с элементами какого класса и объекта надо работать.
Вот и все для начала. Вопросы, предложения и замечания по теме ждем в комментариях к этой статье.
К сожалению, для данной темы пока нет подходящих задач. Если у вас есть таковые на примете, отправте их по адресу: admin@cppstudio.com. Мы их опубликуем!
Дружественные функции
Дружественной функцией класса называется функция, которая, не являясь его компонентом, имеет доступ к его собственным (private) и защищенным (protected) компонентам. Функция не может стать другом класса "без его согласия". Для получения прав друга функция должна быть описана в теле класса со спецификатором friend. Именно при наличии такого описания класс предоставляет функции права доступа к защищенным и собственным компонентам.
Некоторые особенности дружественных функций.
Дружественная функция при вызове не получает указателя this.
Объекты классов должны передаваться дружественной функции только явно через аппарат параметров.
Дружественные функции нельзя вызывать через объекты классов, друзьями которых они являются, а также через указатели на эти объекты. Иначе говоря, следующие действия запрещены:
имя_объекта.имя_функции указатель_на_объект -> имя_функции
На дружественную функцию не распространяется действие спецификаторов доступа (public, protected, private), поэтому место размещения прототипа дружественной функции внутри определения класса безразлично.
Дружественная функция не может быть компонентной функцией того класса, по отношению к которому определяется как дружественная, зато она может быть просто глобальной функцией, а также компонентной функцией другого ранее определенного класса.
Дружественная функция может быть дружественной по отношению к нескольким классам.
Кое-что о применении
Использование механизма дружественных функций позволяет упростить интерфейс между классами. Например, дружественная функция позволит получить доступ к собственным или защищенным компонентам сразу нескольких классов. Тем самым из классов можно иногда убрать компонентные функции, предназначенные только для доступа к этим "скрытым" компонентам.
В качестве примера рассмотрим дружественную функцию двух классов "точка на плоскости" и "прямая на плоскости".
Класс "точка на плоскости" включает компонентные данные для задания координат (х, у) точки.
Компонентными данными класса "прямая на плоскости" будут коэффициенты A, B, C общего уравнения прямой A*х+B*y+C = 0.
Дружественная функция определяет уклонение заданной точки от заданной прямой. Если (a, b) - координаты конкретной точки, то для прямой, в уравнение которой входят коэффициенты A, B, C, уклонение вычисляется как значение выражения A*a+B*b+C.
В нижеописанной программе определены классы с общей дружественной функцией, в основной программе введены объекты этих классов и вычислено уклонение от точки до прямой:
#include using namespace std; // Предварительное упоминание о классе line_. class line_; // Класс "точка на плоскости": class point_ < // Координаты точки на плоскости. float x, y; public: // Конструктор. point_(float xn = 0, float yn = 0) < x = xn; y = yn; >friend float uclon(point_,line_); >; // Класс "прямая на плоскости": class line_ < // Параметры прямой. float A, B, C; public: // Конструктор. line_(float a, float b, float c) < A = a; B = b; C = c; >friend float uclon(point_,line_); >; // Внешнее определение дружественной функции. float uclon(point_ p, line_ l) < // вычисление отклонения прямой return l.A * p.x + l.B * p.y + l.C; >void main() < // Определение точки P. point_ P(16.0,12.3); // Определение прямой L. line_ L(10.0,-42.3,24.0); cout
Дружественная перегрузка
Дружественная перегрузка.Итак, мы рассмотрели дружественные функции и несколько примеров их применения. Однако одним из основных свойств этих специфических функций является то, что с их помощью можно осуществить перегрузку операторов. Такой тип перегрузки носит название дружественной.
Проиллюстрируем особенности оформления операции-функции в виде дружественной функции класса.
#include using namespace std;
класс реализующий работу с логическим значением class Flag
bool flag; // дружественная функция (перегрузка // оператора ! - замена значения флага // на противоположное) friend Flag& operator !(Flag&f);
// Конструктор. Flag(bool iF) < flag = iF; >// Компонентная функция показа значения флага // в текстовом формате: void display()
Определение дружественной операции-функции. (this не передается, поэтому 1 параметр) Flag& operator !(Flag & f) замена значения на противоположное
f.flag=!f.flag; return f;
Flag A(true);
// показ начального значения A.display();
// замена значения на противоположное // с помощью перегруженного оператора A=!A; // показ измененного значения A.display();
Результат выполнения программы:
Глобальная перегрузка. В C++ кроме двух известных вам разновидностей перегрузки (перегрузка в классе и дружественная перегрузка), существует еще одно понятие - глобальная перегрузка, осуществляемая во внешней области видимости.
Допустим, переменные a и b объявлены как объекты класса C. В классе C определен оператор C::operator+(C), поэтому
a+b означает a.operator+(b)
Однако, также возможна глобальная перегрузка оператора +:
Такой вариант перегрузки тоже применим к выражению a+b, где a и b передаются соответственно в первом и втором параметрах функции. Из этих двух форм предпочтительной считается перегрузка в классе. Т. к. вторая форма требует открытого обращения к членам класса, а это отрицательно отражается на строгой эстетике инкапсуляции. Вторая форма может быть более удобной для адаптации классов, которые находятся в библиотеках, где исходный текст невозможно изменить и перекомпилировать.То есть добавить в класс перегрузку в качестве метода класса нереально.
Смешивать эти две формы в программе не рекомендуется. Если для некоторого оператора определены обе формы с одинаковыми типами формальных параметров, то использование оператора может создать двусмысленность, которая, скорее всего, окажется фатальной.
Тем не менее, глобальная перегрузка операторов обеспечивает симметрию, которая также обладает эстетической ценностью. Рассмотрим пример:
#include using namespace std;
класс "точка" class Point координаты точки
int X; int Y;
// конструктор Point(int iX,int iY)
//показ на экран void Show()
// перегруженный оператор + // метод класса для ситуации Point+int Point&operator+(int d) < Point P(0,0); P.X=X+d; P.Y=Y+d; return P; >// функции доступа к // privat-членам без них // глобальная перегрузка невозможна int GetX() const < return X; >int GetY() const < return Y; >void SetX(int iX) < X=iX; >void SetY(int iY)
глобальная перегрузка для ситуации int + Point доступ к private-членам через специальные функции Point&operator+(int d,Point&Z)
Point P(0,0); P.SetX(d+Z.GetX()); P.SetY(d+Z.GetY()); return P;
// создание объекта Point A(3,2); A.Show();
//оператор-метод + Point B=A+5; B.Show();
//глобальный оператор Point C=2+A; C.Show();
Без глобальной перегрузки задача int + Point не решается. Поскольку мы не можем получить доступ к "родному” целому типу (то есть к типу int) и переопределить его операции, обеспечить симметрию простым определением операторов класса не удастся. Потребуется решение с глобальными функциями.
Примечание: Здесь мы могли бы применить дружественную перегрузку, и таким образом избавиться от "функций доступа к private-членам". Однако, если бы тело класса Point было бы для нас закрыто, то вписать в него функцию-друга было бы нереально. Перегрузка ввода/вывода данных. Для того, что бы закрепить новую полученную информацию о перегрузке рассмотрим возможность перегрузить операторы « и ». Для начала немного информации -
Выполнение любой программы С++ начинаются с набором предопределенных открытых потоков, объявленных как объекты классов в файле-библиотеке iostream. Среди них есть два часто используемых нами объекта - это cin и cout.
cin - объект класса istream (Потоковый класс общего назначения для ввода, являющийся базовым классом для других потоков ввода)
cout - объект класса ostream (Потоковый класс общего назначения для вывода, являющийся базовым классом для других потоков вывода)
Вывод в поток выполняется с помощью операции, которая является перегруженной операцией сдвига влево « . Левым ее операндом является объект потока вывода. Правым операндом может являться любая переменная, для которой определен вывод в поток. Например, оператор cout « "Hello!\n"; приводит к выводу в предопределенный поток cout строки "Hello!".
Для ввода информации из потока используется операция извлечения, которой является перегруженная операция сдвига вправо ». Левым операндом операции » является объект класса istream.
Чтобы избежать неожиданностей, ввод-вывод для абстрактных типов данных должен следовать тем же соглашениям, которые используются операциями ввода и вывода для встроенных типов, а именно:
1. Возвращаемым значением для операций ввода и вывода должна являться ссылка на поток, чтобы несколько операций могли быть выполнены в одном выражении.
2. Первым параметром функции должен быть поток, из которого будут извлекаться данные, вторым параметром - ссылка или указатель на объект определенного пользователем типа.
3. Чтобы разрешить доступ к закрытым данным класса, операции ввода и вывода должны быть объявлены как дружественные функции класса.
4. В операцию вывода необходимо передавать константную ссылку на объект класса, поскольку данная операция не должна модифицировать выводимые объекты.
Итак, рассмотрим пример подобной перегрузки:
#include using namespace std; // класс "точка" class Point < // координаты точки int X; int Y; public: // конструктор Point(int iX,int iY)< X=iX; Y=iY; >// дружественные функции перегрузки ввода и вывода данных friend istream& operator>>(istream& is, Point& P); friend ostream& operator<<(ostream& os, const Point& P); >; //ввод данных через поток istream& operator>>(istream&is, Point&P)< cout> P.X; cout> P.Y; return is; > //вывод данных через поток ostream& operator <<(ostream&os, const Point&P)< os << "X = " << P.X << '\t'; os << "Y = " << P.Y << '\n'; return os; >void main() < // создание объекта Point A(0,0); // одиночный ввод и вывод cin>>A; cout>A>>B; cout
В одном из примеров мы использовали константный метод - метод, который не имеет право изменять поля класса. Однако, если какое-то поле объявлено со спецификатором mutable, его значение МОЖНО менять в методе типа const.Дружественная перегрузка.Итак, мы рассмотрели дружественные функции и несколько примеров их применения. Однако одним из основных свойств этих специфических функций является то, что с их помощью можно осуществить перегрузку операторов. Такой тип перегрузки носит название дружественной.
Проиллюстрируем особенности оформления операции-функции в виде дружественной функции класса.
#include using namespace std; // класс реализующий работу // с логическим значением class Flag < bool flag; // дружественная функция (перегрузка // оператора ! - замена значения флага // на противоположное) friend Flag& operator !(Flag&f); public: // Конструктор. Flag(bool iF) < flag = iF; >// Компонентная функция показа значения флага // в текстовом формате: void display() < if(flag) cout>; // Определение дружественной // операции-функции. // (this не передается, поэтому 1 параметр) Flag& operator !(Flag & f) < //замена значения на противоположное f.flag=!f.flag; return f; >void main() < Flag A(true); // показ начального значения A.display(); // замена значения на противоположное // с помощью перегруженного оператора A=!A; // показ измененного значения A.display(); >
Результат выполнения программы:
Глобальная перегрузка.
В C++ кроме двух известных вам разновидностей перегрузки (перегрузка в классе и дружественная перегрузка), существует еще одно понятие - глобальная перегрузка, осуществляемая во внешней области видимости.
Глобальная перегрузка - это перегрузка объекта, а не метода, оператора.
Допустим, переменные a и b объявлены как объекты класса C. В классе C определен оператор C::operator+(C), поэтому
a+b означает a.operator+(b)
Однако, также возможна глобальная перегрузка оператора +:
C operator+(C,C)
Такой вариант перегрузки тоже применим к выражению a+b, где a и b передаются соответственно в первом и втором параметрах функции. Из этих двух форм предпочтительной считается перегрузка в классе. Т. к. вторая форма требует открытого обращения к членам класса, а это отрицательно отражается на строгой эстетике инкапсуляции. Вторая форма может быть более удобной для адаптации классов, которые находятся в библиотеках, где исходный текст невозможно изменить и перекомпилировать.То есть добавить в класс перегрузку в качестве метода класса нереально.
Смешивать эти две формы в программе не рекомендуется. Если для некоторого оператора определены обе формы с одинаковыми типами формальных параметров, то использование оператора может создать двусмысленность, которая, скорее всего, окажется фатальной.
Тем не менее, глобальная перегрузка операторов обеспечивает симметрию, которая также обладает эстетической ценностью. Рассмотрим пример:
#include using namespace std; // класс "точка" class Point < // координаты точки int X; int Y; public: // конструктор Point(int iX,int iY)< X=iX; Y=iY; >//показ на экран void Show() < cout<<"\n+++++++++++++++++++++\n"; cout<<"X = "<// перегруженный оператор + // метод класса для ситуации Point+int Point&operator+(int d) < Point P(0,0); P.X=X+d; P.Y=Y+d; return P; >// функции доступа к // privat-членам без них // глобальная перегрузка невозможна int GetX() const < return X; >int GetY() const < return Y; >void SetX(int iX) < X=iX; >void SetY(int iY) < Y=iY; >>; // глобальная перегрузка // для ситуации int + Point // доступ к private-членам // через специальные функции Point&operator+(int d,Point&Z) < Point P(0,0); P.SetX(d+Z.GetX()); P.SetY(d+Z.GetY()); return P; >void main() < // создание объекта Point A(3,2); A.Show(); //оператор-метод + Point B=A+5; B.Show(); //глобальный оператор Point C=2+A; C.Show(); >
Без глобальной перегрузки задача int + Point не решается. Поскольку мы не можем получить доступ к "родному” целому типу (то есть к типу int) и переопределить его операции, обеспечить симметрию простым определением операторов класса не удастся. Потребуется решение с глобальными функциями.
Здесь мы могли бы применить дружественную перегрузку, и таким образом избавиться от "функций доступа к private-членам". Однако, если бы тело класса Point было бы для нас закрыто, то вписать в него функцию-друга было бы нереально.
Перегрузка ввода/вывода данных.
Для того, что бы закрепить новую полученную информацию о перегрузке рассмотрим возможность перегрузить операторы « и ». Для начала немного информации - Выполнение любой программы С++ начинаются с набором предопределенных открытых потоков, объявленных как объекты классов в файле-библиотеке iostream. Среди них есть два часто используемых нами объекта - это cin и cout.
cin - объект класса istream (Потоковый класс общего назначения для ввода, являющийся базовым классом для других потоков ввода) cout - объект класса ostream (Потоковый класс общего назначения для вывода, являющийся базовым классом для других потоков вывода)
Вывод в поток выполняется с помощью операции, которая является перегруженной операцией сдвига влево « . Левым ее операндом является объект потока вывода. Правым операндом может являться любая переменная, для которой определен вывод в поток. Например, оператор cout « "Hello!\n"; приводит к выводу в предопределенный поток cout строки "Hello!".
Для ввода информации из потока используется операция извлечения, которой является перегруженная операция сдвига вправо ». Левым операндом операции » является объект класса istream.
Чтобы избежать неожиданностей, ввод-вывод для абстрактных типов данных должен следовать тем же соглашениям, которые используются операциями ввода и вывода для встроенных типов, а именно:
Возвращаемым значением для операций ввода и вывода должна являться ссылка на поток, чтобы несколько операций могли быть выполнены в одном выражении.
Первым параметром функции должен быть поток, из которого будут извлекаться данные, вторым параметром - ссылка или указатель на объект определенного пользователем типа.
Чтобы разрешить доступ к закрытым данным класса, операции ввода и вывода должны быть объявлены как дружественные функции класса.
В операцию вывода необходимо передавать константную ссылку на объект класса, поскольку данная операция не должна модифицировать выводимые объекты.
Итак, рассмотрим пример подобной перегрузки:
#include using namespace std; // класс "точка" class Point < // координаты точки int X; int Y; public: // конструктор Point(int iX,int iY)< X=iX; Y=iY; >// дружественные функции перегрузки ввода и вывода данных friend istream& operator>>(istream& is, Point& P); friend ostream& operator<<(ostream& os, const Point& P); >; //ввод данных через поток istream& operator>>(istream&is, Point&P)< cout> P.X; cout> P.Y; return is; > //вывод данных через поток ostream& operator <<(ostream&os, const Point&P)< os << "X = " << P.X << '\t'; os << "Y = " << P.Y << '\n'; return os; >void main() < // создание объекта Point A(0,0); // одиночный ввод и вывод cin>>A; cout>A>>B; cout
В одном из примеров мы использовали константный метод - метод, который не имеет право изменять поля класса. Однако, если какое-то поле объявлено со спецификатором mutable, его значение МОЖНО менять в методе типа const.
Дружественные классы
Пора узнать, что "дружить" могут не только функции. Класс тоже может быть дружественным другому классу.
Особенности "дружбы" между классами.
Дружественный класс должен быть определен вне тела класса, "предоставляющего дружбу".
Все компонентные функции класса-друга будут являться дружественными для другого класса без указания спецификатора friend.
Все компоненты класса доступны в дружественном классе, но не наоборот.
Дружественный класс может быть определен позже (ниже), чем описан как дружественный.

11 Самых Популярных Статей
- ulimit (limits.conf) управление ограничениями ресурсов ОС Linux
- 7 способов сравнения файлов по содержимому в Windows или Linux
- Что такое страны tier 1,2,3 и как правильно выбрать ГЕО для рекламной кампании
- Настройка, использование GitLab CI/CD
- Что означает "> /dev/null 2>&1" или перенаправление STDIN, STDOUT и STDERR?
- Настройка и использование сервера OpenVPN в Linux
- PostgreSQL: создать БД, пользователя, таблицу, установить права
- Виды кодировок символов
- Использование rsync в примерах
- my.cnf примеры конфигурации MySQL, MariaDB
- dig проверка DNS сервера
11 Самых Популярных Обзоров
- ТОП 4 лучших антидетект браузеров в 2023 (Бесплатные & Платные)
- Обзор и отзывы о Namecheap в 2023 году
- Хостинг Zomro (Зомро)
- Обзор браузера Dolphin
- ТОП 3 Проверенных VPN, Прокси, Хостинг VPS Турция в 2023
- Что такое абузоустойчивый хостинг (bulletproof)?
- Обзор и отзывы о 4VPS (FourServer) в 2023 году
- Обзор и отзывы AstroProxy в 2023 году
- Обзор и отзывы о PQ Hosting в 2023 году
- Обзор и отзывы о Hostinger в 2023 году: преимущества и недостатки
- Проверенные VPS / VDS хостинг провайдеры