Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
posobie1.doc
Скачиваний:
13
Добавлен:
01.05.2019
Размер:
457.22 Кб
Скачать
    1. Дружественные функции

Для разделения интерфейса класса и его внутренней реализации различным компонентам класса присваивается разная область видимости с использованием ключевых слов public, protected и private. Компоненты, объявленные в секции private являются частными, то есть недоступными из окружения объекта, и работать с такими компонентами могут только компонентные функции класса. Однако, зачастую возникает необходимость обращаться к частным компонентам объекта из функций, не являющихся методами класса. Это, конечно, происходит не от желания программиста нарушить принцип инкапсуляции и обеспечить доступ извне к внутренней реализации класса. Просто в некоторых случаях необходимо организовать взаимодействие нескольких объектов разных классов, и функция, обеспечивающая взаимодействие, должна иметь доступ к частным компонентам одновременно нескольких объектов. Объявить функцию методом одновременно нескольких классов невозможно, поэтому в стандарте языка С++ предусмотрена возможность объявлять внешнюю по отношению к классу функцию дружественной данному классу. Для этого необходимо в теле класса описать некоторую внешнюю по отношению к классу функцию с использованием ключевого слова friend.

friend имя_функции ( список_формальных_параметров);

Функция - не член класса, - имеющая доступ к его закрытой части, называется другом этого класса.[Страуструп] Дружественная функция имеет доступ ко всем компонентам класса вне зависимости от их области видимости. Дружественная функция класса может быть обычной внешней функцией программы, а может – компонентной функцией другого класса.

Дружественная функция становится расширением интерфейса класса, и этот интерфейс реализует взаимодействие объекта с другими объектами программы. Рассмотрим простой пример.

//Листинг 15. Дружественные функции классов

class ClassB;

class ClassA

{ int x;

friend void func(ClassA,ClassB); //объявляем функцию дружественной классу ClassA

};

class ClassB

{ int y;

friend void func(ClassA,ClassB); //объявляем функцию дружественной классу ClassВ

};

void func(ClassA a,ClassB b)

{

cout <<a.x+b.y;//дружественная функция имеет доступ к частным компонентам обоих

// классов

}

main()

{

ClassA a;

ClassB b;

func(a,b);

}

В последнем примере определены два класса ClassA и ClassB, а также внешняя функция func, объявленная дружественной в обоих классах, благодаря чему func имеет доступ к частным компонентам x и y классов. При этом функция func является внешней по отношению к классу функцией и объекты класса должны либо передаваться ей в качестве параметров, либо объявляться в теле функции. Неявной передачи адреса объекта в дружественную функцию при вызове, как это осуществляется для компонентных функций класса, не происходит.

Дружественная классу функция может быть компонентной функцией другого класса. Рассмотрим пример, в котором помимо уже известного класса «клиент банка», определен класс «банк», и организуется взаимодействие этих классов.

//Листинг 16. Дружественные функции из других классов.

#include <iostream.h>

#include <conio.h>

#include <string.h>

class bank;

class client

{ char * name;

char numb[10];

float value;

char BankName[20]; //название банка, в котором хранится вклад

static float percent;

public:

client(char* ,char *,float);

void PrintClient() { cout<<"\nВывод инф-ии о клиенте\n"<<

name<<'\t'<<numb<<'\t'<<value;}

void ReadClient() { cout<<"\nВвод инф-ии о клиенте\n";

cin>>name>>numb>>value;}

static void ChangePercent(float p) { percent=p; }

void Add(float dx) { value+=dx; }

void AddPercent() { value*=percent+1; }

~client() { delete [] name;}

void SetBank(bank&); //функция записи клиента в список клиентов банка

};

float client::percent=0.1;

client::client(char* s="Без имени",char *n="N0000",float x=0)

{ int k=strlen(s);

name=new char[k+1];

strcpy(name,s);

strcpy(numb,n);

value=x;

}

//класс «банк»

class bank

{ int count; //количество клиентов банка

char Name[20]; //название банка

client *spisok[10]; //массив клиентов банка

public:

bank(char *bankName){strcpy(Name,bankName);count=0;} //конструктор

friend void client::SetBank(bank &); //дружественная функция из другого класса

void PrintAll(); //функция вывода на экран информации о клиентах банка

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

};

bank::~bank()

{ if(count)

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

delete spisok[i];

}

void client::SetBank(bank& b)

{ b.spisok[b.count]=this; //обращение к частной компоненте spisok класса bank

b.count++; //обращение к частной компоненте count класса bank

strcpy(BankName,b.Name); //обращение к частной компоненте Name класса bank

}

void bank::PrintAll()

{ for (int i=0;i<count;i++)

spisok[i]->PrintClient();

cout<<'\n';

}

main()

{

bank SB("Сбербанк"); //создаем объект класса bank

int n;

do{

cout<<"Введите количество клиентов";

cin>>n;

}while(n>10||n<1)

client *c;

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

{

c=new client; //создаем новый объект «клиент»

c->ReadClient(); //вводим информацию о клиенте

c->SetBank(SB); //записываем клиента в список банка

}

SB.PrintAll(); //выводим на экран информацию обо всех клиентах банка

}

В последнем примере определен новый класс bank, который содержит такие компоненты как количество клиентов банка, название банка, список клиентов банка. При этом классу client добавлено новое компонентное данное - название банка, в котором хранится его вклад. Взаимодействие между клиентом и банком устанавливается в функции SetBank, в которой адрес клиента заносится в массив клиентов банка spisok, увеличивается количество клиентов банка в компоненте count, а также для клиента заполняется компонент BankNameназвание банка, в котором хранится вклад. Таким образом, компонентная функция класса client должна иметь доступ к частным компонентам другого класса (класса bank). Для того, чтобы это стало возможным, функция SetBank объявляется как дружественная классу bank:

friend void client :: SetBank (bank &);

В общем виде объявление выглядит следующим образом:

friend тип имя_класса_функции :: имя_функции ( список_форм_параметров );

В функции SetBank для записи адреса объекта типа client в список клиентов банка используется константа this. Константный указатель this может использоваться только в нестатических компонентных функциях класса, причем использовать его необходимо без предварительного определения – он определяется транслятором автоматически. Указатель this хранит адрес объекта, для которого произведен вызов метода класса. Поддержка указателя this осуществляется транслятором путем переопределения компонентных функций класса и их вызовов в программе. На первом этапе преобразования каждая нестатическая функция-член преобразуется в функцию с уникальным именем и дополнительным параметром - константным указателем на объект класса. Затем преобразуются обращения к нестатическим данным-членам в операторах функции-члена. Они переопределяются с учётом нового параметра. В C++ при подобном преобразовании для обозначения дополнительного параметра-указателя (константного указателя) и постфиксного выражения с операциями обращения для обращения к нестатическим данным-членам используется одно и то же имя this. Вот как могла бы выглядеть функция-член SetBank после её переопределения:

void client::client_SetBankl(client const *this, bank& b)

{ b.spisok[b.count]=this;

b.count++;

strcpy(this->BankName,b.Name);

}

На втором этапе преобразуются вызовы функций-членов. К списку значений параметров выражения вызова добавляется выражение, значением которого является адрес данного объекта. Это вполне корректное преобразование. Дело в том, что нестатические функции-члены всегда вызываются для конкретного объекта. И потому не составляет особого труда определить адрес объекта. Например, вызов функции-члена SetBank для объекта c, который имеет вид

c.SetBank(SB);

после преобразования принимает вид:

client_SetBank(&c,SB);

Первый параметр в вызове новой функции является адресом конкретного объекта. В результате такого преобразования функция-член приобретает новое имя и дополнительный параметр типа указатель на объект со стандартным именем this и типом, а каждый вызов функции-члена приобретает форму вызова обычной функции.

Причина изменения имени для функций-членов класса очевидна. В разных классах могут быть объявлены одноименные функции-члены. В этих условиях обращение к функции-члену класса непосредственно по имени может вызвать конфликт имён: в одной области действия имени одним и тем же именем будут обозначаться различные объекты - одноименные функции-члены разных классов. Стандартное преобразование имён позволяет решить эту проблему.

Таким образом, благодаря преобразованиям транслятора, любая нестатическая компонентная функция может использовать в своем теле указатель this. При этом выражение this представляет адрес объекта, вызвавшего функцию, а выражение *this представляет сам объект.

В языке С++ для класса кроме дружественной функции можно объявить дружественной класс. Все компонентные функции дружественного класса имеют доступ к частным и защищенным компонентам того класса, в котором он объявлен другом. Для того чтобы предоставить некоторому классу (назовем его класс1) свойства друга другого класса (класс2) необходимо в теле класса2 поместить следующую строку:

friend class имя_класса1;

После такого определения любая компонентная функция класса1 будет иметь доступ к любой компоненте класса2 независимо от присвоенной ей области видимости. Рассмотрим пример взаимодействия дружественных классов.

//Листинг 17. Дружественный класс

#include <iostream.h>

#include <conio.h>

#include <string.h>

class bank;

class client

{ friend class bank; //объявляем класс bank дружественным классу client

client *next; //адрес следующего клиента в списке клиентов банка

char * name;

char numb[10];

float value;

static float percent;

client(char* ,char *,float);

void PrintClient() { cout<<"\nВывод инф-ии о клиенте\n"<<

name<<'\t'<<numb<<'\t'<<value;}

void ReadClient() { cout<<"\nВвод инф-ии о клиенте\n";

cin>>name>>numb>>value;}

static void ChangePercent(float p) { percent=p; }

void Add(float dx) { value+=dx; }

void AddPercent() { value*=percent+1; }

~client() { delete [] name;}

};

float client::percent=0.5;

client::client(char* s="Без имени",char *n="N0000",float x=0)

{ int k=strlen(s);

name=new char[k+1];

strcpy(name,s);

strcpy(numb,n);

value=x;

}

class bank

{ int count;

char Name[20];

client *first; //адрес первого клиента в списке клиентов(клиенты записываются в

//динамический список)

client *head; //адрес последнего клиента в списке клиентов

public:

bank(char *bankName)

{strcpy(Name,bankName);count=0;first=head=NULL;}

void AddClient();

void PrintAll();

~bank();

};

bank::~bank() //деструктор уничтожает список клиентов банка

{ if(count)

{ client *c=first;

while(c)

{ c=first->next;

delete first;

first=c;

} } }

void bank::AddClient() //функция добавления нового клиента в список клиентов

{ if (!count)

{first=new client;

head=first;head->next=NULL;

head->ReadClient();

count=1;

}

else

{ head->next=new client;

head=head->next;

head->ReadClient();

head->next=NULL;

count++; }}

void bank::PrintAll() //функция вывода информации обо всех клиентах на экран

{ client *c;

c=first;

while(c)

{c->PrintClient();

c=c->next;

} }

main()

{ //client cl; Ошибка! Все компоненты client частные

bank SB("Сбербанк");

char c=0;

while(c!=27) //цикл до нажатия клавиши ESC

{ SB.AddClient(); //ввод информации об очередном клиенте

c=getch();

}

SB.PrintAll();

}

В программе вновь определены два класса: bank и client. Класс client хранит информацию о клиенте банка, а также необходимые интерфейсные методы. Класс банк хранит список всех клиентов, хранящих свои вклады в данном банке. Список клиентов оформлен в виде динамического односвязного списка, что позволяет не ограничивать максимально допустимое количество клиентов банка. В связи с этим в классе client появляется новый компонент – next, хранящий адрес следующего клиента в списке, а в классе bank – компонентные данные first и head, хранящие адрес первого и последнего клиента в списке. Соответственно, функции класса bank создают, просматривают, уничтожают динамический односвязный список объектов класса клиент. При этом класс bank должен быть обязательно дружественен классу client, поскольку методы AddClient, PrintAll и деструктор класса bank работают с частными компонентами класса client (в частности, с компонентами next, ReadClent, PrintClient, конструктором и деструктором). Необходимо обратить внимание на то, что в последнем примере все компоненты класса client (и компонентные данные, и методы класса, и даже конструктор с деструктором) являются частными. Таким образом, в любой внешней по отношению к классу функции (например, в функции main) запрещен доступ к компонентам класса client, более того, запрещено даже создание объекта данного класса в main, поскольку определение объекта сопровождается вызовом конструктора, а он в данном примере также объявлен частным. В такой ситуации единственным способом работы с объектами класса client является использование дружественных функций или дружественных классов.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]