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

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


druzhestvennye_funkcii_klassy._druzhestvennaja_peregruzka

Различия

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

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

druzhestvennye_funkcii_klassy._druzhestvennaja_peregruzka [2010/12/18 20:06]
druzhestvennye_funkcii_klassy._druzhestvennaja_peregruzka [2020/06/13 13:45] (текущий)
Строка 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.
 +  - Все компоненты класса доступны в дружественном классе, но не наоборот.
 +  - Дружественный класс может быть определен позже (ниже), чем описан как дружественный.