Дружественные функции. Перегрузка операторов.


4.1. Дружественные классы.
В классе можно объявить другой класс дружественным. Один класс (в котором объвляется друг) дает другому классу (другу) возможность доступа ко всем закрытым членам первого класса. Обычно дружественные классы используются, когда двум классам, не связанным отношением родства, необходим доступ к закрытым или защищенным членам класса.
class One {
friend class Two; //класс Two друг класса One
private:
double value; //закрытый член класса
public:
One() { value = 3.14159; }
};
class Two {
private:
One object;
public:
//доступ к закрытому члену value
void ShowVal(void) { cout << object.value; }
};
— В классе должны быть заранее перечисленны все его друзья.
— Класс, в котором содержаться закрытые и защищенные дан-ные и в котором объявлен другой класс другом, дает возможность другу специальный доступ к обычно скрытым членам. Класс не может объявлять себя другом другого класса.
— Дружественный класс может объявляться до или после класса, которому он друг. Порядок объявления не играет роли, однако дружественные классы обычно объявля-ется последними, чтобы встраиваемые функции класса могли обращаться к закрытым или защищенным членам класса.
— Производные от дружественных классов не наследуют специального доступа к первоначально закрытым и защищенным членам исходного класса.
— Производный класс может быть другом своего базового класса, хотя в этом случае использование членов базо-вого класса и предоставление дружественного доступа приводит к тем же результатам.

//Программа 1 Пример объявления дружественного класса

#include <iostream.h>
class One {
friend class Two; //класс Two друг класса One
private:
int x;
protected:
void doublex(void) { x *= x; } //доступен One и Two
public:
One() { x = 100; } //конструктор 1
One(int n) { x = n; } //конструктор 2
};
class Two {
private:
One object; //доступен только Two
public:
void ShowVal(void); //доступна всем
};
void main()
{
Two aTwo;
aTwo.ShowVal();
}
void Two::ShowVal(void)
{
One aOne(1234);
cout << endl << «Before, object.x == » << object.x;
object.doublex();
cout << endl << «After, object.x == » << object.x;
cout << endl << «aOne.x == » << aOne.x << endl;
}
Два класса могут объявить себя взаимнодружественными клас-сами.

class Two;
class One {
friend class Two;
. . . . . . . . .
};
class Two {
friend class One;
. . . . . . . . .
};

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

Дружественные функции похожи, но более безопасны, чем дружественные классы. Объявление функции дружественной классу дает ей доступ к закрытым и защищенным членам класса.

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

#include <iostream.h>
class Two;
class One {
friend void Show(One& c1, Two& c2);//Дружественная функция
private: //двум классам
char *s1;
public:
One() { s1 = «Testing «; }
};
class Two {
friend void Show(One& c1, Two& c2);
private:
char *s2;
public:
Two() { s2 = «one, two, three»; }
};
void main()
{
One c1;
Two c2;
Show(c1,c2);
}
void Show(One& c1, Two& c2)
{
cout << c1.s1 << c2.s2 << endl;
}
Неполное объявление класса Two позволяет в классе One ссылаться на класс Two.
Дружественные функции могут быть членами класса. Обычно в классе объявляется дружественной функция-член другого класса. Она имеет доступ к закрытым и защищенным членам класса, в котором она объявлена дружественной.

// Программа 3

#include <iostream.h>
class One;
class Two {
private:
char *s2;
public:
Two() { s2 = «one, two, three»; }
void Show(One& c1);
};
class One {
friend void Two::Show(One& c1);// Дружественная функция-
// член
private:
char *s1;
public:
One() { s1 = «Testing «; }
};
void main()
{
One c1;
Two c2;
c2.Show(c1);
}
void Two::Show(One& c1)
{
cout << c1.s1 << s2 << endl;
}
Порядок объявления классов изменился, так как класс в котором содержиться прототип функции должен быть объявлен раньше, чем класс, в котором функция объявлена дружествен-ной.

4.3. Перегрузка операторов.

Перегрузка операторов дает возможность добавлять к встроенным типам данных в С++ новые типы. В зависимости от назначения перегрузка операторов объявляется так же, как и обычная дружественная функция или функция-член клас-са.
class ZZ {
public:
friend ZZ operator+(ZZa, ZZb);
friend ZZ operator-(ZZa, ZZb);
friend ZZ operator*(ZZa, ZZb);
friend ZZ operator/(ZZa, ZZb);
};
Имена функций — operator/, operator*, operator-, operator+ состоят из ключевого слова operator и символа операции.

Список операций, которые могут быть пререгружены
* / + — % ^ & :
— ! , = < > <= >=
++ — << >> == != && || *= /* %= ^= &= |= += -=
<<= >>= -> ->* [] () new delete

operator+(a, b); аналогично a + b;

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

// Программа 4 Пример перегрузки операторов с помощью
// дружественных функций

#include <iostream.h>
#include <stdlib.h>
#include <string.h>
class TStrOp {
private:
char val[12];
public:
TStrOp() { val[0]=0; }
TStrOp(const char* s);
long GetVal(void) { return atol(val);}
friend long operator+(TStrOp a, TStrOp b);
friend long operator-(TStrOp a, TStrOp b);
};
void main()
{
TStrOp a = «1234»;
TStrOp b = «4321»;
cout << endl << «value of a == » << a.GetVal();
cout << endl << «value of b == » << b.GetVal();
cout << endl << » a + b + 6 == » << ( a + b + 6 );
cout << endl << » a — b + 10 == » << ( a — b + 10 )
<< endl;
}
TStrOp::TStrOp(const char* s)
{
strncpy(val, s, 11);
val[11]=0;
}
long operator+(TStrOp a, TStrOp b)
{
return (atol(a.val) + atol(b.val));
}
long operator-(TStrOp a, TStrOp b)
{
return (atol(a.val) — atol(b.val));
}
Перегруженные функции могут быть членами класса. Отличие состоит только в определении функции, где указыва-ется членом какого класса является данная перегружаемая функция.

class TStrOp {
private:
char val[12];
public:
TStrOp() { val[0]=0; }
TStrOp(const char* s);
long GetVal(void) { return atol(val);}
long operator+(TStrOp a, TStrOp b);
long operator-(TStrOp a, TStrOp b);
};
long TStrOp::operator+(TStrOp b)
{
return (atol(val) + atol(b.val));
}
long TStrOp::operator-(TStrOp b)
{
return (atol(val) — atol(b.val));
}

4.3.1. Перегрузка унарных операторов

Унарные операторы могут быть объявлены как функции-члены, не имеющие аргументов, так и дружественными функ-циями.

long operator -(void) //Унарный минус
{ return -atol(val); }

friend long operator-(TStrOp a)
{ return -atol(a.val); }

4.3.2. Оператор индексирования массива.

Можно перегрузить оператор индексирования массива [] для реализации доступа к данным-членам класса, подобного доступу к элементу массива, даже если эти данные имеют вид отдельных членов класса или связного списка.

// Программа 5 Пример перегрузки индексирования

#include <iostream.h>
class Psevdoarray {
private:
int val0;
int val1;
int val2;
int val3;
public:
Psevdoarray( int v0, int v1, int v2, int v3)
{ val0 = v0; val1 = v1; val2 = v2; val3 = v3;}
int GetInt(unsigned i);
int operator[] (unsigned i);
};
void main()
{
Psevdoarray pa(10, 20, 30,40);
for( int i=0; i<=3; i++)
cout << «pa[» << i << «] ==» << pa[i] << endl;
}
int Psevdoarray::GetInt(unsigned i)
{
switch (i) {
case 0: return val0;
case 1: return val1;
case 2: return val2;
case 3: return val3;
default: return val0;
}
}
int Psevdoarray::operator[](unsigned i)
{
return GetInt(i);
}

4.3.3. Оператор вызова функции

Перегрузка вызова функции operator() делает объект класса похожим на функцию, которую можно вызывать. Пере-груженная функция может возвращать значения заданных типов или ничего не возвращать. Кроме того в ней могут быть объявлены параметры.

class X {
int x;
public:
int operator()(void);
X(int n) { x=n; }
};
int X::operator()(void)
{
return x;
}

main()
{
X object = 100;
int g == object(); //выглядит как вызов функции
cout << q; //на самом деле object.operator()();
return 0;
}

4.3.4. Перегрузка операторов new и delete

Для перегрузки new следует использовать прототип функ-ции вида: void * operator new(size_t size); В дальнейшем обращение к оператору new для выделения памяти объектам класса будут перенаправлены замещающей функции. Функция должна возвращать адрес памяти, выделенной объекту. Вместо кучи можно выделять память из статического буфера, на диске или другом запоминающем устройстве.

// Программа 6
#include <iostream.h>
class DemoNew {
private:
int x;
public:
DemoNew();
void * operator new(size_t size);
};
char buf[512];
int index;
void main()
{
cout << endl << «Creating local instance»;
DemoNew b1;
cout << endl << «Allocating space via new»;
DemoNew *b2 = new DemoNew;
DemoNew *b3 = new DemoNew;
DemoNew *b4 = new DemoNew;
DemoNew *b5 = new DemoNew;
}
DemoNew::DemoNew()
{
cout << endl << «Inside constructor»;
x = index;
}
void *DemoNew::operator new(size_t size)
{
cout << endl << «Inside overloaded new. Size == «<< size;
if ( index >=512 — sizeof(DemoNew)) //Проверка наличия
return 0; //пространства в buf
else {
int k = index;
index+= sizeof(DemoNew);
return &buf[k];
}
}
Оператор delete служит для удаления объектов, адресован-ных указателями. Прототип функции перегрузки оператора delete должен иметь вид: void operator delete(void *p); , где р ссылается на удаляемый объект.
void operator delete(void *p);

void DemoNew::operator delete(void *p)
{
cout << endl << «Deleting object at «<< p;
}
Поскольку объекты не запоминаются в куче, то перегру-женный оператор ничего не освобождает.

4.4. Лабораторная работа 4

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

1? Порядок выполнения работы.

1. Ознакомьтесь с теоретической частью.
2. Разберитесь с работой программ 1 — 5.
3. В программе 6 вставьте перегружаемый оператор delete. Откомпилируйте программу и разберите ее работу в отладочном режиме.
4. Выполните индивидуальное задание.

2? Индивидуальные задания.

1. Определите класс string. Для него определите операцию сцепления «+» и операцию «добавить к концу» («+=»). Какие другие операции могли бы быть выполнены над string.
2. Определите класс DATA, для которого выполняются операции «+», «-«. Создайте две версии с использованием дружественных функций и функций-членов. Сравните два эти варианта.
3. Определите класс string так, что операцию получения подстроки можно использовать с левой стороны присваивания. Сначала напишите версию, в которой строку можно присвоить подстроке той же самой длины, а затем версию, в которой длины могут быть различны.
4. Определить класс INT, который ведет себя точно так же, как int. Подсказка: определите INT::operator int( ).
5. Определите класс TIME, для которого выполняются операции «+», «-«. Создайте две версии с использованием дружественных функций и функций-членов. Сравните два эти варианта.
6. Определите класс RINT, который ведет себя как int, за исключением того, что разрешены только операции «+» (унарный и бинарный), «-» (унарный и бинарный), «*», «/», «%». Подсказка: не определяйте INT::operator int( ).
7. Определите класс LINT, который ведет себя так, как RINT, за исключением того, что он имеет по меньшей мере 64 бита точности.
8. Определите класс complex, tiny (4 бита), не исполь-зуя дружественных функций. Используйте только функции-члены класса.
9. Определите тип vec4 как вектор из четырех float. Опреде- лите operator[] для vec4. Определите операции «+», «-«, «*», «/»,»=», «+=», «-=», «*=», «/=» для комбинаций векторов и чисел с плавающей точкой.
10. Определите класс mat4 как вектор из четырех vec4. Определите operator[], возвращающий vec4 для mat4. Опреде-лите обычные операции над матрицами для этого типа.
11. Определите класс vector, подобный vec4, но с раз-мером, данным как аргумент для конструктора vector::vector(int).
12. Определите класс matrix, подобный mat4, но с раз-мерностями, данными как аргументы для конструктора matrix::matrix(int,int).

3? Отчет по работе

• Название работы и ее цель;
• Ответы на поставленные вопросы в порядке проведения рабо-ты;
• Листинг разработанной и отлаженной программы индивидуаль-ного задания?

4? Вопросы по лабораторной работе

1. Что такое перегрузка операций? Для чего определяется в классе перегрузка операций?
2. Какие операции можно перегружать?
3. Какие функции называются дружественными? Как в классе определяются дружественные функции?
4. Отличие в использовании дружественных функций при пере-грузке операций от функций-членов.
5. Что значит дружественные классы?
6. Как определяются дружественные классы?
7. Как определяются взаимодружественные классы?
8. Как можно перегрузить операторы new и delete?
9. Как можно перегружать оператор индексирования массива?
10. Как перегружается оператор вызова функции?

Загрузка...