Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

OOP / Лекция 4

.pdf
Скачиваний:
25
Добавлен:
20.04.2015
Размер:
96.28 Кб
Скачать

Лекция 4

1. Статические компоненты классов

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

//Листинг 12. Класс «клиент банка» class client

{ char *name; //ФИО клиента

char numb[10]; //номер счета клиента

float

value;

//сумма на

счету

float

percent;

//величина

процентной ставки для клиента

public:

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;

 

percent=0.1;

 

}

 

void PrintClient()

//функция вывода информации о клиенте на экран

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

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

}

 

void ReadClient()

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

{ cout<<"\nВвод инф-ии о клиенте\n"; cin>>name>>numb>>value;

}

void ChangePercent(float p) //функция изменения процентной ставки { percent=p; }

void Add(float dx) //функция изменения суммы на счету { if (value+dx>0)

value+=dx;

else

cout<<”Нельзя снять такую сумму со счета”;

}

void AddPercent() //функция вывода начисления процентов на вклад { value*=percent+1; }

~client() { delete [] name;} //деструктор };

main()

 

 

{client c(“Иванов И.И.”,”N12345”,1000); //определяем

объект класса client

c.PrintClient();

//выводим информацию л клиенте

на экран

c.Add(2000);

//добавляем на счет клиента 2000

 

c.PrintClient();

 

 

c.ChangePercent(0.05); //изменяем процентную ставку

 

c.AddPercent();

//начисляем проценты на сумму

вклада

c.PrintClient();

 

 

}

 

 

В последнем примере для класса «клиент банка» определены ряд наиболее существенных характеристик, которые объявлены частными компонентными данными, а также несколько общедоступных методов, позволяющих обрабатывать эти характеристики. Если не принимать в расчет иллюстративный характер приведенного примера, а, следовательно, и недостаточное количество компонентных данных и методов в классе для его практического использования, у класса client есть еще один недостаток, который делает эту программную модель не вполне соответствующей реальному субъекту – клиенту банка. Если обратить внимание на компонент percent, то можно отметить, что процентная ставка в реальности вводится не для каждого клиента по отдельности, а является характеристикой банка и для всех клиентов конкретного банка является одинаковой. В связи с этим необходимо определить компонент percent таким образом, чтобы он хранился в памяти в единственном экземпляре при любом количестве объявленных объектов класса client, а его значение было бы общим для всех объектов. Этого можно добиться, объявив данный компонент статическим. Для этого в теле класса необходимо определить компонент следующим образом:

static тип имя_статического_компонентного_данного;

Ключевое слово static определяет компонент статическим. Основное свойство статического компонентного данного заключается в том, что память под него выделяется единственный раз в программе, причем еще до определения объектов данного класса. Для выделения памяти под статический элемент данных класса необходимо вне класса на внешнем уровне программы поместить инициализатор статического компонента вида

тип имя_класса :: имя_статического_компонентного_данного=выражение;

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

При создании объектов класса память под статический элемент данных не выделяется, все объекты работают с единственным статическим компонентом.

Обращаться

к

статическому компоненту класса можно как к обычному компоненту

через имя

уже

существующего объекта, словно он является частью этого объекта.

При этом необходимо учитывать, что, обращаясь к компонентному данному через имя одного объекта, мы изменяем его значение и для всех остальных объектов того же класса. Так как память под статический элемент данных выделяется еще до определения объектов, существует еще один способ обращения к нему:

имя_класса :: имя_статического_компонентного_данного

Такой способ используется для изменения значения статического члена данных в тот момент, когда недоступен ни один из объектов класса.

Необходимо также отметить, что на статические компоненты класса действуют ограничения по области видимости, задаваемые ключевыми словами public, protected и private в теле класса.

Объявим компонент percent класса client статическим. //Листинг 13. Использование статических компонент в класса class client

{public:

static float percent; };

float client::percent=0.1; main()

{client::percent=0.2; //работать со статическими компонентными данными можно еще до определения объектов класса…

client c(“Иванов”,”N12345”,200);

c.percent=0.05;//…а можно и через имя уже определенного объекта

}

Статическими могут быть объявлены не только компонентные данные, но и методы класса. Очевидно, что объявление метода класса статическим носит иное назначение, нежели чем компонентного члена данных, поскольку методы класса, как уже говорилось, и так существуют в памяти в единственном экземпляре. Статические методы класса обычно выступают в качестве интерфейса для работы с частными или защищенными статическими компонентными данными. Если, например, объявить компонент percent в классе client частным, то изменять его значение можно будет только с использованием метода ChangePercent. Однако вызвать метод ChangePercent можно только для конкретного объекта класса, тогда как изменять статические компонентные данные можно и без наличия созданных объектов. Статические методы класса позволяют работать со статическими компонентными данными без использования объектов данного класса. Объявляются такие методы в теле класса через ключевое слово static:

static тип имя_статического_метода( список_формальных_параметров );

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

имя_класса :: имя_статического_метода (список_ фактических_ параметров ) Рассмотрим пример с классом «клиент банка».

//Листинг 14. Использование статических компонент в класса class client

{ …

static float percent; public:

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

}

};

float client::percent=0.1; main()

{ //client::percent=0.2; Такое обращение запрещено, т.к. percentчастный компонент

client::ChangePercent(0.1); client c(“Иванов”,”N12345”,200); c.ChangePercent=0.05;

}

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

Для разделения интерфейса класса и его внутренней реализации различным компонентам класса присваивается разная область видимости с использованием ключевых слов 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;

}

33

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

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;}

};

37

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;

38

} }

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 является использование дружественных функций или дружественных классов.

3. Перегрузка операций

Классы вводят в программу новые пользовательские типы данных. Такие типы могут использоваться наравне со стандартными (базовыми): они могут использоваться для определения новых переменных, могут входить в списки параметров функций и определять тип возвращаемого значения. В условиях фактического равноправия производных и основных типов данных должна существовать возможность сохранения привычной структуры выражений при работе с данными производных типов. Это означает, что выражение для вычисления суммы двух слагаемых уже известного нам типа array по своей структуре не должно отличаться от соответствующих выражений для слагаемых типа int или float. Но большинство операций языка C++ определены лишь для основных типов данных. Использование в качестве операндов операций выражений производных типов вызывает ошибки трансляции.

int a,b,c;

c=a+b; // использование операндов базовых типов для операции сложения разрешено

array m1(4),m2(4),m3(4);

Соседние файлы в папке OOP