- •Д. Н. Лясин, с. Г. Саньков
- •Оглавление
- •1.Обзор стилей программирования
- •1.1. Процедурное программирование
- •Структурное программирование
- •Функциональное программирование
- •Логическое программирование
- •1.5. Объектно-ориентированное программирование
- •Основные принципы объектно-ориентированного программирования
- •3.1. Объявление классов и объектов
- •3.2. Конструкторы и деструкторы
- •3.3. Область видимости компонент класса
- •3.4. Определение компонентных функций класса
- •3.5. Статические компоненты классов
- •Дружественные функции
- •3.7. Перегрузка операций
- •4. Наследование классов
- •4.1. Повторное использование классов: наследование и агрегирование
- •4.3. Множественное наследование
- •4.4. Виртуальные классы
- •4.5. Виртуальные функции. Полиморфизм
- •4.6. Абстрактные классы
- •Список литературы
Дружественные функции
Для разделения интерфейса класса и его внутренней реализации различным компонентам класса присваивается разная область видимости с использованием ключевых слов 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 является использование дружественных функций или дружественных классов.