Дружественной функцией класса называется функция, которая, не являясь его компонентом, имеет доступ к его собственным (private) и защищенным (protected) компонентам. Функция не может стать другом класса "без его согласия". Для получения прав друга функция должна быть описана в теле класса со спецификатором friend. Именно при наличии такого описания класс предоставляет функции права доступа к защищенным и собственным компонентам.
имя_объекта.имя_функции указатель_на_объект -> имя_функции
Использование механизма дружественных функций позволяет упростить интерфейс между классами. Например, дружественная функция позволит получить доступ к собственным или защищенным компонентам сразу нескольких классов. Тем самым из классов можно иногда убрать компонентные функции, предназначенные только для доступа к этим "скрытым" компонентам.
В качестве примера рассмотрим дружественную функцию двух классов "точка на плоскости" и "прямая на плоскости".
В нижеописанной программе определены классы с общей дружественной функцией, в основной программе введены объекты этих классов и вычислено уклонение от точки до прямой:
#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;
}
Пора узнать, что "дружить" могут не только функции. Класс тоже может быть дружественным другому классу.
Особенности "дружбы" между классами.