Дружественные функции
Дружественной функцией класса называется функция, которая, не являясь его компонентом, имеет доступ к его собственным (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 <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"; }
Дружественная перегрузка
Дружественная перегрузка.Итак, мы рассмотрели дружественные функции и несколько примеров их применения. Однако одним из основных свойств этих специфических функций является то, что с их помощью можно осуществить перегрузку операторов. Такой тип перегрузки носит название дружественной.
Проиллюстрируем особенности оформления операции-функции в виде дружественной функции класса.
#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. В операцию вывода необходимо передавать константную ссылку на объект класса, поскольку данная операция не должна модифицировать выводимые объекты.
Итак, рассмотрим пример подобной перегрузки:
#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; }
В одном из примеров мы использовали константный метод - метод, который не имеет право изменять поля класса. Однако, если какое-то поле объявлено со спецификатором mutable, его значение МОЖНО менять в методе типа const.Дружественная перегрузка.Итак, мы рассмотрели дружественные функции и несколько примеров их применения. Однако одним из основных свойств этих специфических функций является то, что с их помощью можно осуществить перегрузку операторов. Такой тип перегрузки носит название дружественной.
Проиллюстрируем особенности оформления операции-функции в виде дружественной функции класса.
#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) и переопределить его операции, обеспечить симметрию простым определением операторов класса не удастся. Потребуется решение с глобальными функциями.
Перегрузка ввода/вывода данных.
Для того, что бы закрепить новую полученную информацию о перегрузке рассмотрим возможность перегрузить операторы « и ». Для начала немного информации - Выполнение любой программы С++ начинаются с набором предопределенных открытых потоков, объявленных как объекты классов в файле-библиотеке iostream. Среди них есть два часто используемых нами объекта - это cin и cout.
cin - объект класса istream (Потоковый класс общего назначения для ввода, являющийся базовым классом для других потоков ввода) cout - объект класса ostream (Потоковый класс общего назначения для вывода, являющийся базовым классом для других потоков вывода)
Вывод в поток выполняется с помощью операции, которая является перегруженной операцией сдвига влево « . Левым ее операндом является объект потока вывода. Правым операндом может являться любая переменная, для которой определен вывод в поток. Например, оператор cout « "Hello!\n"; приводит к выводу в предопределенный поток cout строки "Hello!".
Для ввода информации из потока используется операция извлечения, которой является перегруженная операция сдвига вправо ». Левым операндом операции » является объект класса istream.
Чтобы избежать неожиданностей, ввод-вывод для абстрактных типов данных должен следовать тем же соглашениям, которые используются операциями ввода и вывода для встроенных типов, а именно:
- Возвращаемым значением для операций ввода и вывода должна являться ссылка на поток, чтобы несколько операций могли быть выполнены в одном выражении.
- Первым параметром функции должен быть поток, из которого будут извлекаться данные, вторым параметром - ссылка или указатель на объект определенного пользователем типа.
- Чтобы разрешить доступ к закрытым данным класса, операции ввода и вывода должны быть объявлены как дружественные функции класса.
- В операцию вывода необходимо передавать константную ссылку на объект класса, поскольку данная операция не должна модифицировать выводимые объекты.
Итак, рассмотрим пример подобной перегрузки:
#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; }
Дружественные классы
Пора узнать, что "дружить" могут не только функции. Класс тоже может быть дружественным другому классу.
Особенности "дружбы" между классами.
- Дружественный класс должен быть определен вне тела класса, "предоставляющего дружбу".
- Все компонентные функции класса-друга будут являться дружественными для другого класса без указания спецификатора friend.
- Все компоненты класса доступны в дружественном классе, но не наоборот.
- Дружественный класс может быть определен позже (ниже), чем описан как дружественный.
📌 Для тестирования скриптов, установщиков VPN, Python ботов рекомендуем использовать надежные VPS на короткий срок. Если вам нужна помощь с более сложными задачами, вы можете найти фрилансера, который поможет с настройкой. Узнайте больше о быстрой аренде VPS для экспериментов и о фриланс-бирже для настройки VPS, WordPress. 📌
💥 Подпишись в Телеграм 💥 и задай вопрос по сайтам и хостингам бесплатно!
7 Самых Популярных Статей
- Как запустить скрипты и веб-приложения на Python
- Что такое страны TIER 1,2,3
- 7 способов сравнения файлов по содержимому в Windows или Linux
- Установка и тестирование веб-панели HestiaCP
- Китайский VPN Shadowsocks простая установка и настройка
- top, htop, atop определение загрузки ОС (Load average, LA)
- Использование rsync в примерах