Дружественность не наследуется. Если A объявляет класс
B своим другом, то потомки B не становятся автоматически друзьями A. Для этого каждый из них должен быть объявлен другом A в явной форме.
Дружественность не распространяется на потомков.
Если класс A объявляет B другом, то B не становится автоматически другом для классов-потомков A. Каждый потомок, если это нужно, должен объявить B своим другом самостоятельно.
При разработке программ случается, что одних функций-методов для доступа к личным полям не хватает. Это не обязательно признак просчета
28
при разработке классов, просто некоторые проблемы невозможно решить с помощью функций-членов. Например, что делать, если функции требуется полный доступ к полям двух классов?
Для того, чтобы в подобных случаях не приходилось делать поля публичными, С++ позволяет классу объявлять друзей (friends) - функции и классы, у которых есть полный доступ к начинке класса (такой же доступ, как у его собственных функций). Делается это с помощью ключевого слова friend. В приведенном ниже примере в классе A определяются friend-функция print_fields(A a) и friend-класс B.
//
|
Forward
|
declaration
|
|
of
|
|
class
|
B;
|
class
|
|
|
|
|
|
|
|
|
|
B;
|
//
|
|
class
|
|
|
|
A
|
|
|
definition
|
class
|
|
|
|
|
A
|
|
|
|
|
{
|
int
|
|
|
|
|
n1,
|
|
|
|
|
n2;
|
friend
|
|
void
|
|
|
print_fields(A
|
|
a);
|
friend
|
|
|
|
|
class
|
|
|
|
B;
|
public:
|
|
|
|
|
|
|
|
|
|
|
set_fields(n_1,n_2)
|
|
|
|
|
|
|
|
{
|
n1
|
|
|
|
|
=
|
|
|
|
|
n_1;
|
n2
|
|
|
|
|
=
|
|
|
|
|
n_2;
|
}
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
void
|
|
print_fields(A
|
|
|
a)
|
|
{
|
cout
|
<<
|
a.n1
|
<<
|
"
|
"
|
<<
|
a.n2
|
<<
|
endl;
|
}
|
|
|
|
|
|
|
|
|
|
|
Функция print_fields(A a) имеет полный доступ к личным полям класса A, поскольку она объявлена friend-функцией класса. Заметьте - friend-объявление распространяется только на функцию с указанной сигнатурой, в нашем случае print_fields(A), а не на все функции с именем print_fields. Что касается объявления
friend class B;
то оно делает поля класса A доступными для всех функций-членов класса B.
Функции и классы-друзья незаменимы там, где разные типы данных тесно связаны между собой, но реализовывать их в одном классе невыгодно или просто невозможно. Хороший пример этому - С++-стиль ввода-вывода, в котором именно дружественные функции позволяют разрабатывать версии операторов << и >> для вновь создаваемых классов.
29
Тема «Перегрузка операций (операторов) для классов. Функции-операции. Бинарные операции. Унарные операции»
Перегрузка - это возможность поддерживать несколько функций с одним названием, но разными сигнатурами вызова. Рассмотрим пример:
double sqrt ( double x ); //Функция корня для чисел с плавающей точкой int sqrt ( int x ); //Функция корня для целых чисел
...
sqrt(1.5);//В этом случае вызовется функция чисел с плавающей точкой sqrt(7);//А в этом уже для целых чисел
Компилятор сам определяет какую функцию выбрать в зависимости от сигнатуры. Более подробно о том, как компилятор определяет какую из перегруженных функций выбрать, можно прочесть в стандарте языка С++.
Пример операторов:
+, <<, & и т.д.
Оператор в С++ - это некоторое действие или функция обозначенная специальным символом. Для того что бы распространять эти действия на новые типы данных, при этом сохраняя естественный синтаксис, в С++ была введена возможность перегрузки операторов.
Список операторов:
- * / % //Арифметические операторы
+= -= *= /= %=
+a -a //Операторы знака
++a a++ --a a-- //Префиксный и постфиксный инкременты && || ! //Логические операторы
& | ~ ^ &= |= ^=
<< >> <<= >>= //Битовый сдвиг = //Оператор присваивания == != //Операторы сравнения
< > >= <=
Операторная функция, с помощью которой перегружается оператор, может быть внутренней, то есть являться методом класса. Принципиально ситуация мало отличается от рассмотренных ранее, однако есть ряд особенностей, главная из которых связана со способом передачи аргументов такой функции.
Строго говоря, изменения происходят только с количеством аргументов в операторных функциях - при перегрузке бинарных операторов
30
функции передается один аргумент, а при перегрузке унарных операторов функции аргументы не передаются (за исключением, пожалуй, перерузки префиксной формы операторов инкремента и декремента). Причина связана с тем, что один аргумент внутренней операторной функции - объект, из которого вызывается эта функция. Доступ к этому объекту, в случае необходимости, можно получить через указатель. В качестве иллюстрации рассмотрим, как будет выглядеть процедура перегрузки оператора сложения, если перегрузка осуществляется методом класса. Программный код приведен ниже.
#include
using namespace std;
//Класс для реализации комплексных чисел:
class MComp{
public:
double Re;
double Im;
//Переопределение оператора сложения методом класса:
MComp operator+(MComp y){
MComp z;
z.Re=Re+y.Re;
z.Im=Im+y.Im;
return z;}
};
int main(){
MComp a,b,c;
a.Re=1;
a.Im=2;
b.Re=2;
b.Im=3;
//Сложение объектов:
c=a+b;
cout << "c.Re = " << c.Re << "\n";
cout << "c.Im = " << c.Im << "\n";
return 0;
}
Тема «Полиморфизм и виртуальные функции»
Семантика системы типов С++ не полиморфна, однако есть несколько способов обеспечить полиморфное поведение. Прежде всего это перегрузка методов классов при наследовании — традиционный для ООП способ обеспечения абстракции данных
Для обеспечения абстракции данных необходимо связать несколько классов в иерархию наследования и назначить функциям одинаковые спецификации. Например:
class Figure
{
void Draw() const;
};
class Square : public Figure
{
void Draw() const;
};
31
class Circle : public Figure
{
void Draw() const;
};
class Window
{
void Draw() const;
};
результате компиляции этих определений формируется шесть тел функций. В коде они используются одинаково; выбор конкретного экземпляра функции осуществляется в зависимости от типа экземпляра объекта, для которого осуществляется вызов. Согласованность поведения функций остаётся на совести программиста.
Circle *c = new Circle(0,0,5);
Figure *f = c; // правильно: Figure — базовый класс для Circle c->Draw();
f->Draw(); // Указатели равны друг другу, но для f и c будут вызваны разные функции
SquareWindow *sw = new Window(0,0,5);
sw->Draw(); // используется так же
f = sw; // ошибка! SquareWindow не входит в число наследников Figure!
Как видно, диапазон этого вида полиморфизма в С++ ограничивается на этапе проектирования заданным перечнем типов. По умолчанию такой полиморфизм является статическим, но при использовании спецификатора virtual он превращается в динамический:
class Figure
{...
virtual void Draw() const;
};
class Square : public Figure
{
void Draw() const;
};
class Circle : public Figure
{...
void Draw() const;
};
Figure* figures[10];
figures[0] = new Square(1, 2, 10);
figures[1] = new Circle(3, 5, 8);
...
for (int i = 0; i < 10; i++)
figures[i]->Draw();
этом случае для каждого элемента массива будет вызвана
Square::Draw() или Circle::Draw(), в зависимости от вида фигуры.
Чистой виртуальной функцией называется виртуальная функция-член, которая объявлена со спецификатором = 0 вместо тела:
32
{
...
virtual void Draw() const = 0;
);
Чистая виртуальная функция не имеет определения и не может быть непосредственно вызвана. Цель объявления такой функции — создать в общем базовом классе сигнатуру-прототип, которая не имела бы собственного определения, но позволяла создавать такие определения в классах-потомках и вызывать их через указатель на общий базовый класс. Функция-метод объявляется чистой виртуальной тогда, когда её определение для базового класса не имеет смысла.
Так, в вышеприведённом примере для базового класса Figure определение функции Draw() не имеет смысла, так как «фигур вообще» не бывает и их невозможно отобразить, но описание такой функции необходимо, чтобы можно было её переопределить в классах-потомках и вызывать методы Draw этих классов через указатель на Figure. Следовательно, вполне логично объявить Figure.Draw() как чистую виртуальную функцию-метод.
понятием чистой виртуальной функции в C++ тесно связано понятие «абстрактный класс». Абстрактным классом называется такой, у которого есть хотя бы одна не переопределённая чистая виртуальная функция-метод. Экземпляры таких классов создавать запрещено, абстрактные классы могут использоваться только для порождения новых классов путём наследования. Если в классе-потомке абстрактного класса не переопределены все унаследованные чистые виртуальные функции, то такой класс также является абстрактным и на него распространяются все указанные ограничения.
Do'stlaringiz bilan baham: |