Дружественные функции и классы 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Дружественные функции и классы



Дружественные функции

Кроме спецификаторов доступности public, private, protected и ключей класса? доступностью компонент класса можно управлять через механизм дружественных функций и классов. Под дружественной функцией (friend) класса понимается функция, которая, не являясь компонентом этого класса, имеет доступ ко всем его компонентам, включая защищенные и закрытые. В свою очередь, указанный класс принято называть классом, предоставляющим «дружбу» (befriended class).

Чтобы сделать функцию дружественной, надо включить ее заголовок с предшествующим ему ключевым словом friend в требуемый класс. В остальном дружественная функция – это обычная функция. Она может быть компонентом другого класса или глобальной функцией, а также определяться в каком-либо пространстве имен. Дружественная функция не является компонентом класса, к которому объявлена дружественной, и поэтому при работе с объектами этого класса не получает указателя this. Соответственно, для ее вызова нельзя применять ни операцию «.», ни операцию «–>» (исключение составляет лишь случай, когда дружественная функция – компонент другого класса), а чтобы обеспечить работу функции с объектами класса, их нужно передавать в нее через механизм параметров. На дружественную функцию не действуют спецификаторы доступности public, protected и private; следовательно, и размещение ее прототипа в классе, предоставляющем «дружбу», безразлично. Если же дружественная функция входит в качестве компонентной в другой класс, на нее действуют все ограничения ее собственного класса. Функция может быть дружественной по отношению сразу к нескольким независимым или взаимосвязанным классам.

В рассмотренном ниже примере используется глобальная дружественная функция.

Пример

class CBefriended { // класс, предоставляющий «дружбу»

int i;

// объявление дружественной функции

friend void friend_func(CBefriended *, int);

public:

void member_func(int); // компонентная функция

};

/* внешние определения */

// операция:: не нужна, так как friend_func не входит в CBefriended

void friend_func(CBefriended * ptr, int a) {

// доступ к объекту CBefriended выполняется через указатель ptr

ptr–>i = a;

}

void CBefriended::member_func(int a) { // компонентная функция

this->i = a;

}

/* вызовы */

void another_func() {

CBefriended befriended;

friend_func(&befriended, 6); // вызов дружественной функции

befriended.member_func(6); // вызов компонентной функции

}

Ниже дан пример с дружественной компонентной функцией.

Пример

Class CBefriended; // класс, предоставляющий «дружбу»

class CFriendHolder { // класс, содержащий дружественную функцию

public:

int friend_member_func(CBefriended * p);

// без предварительного объявления CBefriended не компилируется

};

class CBefriended { // класс, предоставляющий «дружбу»

int i;

// декларация дружественной функции

friend int CFriendHolder::friend_member_func(CBefriended * p);

};

// внешнее определение дружественной функции

int CFriendHolder::friend_member_func(CBefriended * p) { return p->i; }

...

void another_func() {

CBefriended befriended;

CFriendHolder friend_holder;

// вызов дружественной компонентной функции

int i = friend_holder.friend_member_func(&befriended);

...

}

Аналогичным образом можно сделать одну и ту же функцию дружественной сразу по отношению к нескольким независимым классам. Возможно введение дружественных функций и к классам, связанным иерархией наследования. Однако функцию необходимо отдельно объявлять дружественной по отношению к каждому классу: дружественность не передается при наследовании. Например, функция f дружественна к классу C, а у класса C есть открытый производный класс D. В этом случае функция f не будет другом к классу D без явного объявления. Если функция имеет несколько вариантов перегрузки, то объявление одного из них дружественным к какому-либо классу не делает автоматически таковыми другие варианты. И еще одна тонкость. Дружественную функцию иногда можно сразу определить в классе, предоставляющем дружбу. Это возможно при условии, что класс не локальный, а функция не является компонентом другого класса и представлена неквалифицированным именем. Определенная таким образом функция будет inline-функцией и лексически будет принадлежать сфере действия имен класса, дающего «дружбу».


Дружественные классы

Механизм дружественности в С++ допускает устанавливать отношение дружественности между классами. Под дружественным классом для некоторого класса C понимается класс, не входящий в число компонент класса C, но имеющий доступ ко всем его компонентам независимо от их уровня доступности. Все компонентные функции дружественного класса становятся также дружественными, хотя и не объявляются отдельно со спецификатором friend.

Чтобы сделать некоторый класс Y дружественным к классу X, нужно объявить его со спецификатором friend в теле класса X. В результате все компонентные функции класса Y получат доступ к закрытой и защищенной части класса X.

Ниже приведен фрагмент программы с использованием дружественного класса. В программе есть два класса. Первый (CPointN) представляет точку в N-мерном пространстве. Второй (CVectorN) моделирует вектор в N-мерном пространстве. Класс CVectorN для реализации интенсивно использует класс CPointN и поэтому объявляется к последнему дружественным. (Для упрощения в классы введены только некоторые из возможных компонентных функций.)

class CPointN { // точка

int __N; // мерность пространства

double * __Coords; // массив координат точки

Friend class CVectorN; // объявление дружественного класса

public:

CPointN(int n, double d); // конструктор

~CPointN(); // деструктор

...

};

class CVectorN { // вектор

int __N; // мерность пространства

double * __Coords; // массив координат конца вектора

public:

CVectorN(const CPointN &, const CPointN &); // конструктор

~CVectorN(); // деструктор

Double Norm() const; // норма вектора

...

};

// это пишем в файле реализации

// конструктор точки

CPointN::CPointN(int n, double d) {

__N = n; // определение мерности

// формирование массива координат

__Coords = new double[__N];

for(int i = 0; i < __N; i++) __Coords[i] = d;

}

// деструктор точки

CPointN::~CPointN() {

delete[] __Coords;

}

// конструктор вектора

CVectorN::CVectorN(const CPointN & s, const CPointN & d) {

__N = s.__N; // установка мерности вектора

__Coords = new double[__N]; // формирование массива координат

for(int i = 0; i < __N; i++)

__Coords[i] = d.__Coords[i] – s.__Coords[i];

}

// деструктор вектора

CVectorN::~CVectorN() {

delete[] __Coords;

}

double CVectorN::Norm() const {

double norm = 0.0;

for(int i = 0; i < __N; i++) norm += __Coords[i] * __Coords[i];

return norm;

}

...

За счет дружественности в конструкторе класса CVectorN происходит прямое обращение к компонентам точек.

Следует отметить, что дружественный класс не получает доступа к закрытым и защищенным компонентам вложенных классов для класса, дающего «дружбу». Отношение дружественности классов не является транзитивным, т.е. из того что X дружествен к Y, а Y дружествен к Z, не следует, что X будет дружественным к Z. Оно также не передается при наследовании, и чтобы сделать класс дружественным к иерархии классов, нужно объявить его таковым к каждому из них. Если класс объявляется дружественным к вложенному классу, то он также должен быть вложенным в ту же область действия имен, что и класс, предоставляющий «дружбу». Доступа к закрытым и защищенным компонентам объемлющего класса этот дружественный класс не имеет.

Рассматриваемый в следующей главе механизм вложения является еще одним способом определения отношений между классами и в сочетании с механизмом дружественности способен обеспечить весьма элегантные и эффективные решения.

 


Вопросы для самопроверки

 

1. Какие функции принято называть дружественными?

2. Как описать и определить дружественную функцию?

3. Опишите случаи, когда только механизм дружественных функций позволяет правильно реализовать требуемую функциональность.

4. В каком случае при вызове дружественной функции можно использовать операции ‘.’, ‘–>’, ‘.*’, ‘–>*’? Поясните ответ.

5. Каким образом можно определить дружественную функцию к вложенному классу? Приведите пример.

6. Как дружественная функция получает доступ к компонентам объекта класса, предоставляющего «дружбу»?

7. Класс C объявлен дружественным к классу B, а класс B является дружественным к классу A. Как сделать класс C дружественным к классу A? Приведите эскиз кода.

8. Можно ли сделать некоторый класс дружественным к иерархии классов? Если можно, то как? Поясните ответ примером.

9. Имеется класс class C { class A {}; class B {}; }; Как обеспечить неограниченный доступ классов C::A и C::B друг к другу?

10. Как можно сделать функцию, дружественную по отношению к иерархии классов, виртуальной к этой иерархии? Приведите фрагмент кода.

11. Каким образом можно имитировать «наследование» дружественности функций в классовых иерархиях? Приведите фрагмент кода.

 

Задачи

 

1. Разработать класс CSegment, представляющий отрезки на плоскости, а также класс CSquare, описывающий квадраты. Квадрат реализовать на основе четырех отрезков. При этом для обеспечения эффективности использовать отношение дружественности между классами CSquare и CSegment.

2. Определить класс CSheet, описывающий лист бумаги, на котором можно делать заметки. Также разработать класс CNotebook, отображающий свойства виртуальной записной книжки. Для реализации последнего использовать функциональность класса CSheet. Для обеспечения эффективности установить отношение дружественности между классами CSheet и CNotebook.

 


Механизм вложения

Любой класс в качестве компонента может содержать другой класс. Такое отношение, называемое отношением вложенности, обычно используется тогда, когда один класс (объемлющий) реализуется посредством другого (вложенного). Примером является класс, представляющий квадраты на плоскости (объемлющий), и класс, описывающий отрезки (вложенный), посредством которого можно реализовать класс квадратов (см. задачу 1 из главы 3). Еще один пример. Объемлющим классом является класс, описывающий матрицы. Вложенный – класс, описывающий одномерные массивы. Одномерный массив может представлять строку объекта-матрицы. Соответственно, компоненты такого массива могут использоваться матрицей для реализации ее функциональности (так, одномерный массив может включать функцию для суммирования всех элементов; в матрице эта функция будет суммировать элементы строки).

Вложенные классы

Вложенный класс можно определить непосредственно в объемлющем классе или за его пределами, но в том же пространстве имен. Его функции можно определять как внутри класса, так и за его пределами, в пространстве имен объемлющего класса. А его статические компоненты определяются только за пределами объемлющего класса. Внешнее определение вложенного класса и его компонент должно включать полную квалификацию имен.

Представленный ниже пример иллюстрирует возможные способы определения вложенных классов и их компонент.

Пример

class CEnclosing { // определение объемлющего класса

public:



Поделиться:


Последнее изменение этой страницы: 2016-08-14; просмотров: 643; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.116.63.236 (0.038 с.)