Инструменты пользователя

Инструменты сайта


druzhestvennye_funkcii_klassy._druzhestvennaja_peregruzka

Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

druzhestvennye_funkcii_klassy._druzhestvennaja_peregruzka [2010/12/18 19:06] (текущий)
Строка 1: Строка 1:
 +====== Дружественные функции ======
 +Дружественной функцией класса называется функция,​ которая,​ не являясь его компонентом,​ имеет доступ к его собственным (private) и защищенным (protected) компонентам. Функция не может стать другом класса "​без его согласия"​. Для получения прав друга функция должна быть описана в теле класса со спецификатором friend. Именно при наличии такого описания класс предоставляет функции права доступа к защищенным и собственным компонентам.
 +
 +  * **Некоторые особенности дружественных функций.**
 +  - Дружественная функция при вызове не получает указателя this.
 +  - Объекты классов должны передаваться дружественной функции только явно через аппарат параметров.
 +  - Дружественные функции нельзя вызывать через объекты классов,​ друзьями которых они являются,​ а также через указатели на эти объекты. Иначе говоря,​ следующие действия запрещены:<​file>​
 +имя_объекта.имя_функции
 +указатель_на_объект ​ -> имя_функции</​file>​
 +  - На дружественную функцию не распространяется действие спецификаторов доступа (public, protected, private), поэтому место размещения прототипа дружественной функции внутри определения класса безразлично.
 +  - Дружественная функция не может быть компонентной функцией того класса,​ по отношению к которому определяется как дружественная,​ зато она может быть просто глобальной функцией,​ а также компонентной функцией другого ранее определенного класса.
 +  - Дружественная функция может быть дружественной по отношению к нескольким классам. ​
 +
 +  * **Кое-что о применении**
 +Использование механизма дружественных функций позволяет упростить интерфейс между классами. Например,​ дружественная функция позволит получить доступ к собственным или защищенным компонентам сразу нескольких классов. Тем самым из классов можно иногда убрать компонентные функции,​ предназначенные только для доступа к этим "​скрытым"​ компонентам. ​
 +
 +В качестве примера рассмотрим дружественную функцию двух классов "​точка на плоскости"​ и "​прямая на плоскости"​. ​
 +
 +  - Класс "​точка на плоскости"​ включает компонентные данные для задания координат (х, у) точки.
 +  - Компонентными данными класса "​прямая на плоскости"​ будут коэффициенты A, B, C общего уравнения прямой A*х+B*y+C = 0.
 +  - Дружественная функция определяет уклонение заданной точки от заданной прямой. Если (a, b) - координаты конкретной точки, то для прямой,​ в уравнение которой входят коэффициенты A, B, C, уклонение вычисляется как значение выражения A*a+B*b+C.
 +
 +В нижеописанной программе определены классы с общей дружественной функцией,​ в основной программе введены объекты этих классов и вычислено уклонение от точки до прямой: ​
 +<​file>​
 +#include <​iostream>​
 +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 << "\n Result"​ << uclon(P,L) << "​\n\n";​
 +}
 +</​file>​
 +====== Дружественная перегрузка ======
 +
 +Дружественная перегрузка.Итак,​ мы рассмотрели дружественные функции и несколько примеров их применения. Однако одним из основных свойств этих специфических функций является то, что с их помощью можно осуществить перегрузку операторов. Такой тип перегрузки носит название дружественной. ​
 +
 +Проиллюстрируем особенности оформления операции-функции в виде дружественной функции класса. ​
 +
 +#include <​iostream>​
 +using namespace std;
 +
 +// класс реализующий работу
 +// с логическим значением
 +class Flag
 +{  ​
 + bool flag;
 + // дружественная функция (перегрузка
 + // оператора ! - замена значения флага
 + // на противоположное)
 + friend Flag& operator !(Flag&​f);​
 + ​public:​
 +
 + // Конструктор.
 + Flag(bool iF)  ​
 +     {
 + flag = iF;
 + }
 + // Компонентная функция показа значения флага ​
 + // в текстовом формате:​
 + void display(){
 + if(flag) cout<<"​\nTRUE\n";​
 + else cout<<"​\nFALSE\n";​
 + }
 +};
 +
 +// Определение дружественной
 +// операции-функции.
 +// (this не передается,​ поэтому 1 параметр)
 +Flag& operator !(Flag & f) 
 +
 + //​замена значения на противоположное
 + f.flag=!f.flag;​
 + return f;
 +}
 +
 +void main()
 +
 + Flag A(true);
 +
 + // показ начального значения
 + A.display();​
 +
 + // замена значения на противоположное
 + // с помощью перегруженного оператора
 + A=!A;
 + // показ измененного значения
 + A.display();​
 +}
 +
 +Результат выполнения программы: ​
 +
 +TRUE
 +
 +FALSE
 +
 +
 + 
 +
 +Глобальная перегрузка.
 +В C++ кроме двух известных вам разновидностей перегрузки (перегрузка в классе и дружественная перегрузка),​ существует еще одно понятие - глобальная перегрузка,​ осуществляемая во внешней области видимости. ​
 +
 +Допустим,​ переменные a и b объявлены как объекты класса C. В классе C определен оператор C::​operator+(C),​ поэтому ​
 +
 +
 +a+b означает a.operator+(b)
 +
 + 
 +
 +Однако,​ также возможна глобальная перегрузка оператора +: 
 +
 +
 +C operator+(C,​C) {....} ​
 +
 + 
 +
 +Такой вариант перегрузки тоже применим к выражению a+b, где a и b передаются соответственно в первом и втором параметрах функции. Из этих двух форм предпочтительной считается перегрузка в классе. Т. к. вторая форма требует открытого обращения к членам класса,​ а это отрицательно отражается на строгой эстетике инкапсуляции. Вторая форма может быть более удобной для адаптации классов,​ которые находятся в библиотеках,​ где исходный текст невозможно изменить и перекомпилировать.То есть добавить в класс перегрузку в качестве метода класса нереально. ​
 +
 +Смешивать эти две формы в программе не рекомендуется. Если для некоторого оператора определены обе формы с одинаковыми типами формальных параметров,​ то использование оператора может создать двусмысленность,​ которая,​ скорее всего, окажется фатальной. ​
 +
 +Тем не менее, глобальная перегрузка операторов обеспечивает симметрию,​ которая также обладает эстетической ценностью. Рассмотрим пример: ​
 +
 +#include <​iostream>​
 +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 = "<<​X<<"​\tY = "<<​Y;​
 + cout<<"​\n+++++++++++++++++++++\n";​
 + }
 +
 + // перегруженный оператор +
 + // метод класса для ситуации 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. ​
 +
 +Чтобы избежать неожиданностей,​ ввод-вывод для абстрактных типов данных должен следовать тем же соглашениям,​ которые используются операциями ввода и вывода для встроенных типов, а именно: ​
 +
 +1. Возвращаемым значением для операций ввода и вывода должна являться ссылка на поток, чтобы несколько операций могли быть выполнены в одном выражении. ​
 +
 +2. Первым параметром функции должен быть поток, из которого будут извлекаться данные,​ вторым параметром - ссылка или указатель на объект определенного пользователем типа. ​
 +
 +3. Чтобы разрешить доступ к закрытым данным класса,​ операции ввода и вывода должны быть объявлены как дружественные функции класса. ​
 +
 +4. В операцию вывода необходимо передавать константную ссылку на объект класса,​ поскольку данная операция не должна модифицировать выводимые объекты. ​
 +
 +Итак, рассмотрим пример подобной перегрузки: ​
 +<​file>​
 +#include <​iostream>​
 +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<<"​Set X\t";
 + is >> P.X;
 + cout<<"​Set Y\t";
 +    is >> 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;​
 +
 + // множественное выражение
 + Point B(0,0);
 + cin>>​A>>​B;​
 + cout<<​A<<​B;​
 +}
 +</​file>​
 +В одном из примеров мы использовали константный метод - метод, который не имеет право изменять поля класса. Однако,​ если какое-то поле объявлено со спецификатором mutable, его значение МОЖНО менять в методе типа const.Дружественная перегрузка.Итак,​ мы рассмотрели дружественные функции и несколько примеров их применения. Однако одним из основных свойств этих специфических функций является то, что с их помощью можно осуществить перегрузку операторов. Такой тип перегрузки носит название дружественной. ​
 +
 +Проиллюстрируем особенности оформления операции-функции в виде дружественной функции класса. ​
 +<​file>​
 +#include <​iostream>​
 +using namespace std;
 +
 +// класс реализующий работу
 +// с логическим значением
 +class Flag
 +{  ​
 + bool flag;
 + // дружественная функция (перегрузка
 + // оператора ! - замена значения флага
 + // на противоположное)
 + friend Flag& operator !(Flag&​f);​
 + ​public:​
 +
 + // Конструктор.
 + Flag(bool iF)  ​
 +     {
 + flag = iF;
 + }
 + // Компонентная функция показа значения флага ​
 + // в текстовом формате:​
 + void display(){
 + if(flag) cout<<"​\nTRUE\n";​
 + else cout<<"​\nFALSE\n";​
 + }
 +};
 +
 +// Определение дружественной
 +// операции-функции.
 +// (this не передается,​ поэтому 1 параметр)
 +Flag& operator !(Flag & f) 
 +
 + //​замена значения на противоположное
 + f.flag=!f.flag;​
 + return f;
 +}
 +
 +void main()
 +
 + Flag A(true);
 +
 + // показ начального значения
 + A.display();​
 +
 + // замена значения на противоположное
 + // с помощью перегруженного оператора
 + A=!A;
 + // показ измененного значения
 + A.display();​
 +}
 +</​file>​
 +
 +Результат выполнения программы: ​
 +
 +TRUE
 +
 +FALSE
 +
 +
 + 
 +
 +====== Глобальная перегрузка. ======
 +В C++ кроме двух известных вам разновидностей перегрузки (перегрузка в классе и дружественная перегрузка),​ существует еще одно понятие - глобальная перегрузка,​ осуществляемая во внешней области видимости. ​
 +<note important>​Глобальная перегрузка - это перегрузка объекта,​ а не метода,​ оператора.</​note>​
 +Допустим,​ переменные a и b объявлены как объекты класса C. В классе C определен оператор C::​operator+(C),​ поэтому ​
 +<​file>​
 +a+b означает a.operator+(b)
 +</​file>​
 +Однако,​ также возможна глобальная перегрузка оператора +: 
 +<​file>​
 +C operator+(C,​C) {....}
 +</​file> ​
 +Такой вариант перегрузки тоже применим к выражению a+b, где a и b передаются соответственно в первом и втором параметрах функции. Из этих двух форм предпочтительной считается перегрузка в классе. Т. к. вторая форма требует открытого обращения к членам класса,​ а это отрицательно отражается на строгой эстетике инкапсуляции. Вторая форма может быть более удобной для адаптации классов,​ которые находятся в библиотеках,​ где исходный текст невозможно изменить и перекомпилировать.То есть добавить в класс перегрузку в качестве метода класса нереально. ​
 +
 +Смешивать эти две формы в программе не рекомендуется. Если для некоторого оператора определены обе формы с одинаковыми типами формальных параметров,​ то использование оператора может создать двусмысленность,​ которая,​ скорее всего, окажется фатальной. ​
 +
 +Тем не менее, глобальная перегрузка операторов обеспечивает симметрию,​ которая также обладает эстетической ценностью. Рассмотрим пример: ​
 +
 +<​file>​
 +#include <​iostream>​
 +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 = "<<​X<<"​\tY = "<<​Y;​
 + cout<<"​\n+++++++++++++++++++++\n";​
 + }
 +
 + // перегруженный оператор +
 + // метод класса для ситуации 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();
 +}
 +</​file>​
 +Без глобальной перегрузки задача int + Point не решается. Поскольку мы не можем получить доступ к "​родному” целому типу (то есть к типу int) и переопределить его операции,​ обеспечить симметрию простым определением операторов класса не удастся. Потребуется решение с глобальными функциями. ​
 +
 +<​note>​Здесь мы могли бы применить дружественную перегрузку,​ и таким образом избавиться от "​функций доступа к private-членам"​. Однако,​ если бы тело класса Point было бы для нас закрыто,​ то вписать в него функцию-друга было бы нереально.</​note> ​
 +====== Перегрузка ввода/​вывода данных. ======
 +Для того, что бы закрепить новую полученную информацию о перегрузке рассмотрим возможность перегрузить операторы << и >>. Для начала немного информации - 
 +Выполнение любой программы С++ начинаются с набором предопределенных открытых потоков,​ объявленных как объекты классов в файле-библиотеке iostream. Среди них есть два часто используемых нами объекта - это cin и cout. 
 +
 +cin - объект класса istream (Потоковый класс общего назначения для ввода, являющийся базовым классом для других потоков ввода) ​
 +cout - объект класса ostream (Потоковый класс общего назначения для вывода,​ являющийся базовым классом для других потоков вывода) ​
 +
 +Вывод в поток выполняется с помощью операции,​ которая является перегруженной операцией сдвига влево << . Левым ее операндом является объект потока вывода. Правым операндом может являться любая переменная,​ для которой определен вывод в поток. Например,​ оператор cout << "​Hello!\n";​ приводит к выводу в предопределенный поток cout строки "​Hello!"​. ​
 +
 +Для ввода информации из потока используется операция извлечения,​ которой является перегруженная операция сдвига вправо >>. Левым операндом операции >> является объект класса istream. ​
 +
 +Чтобы избежать неожиданностей,​ ввод-вывод для абстрактных типов данных должен следовать тем же соглашениям,​ которые используются операциями ввода и вывода для встроенных типов, а именно: ​
 +
 +  - Возвращаемым значением для операций ввода и вывода должна являться ссылка на поток, чтобы несколько операций могли быть выполнены в одном выражении.
 +  - Первым параметром функции должен быть поток, из которого будут извлекаться данные,​ вторым параметром - ссылка или указатель на объект определенного пользователем типа.
 +  - Чтобы разрешить доступ к закрытым данным класса,​ операции ввода и вывода должны быть объявлены как дружественные функции класса. ​
 +  - В операцию вывода необходимо передавать константную ссылку на объект класса,​ поскольку данная операция не должна модифицировать выводимые объекты. ​
 +
 +Итак, рассмотрим пример подобной перегрузки: ​
 +<​file>​
 +#include <​iostream>​
 +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<<"​Set X\t";
 + is >> P.X;
 + cout<<"​Set Y\t";
 +    is >> 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;​
 +
 + // множественное выражение
 + Point B(0,0);
 + cin>>​A>>​B;​
 + cout<<​A<<​B;​
 +}
 +</​file>​
 +<​note>​В одном из примеров мы использовали константный метод - метод, который не имеет право изменять поля класса. Однако,​ если какое-то поле объявлено со спецификатором mutable, его значение МОЖНО менять в методе типа const.</​note>​
 +====== Дружественные классы ======
 +Пора узнать,​ что "​дружить"​ могут не только функции. Класс тоже может быть дружественным другому классу. ​
 +
 +Особенности "​дружбы"​ между классами.
 +  - Дружественный класс должен быть определен вне тела класса,​ "​предоставляющего дружбу"​.
 +  - Все компонентные функции класса-друга будут являться дружественными для другого класса без указания спецификатора friend.
 +  - Все компоненты класса доступны в дружественном классе,​ но не наоборот.
 +  - Дружественный класс может быть определен позже (ниже), чем описан как дружественный.
  
druzhestvennye_funkcii_klassy._druzhestvennaja_peregruzka.txt · Последние изменения: 2010/12/18 19:06 (внешнее изменение)