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

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
238
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

710

Часть IV * Расширенное использование С-^-^

Четвертый вид сообщения не требует преобразования. Даже если эти сообще­ ния направляются с использованием указателя базового класса, они интерпрети­ руются средой выполнения программ в соответствии с типом объекта, на который указывает указатель (динамическое связывание). Структура, использующая вир­ туальные функции, инкапсулирует алгоритмы. Они выполняются по-разному для разных видов объектов в функциях с теми же именами.

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

Рассматривалось множественное наследование. Это сложный вопрос. Реко­ мендуем реже использовать множественное наследование.

Виртуальные функции популярны в программировании на языке С+-Н. Помни­ те, однако, что механизм виртуальной функции является "хрупким". Необходимо применять общедоступное наследование. Обязательно использование тех же са­ мых имен во всех классах своей иерархии наследования. Требуется использовать тот же список параметров, возвращаемых значений и даже модификаторов кон­ стант. При самом незначительном несоответствии программа вызовет совершенно другую функцию просто потому, что у нее то же самое имя. Иначе говоря, исполь­ зуйте виртуальные функции там, где обработку разного вида связанных объектов можно приемлемо описать с помошд>ю одного и того же имени функции.

sr^^ei^16

у ^—^ асширенное использование перегрузки операций

Темы данной главы

•^ Перегрузка операций: краткий обзор •/ Унарные операции

•^ Операции, возвращающие компонент массива по индексу, и операции вызова функции

•^ Операции ввода/вывода •^ Итоги

Перегруженные операции языка C++ обсуждались в главе 10 (числовые классы) и главе 11 (нечисловые классы). В этой главе рассматриваются более экзотические варианты использования перегрузки операций в язы­

ке C+ + . Для некоторых программистов любая перегрузка операции сама по себе является достаточно эксцентричной.

Расширенные перегруженные операции пишутся не часто. Однако расширен­ ные операции являются важным компонентом библиотеки C+ + , стандартным или нестандартным, и полезны для понимания того, как они работают. Это опреде­ ленно не самое важное при изучении C+ + , но эти операции интересны.

Перегрузка операций: краткий обзор

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

Включение перегруженных операций в язык C++ было вызвано желанием ин­ терпретировать переменные встроенного типа. Если можно добавить два числовых значения, добавьте два объекта Account. Если можно добавить три числовых зна­ чения, добавьте три объекта Account и т. д. Перегруженные операции позволяют это сделать.

Именно в языке C++ значение встроенных операций определяется по встро­ енным типам. Для перегруженных операций это делает программист. Значение не должно быть произвольным. Оно должно зависеть от характера добавляемых,

f~7^

.ширенное тспоАШОваите С'^^

умножаемых объектов. Но программист располагает достаточной свободой

вопределении смысла операции, и это может легко привести к неправильному использованию — к проектированию перегруженных операций, значение которых не очень понятно интуитивно. Хорошим примером такого неверного использова­ ния является унарная операция, к которой добавлена операция, спроектированная

вглаве 10, для отображения полей комплексных чисел (см. листинг 10.4). Если вы будете ее использовать в клиентской программе, то любой программист, осуидествляющий сопровождение, окажется в сложной ситуации. Немногие люди мо­ гут правильно угадать, что, например, +х означает отображение полей объекта х

вуказанном формате.

Вы не можете свободно выбирать имена перегруженных операций. Имя опера­ торной функции должно включать зарезервированное слово operator, за которым следует допустимая операция C + + (разрешается использование двухсимвольных операций типа == или +=).

Из этого правила есть пять исключений: ". ", ". *", ": ;", "?:" и "sizeof". Пере­ груженные операции могут быть определены либо как члены класса (следователь­ но, используемые как сообш,ения), либо как глобальные функции верхнего уровня ("друзья" класса, объекты которого применяются как операнды перегруженных операций). Если операция перегружается как член класса, она может иметь любые подходящие аргументы.

Цель сообш^ения будет использоваться как первый операнд. Если операция перегружается как глобальная функция, она должна содержать хотя бы один аргумент класса. Она не может иметь аргументы только встроенных типов. Это ограничение не применяется к операциям управления памятью (new, delete и delete []).

Операции, перегруженные в базовом классе, наследуются в производных клас­ сах. Очевидно, что эти операции не могут осундествлять доступ к членам, опреде­ ленным в производных классах, поскольку члены производного класса находятся вне области видимости базового класса. Следовательно, к ним невозможно осуще­ ствить доступ из методов базового класса. Перегруженные операции присваива­ ния являются аномалией — они не наследуются производными классами. Они могут осуществить доступ только к базовой части производного объекта, но не к его производной части. Значит, каждый класс в иерархии наследования должен определять свой собственный оператор присваивания.

Предшествование операций для перегруженных операций такое же, как и для их встроенных аналогов. Например, операция умножения всегда имеет более вы­ сокий приоритет, чем операция сложения, какое бы ни было значение для класса, определенного программистом. Синтаксис выражения для перегруженных опера­ ций аналогичен соответствующим встроенным операциям. Например, бинарные операции всегда появляются между их двумя аргументами независимо от того, встроены они или перегружены. (Однако в этой главе можно будет увидеть неко­ торые исключения из этого правила.)

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

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

Глава 16 • Расширенное использование перегрузки операций

713

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

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

Поскольку класс динамически управляет памятью, хорошо было бы добавить

кнему копию конструктора и оператор присваивания или сделать закрытыми про­ тотипы этих функций-членов (см. главу 11). Здесь это не показано, поскольку объекты Account не передавались по значению. Один объект Account не инициали­ зировался из данных другого объекта Account и один объект Account не присваи­ вался другому объекту Account. В реальной жизни важно защитить объекты Account даже от случайного неверного использования.

class Account {

 

 

 

 

 

/ /

базовый класс

иерархии

protected:

 

 

 

 

 

 

 

 

 

 

double balance;

 

 

 

 

 

/ /

защищенные данные

char

*owner;

 

 

 

 

 

 

 

 

 

 

public:

 

 

 

 

 

 

 

 

 

 

 

Account(const char*

name,

double

initBalance)

/ / общий

{

owner = new char[strlen(name)+l];

/ /

выделение памяти для динамически

 

 

 

 

 

 

 

 

/ /

распределяемой области памяти

 

i f

(owner == 0) {

cout

«

"\nOut

of

memory\n";

exit(O);

}

 

strcpy(owner,

name) ;

 

 

 

/ /

инициализация

полей данных

 

balance = initBalance;

}

 

 

 

 

 

 

 

double getBalO

const

 

 

 

/ /

общий для обоих счетов

{

return balance;

}

 

 

 

 

 

 

 

 

 

const

char* getOwnerO const

 

/ /

защитить данные от изменений

{

return owner; }

 

 

 

 

 

 

 

 

 

 

void withdraw(double amount)

 

 

 

 

 

 

{ balance -= amount;

}

 

 

 

/ /

извлечение

обязанностей

void

deposit(double

amount)

 

 

 

 

 

 

{

balance += amount;

} }

;

 

 

/ /

безусловное

приращение

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

В листинге 16.1 приведена исходная программа д/ш данного примера. Функция createAccountO динамически создает объект Account, вызывает конструктор Account с двумя параметрами и возвращает указатель на новый созданный объект. Функция processRequestO устанавливает флаги ios для вывода на печать чисел с плавающей точкой в фиксированном формате и с нулевыми младшими разряда­ ми числа, выполняет поиск имени клиента в рамках объектов и выводит на печать сообщение, если имя не найдено. В противном случае функция запрашивает у пользователя код транзакции,'сумму транзакции и выполняет транзакцию на указанную сумму (вклад или снятие).

714

Часть IV » Расширенное использование C+'t^

Листинг 16.1. Пример обработки класса Account методами, заданными программистами

#include <iostream>

 

 

 

 

 

 

using namespace std;

 

 

 

 

 

 

class Account {

 

 

 

 

// базовый

класс иерархии

protected:

 

 

 

 

 

// защищенные данные

double balance;

 

 

 

char *owner;

 

 

 

 

 

 

 

public:

 

 

 

 

 

 

 

 

Account(const char* name, double initBalance)

// общий

{ owner = new char[strlen(name)+1] ;

 

// выделить пространство из динамически

 

if (owner =- 0) {cout «

 

 

// распределяемой области памяти

 

"\nOut ofmemory\n" exit(O); }

 

strcpy(owner, name);

 

 

// инициализировать поля данных

 

balance = initBalance; }

 

 

 

 

 

double getBalO const

 

 

// общее для обоих счетов

{

return balance; }

 

 

 

 

 

 

const char* getOwnerO const

 

// защита данных от изменений

{

return owner; }

 

 

 

 

 

 

void withdraw(double

amount)

 

// извлечение ответственности

{ balance -=amount; }

 

 

void deposit(double

amount)

 

// безусловное

приращение

{ balance +=amount; }

 

 

}

 

 

 

 

 

 

 

 

 

Account* createAccount(const

char* name, double bal)

 

 

{ Account* a = new Account (name, bal);

 

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

if (а == 0) {cout «

"\nOut ofmemory\n"; exit(O); }

 

return a; }

 

 

 

 

 

 

 

 

void processRequest(Account* a[] , const char name[])

 

 

{ int i; int choice; double amount;

 

 

 

 

cout. setf (ios:: fixed, ios: :floatfield);

 

 

cout.precision(2);

i++)

 

 

 

 

 

for (i=0; a[i] != 0;

 

 

 

 

// поиск имени

{ if (strcmp(a[i]->getOwner(),name)==0)

 

() «

 

{ cout «

"Account balance: " «

a[i]->getBal

endl;

 

cout «"Enter

1 todeposit, 2 to withdraw, 3 tocancel:

 

cin »

choice;

 

 

 

 

 

// тип транзакции

 

if (choice != 1 &&choice !=2) break;

 

// выход

 

cout «

"Enter amount: ";

 

 

 

// сумма транзакции

 

cin »

amount;

 

 

 

 

 

 

switch

(choice) {

 

 

 

 

// выбрать следующий путь

 

case 1: a[i]->deposit(amount);

 

 

// безусловно

 

 

 

break;

 

 

 

 

// достаточно средств?

 

case 2: if (amount <= a[i]->getBal())

 

 

 

 

 

a[i]->withdraw(amount);

 

 

 

 

 

else

 

 

 

 

// конец области switch

 

 

 

 

cout « "Insufficient funds\n" ;

 

cout «

break; }

 

 

 

endl;

//OK

 

"New balance: " « a[i] ->getBal() «

 

break;

} }

 

 

 

 

 

// конец цикла поиска

if (a[i] -

0)

 

 

}

}

 

 

 

{ cout «

"Customer isnot found\n";

 

 

Глава 16 • Расширенное использование перегрузки операций

715

int mainO

{

Account* accounts[100]; char name[80]; accounts[0] =createAccountC'Jones",5000); accounts[1] =createAccount("Smith",3000); accounts[2] =createAccountC'Green",1000); accounts[3] =createAccountC'Brown",1000); accounts[4] = 0;

while (true)

{ cout «

"Enter customer name ('exit' to exit)

cin »

name;

if (strcmp(name,"exit")==0) break;

processRequest(accounts, name);

}

return 0;

//данные программы

//создать объекты

//запросы процесса

//принять имя

//выход?

// следующая транзакция

Enter customer name ( exit' to exit): Brown

Функция main() определяет массив указате­

лей Account и вызывает createAccountO для

Account balance: 1000 00

создания объектов Account. В цикле она запра­

Enter

1 to deposit,

2 to withdraw, 3to cancel:2

Enter

amount:

2000

 

 

шивает у пользователя имя клиента и вызывает

Insufficient

funds

 

 

функцию processRequestO для обработки тран­

New balance:

1000.00

 

закции. Пример выполнения программы пока­

Enter

customer name ( exit' to exit): Brown

Account balance: 1000, 00

зан на рис. 16.1.

Enter

1 to deposit,

2 to withdraw, 3to cancel: 2

В данном примере класс Account основыва­

Enter

amount:

500

 

 

ется на своей клиентской программе для про­

New balance:

500.00

 

 

Enter customer name (' exit' to exit): Smith

верки, является ли законной транзакция снятия.

Account balance: 3000. 00

Преимущество подобного подхода в том, что

Enter

amount:

2000

2

to withdraw, 3to cancel: 1

функции-члены Account не включены в пользо­

Enter

1 to deposit,

 

 

New balance:

5000.00

(' exit' to exit): Simons

вательский интерфейс, они отвечают только за

Enter

customer name

доступ к элементам данных Account. Недоста­

Customer is not found

 

ток этого подхода состоит в том, что данные

Enter

customer name

(' exit' to exit): exit

выталкиваются в клиентскую программу для

Рис. 16-1- Вывод

для программы,

дальнейшей обработки, а не передают обязан­

ность серверной программе. Такая структура

 

представленной в листинге 16.1

 

лучше подходит к использованию перегружен­

 

 

 

 

 

 

 

 

 

 

ных операций.

 

 

 

 

Первыми кандидатами на роль перегруженных операций являются функции-

 

 

 

 

члены Account — depositO

и withdrawO. Для преобразования их в функции-

 

 

 

 

операторы надо отбросить текуш^ие имена (deposit и withdraw) и переместиться

 

 

 

 

под новые имена (operateг+= и operateг-=). Другие изменения не требуются.

void operator

-= (double amount)

{ balance -=

amount; }

void operator += (double amount) { balance += amount; }

/ / клиент проверяет выполнение

/ / безусловное приращение

Вместо вызова функций-членов depositO и withdrawO клиентская функция processRequestO сможет использовать синтаксис выражения, в котором опера­ ция вставлена между левым операндом (цель сообидения) и правым операндом (параметр сообщения).

switch

(choice)

{

 

case

1: * a [ i ]

+= amount;

/ / а[1]->вклад(сумма);

 

break;

 

 

// базовый класс иерархии
// защищенные данные
// общий
// динамическое выделение области памяти

716

Часть IV * Расширенное использование C-f^

 

case 2: if (amount <=a[i]->

getBalO)

 

*a[i] -=amount;

// а[1]->снятие(сумма);

 

else

 

 

 

cout

< "Insufficient funcls\n";

 

break;

}

 

Обратите внимание, что цель сообщения — указатель Account. Он должен быть разыменован, когда используется в выражениях. Это неудобство, но не очень серьезное.

Реальный смысл синтаксиса выражения, конечно, вызов функции для отправ­ ления сообщения левому операнлу в таком выражении: a[i]->operator+=(amount) или a[i]->operator-=(amount).

Customer List:

 

 

В листинге 16.2 представлена программа,

 

 

использующая

перегруженные

операторные

1 Jones

5000.00

 

функции

вместо

именованных

программистом

 

методов. Это просто для программы листин­

Smith

3000.00

 

Green

1000.00

 

га 16.1. Прежде чем начать этап интерактивной

Brown

1000.00

 

обработки, функция main() вызывает функцию

Enter customer name ( 'exit' to exit): Smith

 

printListO, которая проходит по списку ука­

Recount balance: 3000 00

|

зателей Account и выводит на печать содержа­

Enter 1 todeposit, 2 towithdraw, 3 tocancel: 1

ние объектов, на которые указывает указатель

Enter amount:

1000

 

(см. рис. 16.2). Обратите

внимание на то, что

New balance: 4000.00

|

Enter customer

name ( exit' to exit): exit

операторы форматируют имена, выводимые на

Рис. 16.2. Вывод программы

 

печать с

выравниванием

по

левому

краю.

 

а остатки на счетах выводятся на печать с вы­

из листинга 16.2

 

равниванием по правому краю.

 

 

 

 

 

 

 

 

Подобно processRequestO, функция printListO итеративно выполняется по

 

списку до тех пор, пока в массиве не будет найден нулевой указатель (он играет

 

роль сигнальной метки). Обратите внимание, что заголовки циклов этих двух

 

функций различаются. В printListO индекс i является локальным для

цикла,

 

в processRequestO — глобальным для цикла. (Он является локальным для облас­

 

ти видимости функций.) Причина различий состоит в том, что после завершения

 

цикла в printListO значение индекса больше не требуется. Итерации всегда

 

выполняются от начала списка до конца. В processRequestO итерацию можно

 

остановить до того, как

будет достигнут

конец

списка (если имя найдено),

а processRequestO должен знать об этом.

Листинг 16.2. Пример обработки класса Account методом перегрузки операции

#include <iostream> using namespace std;

class Account { protected:

double balance; char *owner;

public:

Account(const char* name, double initBalance) { owner = new char[strlen(name)+l];

if (owner ==0) {cout «

"\nOut ofmemory\n" exit(O); }

strcpy(owner, name);

// инициализация полей данных

balance = initBalance; }

double getBalO const

// общее для обоих счетов

{ return balance; }

 

 

 

Глава 16 • Расширенное использование перегрузки операций

717

 

const char* getOwnerO const

// защита данных отизменений

 

 

{ return owner; }

 

 

 

void operator -= (double amount)

// извлечение ответственности

 

 

{ balance -=amount; }

 

 

void operator += (double amount)

// безусловное приращение

 

 

{ balance += amount; }

 

} ;

 

 

 

 

 

Account* createAccount(const char* name, double bal)

 

{

Account* a = new Account(name,bal);

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

 

if (а == 0) {cout < "\nOut of memory\n"; exit(O); }

 

 

return a; }

 

 

 

 

void processRequest(Account* a[], const char name[])

 

{

int i; int choice; double amount;

 

 

 

cout.setf(ios::fixed,ios::floatfield);

 

 

 

cout.precision(2) ;

 

 

 

for (i=0; a[i] != 0; i++)

// поиск имени

 

 

{ if (strcmp(a[i]->getOwner(),name)==0)

 

 

{

cout «

"Account balance: " « a[i]->getBal() « endl;

 

 

 

cout «"Enter 1 todeposit, 2 towithdraw, 3 tocancel: ";

 

 

 

cin »

choice;

// тип транзакции

 

 

 

if (choice ! = 1 &&choice !=2) break;

 

 

 

cout « "Enter amount: ";

// сумма транзакции

 

 

 

cin »

amount;

 

 

 

switch

(choice) {

// a[i]->operator+==( сумма);

 

 

 

case 1: *a[i] += amount;

 

 

 

 

 

break;

 

 

 

 

case 2: if (amount <= a[i]->getBal())

 

 

 

 

 

*a[i] -=amount;

// a[i]->operator-=( сумма);

 

 

 

 

 

else

 

 

 

 

 

 

cout » "Insufficient funds\n";

 

 

 

cout «

break; }

// конец области действия switch

 

 

 

"New balance: " « a[i]->getBal() « endl;

 

 

break;

} }

// конец цикла поиска

 

 

if (a[i] - 0)

 

}

 

 

{ cout «

"Customer isnot found\n"; }

 

void printList (Account* a[])

 

 

{

cout «

"Customer List:\n\n";

 

 

 

for (int i=0; a[i] != 0; i++)

cout.width(30);

 

 

{ cout.setf(ios::left, ios::adjustfield);

 

 

cout « a[i]->getOwner();

cout.width(10);

 

 

cout.setf(ios::right, ios::adjustfield);

 

 

cout «

a[i]->getBal() « endl; }

 

 

 

cout «

endl; }

 

 

 

int mainO

 

 

 

 

 

{

Account* accounts[100]; char name[80];

// данные программы

 

 

 

 

accounts[0] = createAccount("Jones", 5000);

// создать объекты

 

 

accounts[1] = createAccount("Smith",3000);

 

 

 

accounts[2] = createAccount("Green", 1000);

 

 

accounts[3] = createAccount("Brown",1000); accounts[4] = 0;

printList(accounts);

718

Часть IV • Расширенное использование С4-+

while (true)

// запросы процесса

{ cout «

"Enter customer name ('exit' toexit): ";

cin »

name;

// принять имя

if (strcmp(name,"exit")==0) break;

// выход?

processRequest(accounts, name);

//следующая транзакция

}

;

 

return 0

 

}

 

 

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

void operator

-= (Account

 

&а,

double

amount)

/ /

глобальная функция

{ а. balance

-= amount;

}

 

 

 

/ /

извлечение ответственности

void operator

+= (Account

 

&a,

double

amount)

 

 

{ a.balance

+- amount;

}

 

 

 

/ /

безусловное приращение

Поскольку эти две функции осуществляют доступ к неоткрытым компонентам класса Account, они должны быть объявлены "друзьями" класса Account. У неко­ торых программистов это вызывает раздражение, поскольку требует дополнитель­ ной работы. Как уже упоминалось, современный подход к программированию не рассматривает дополнительные затраты на написание программы как недостаток. Дополнительные данные записываются только один раз, но читаются в ходе разра­ ботки, тестирования и сопровождения программы неоднократно.

В этом случае добавление описаний функций, "дружественных" для класса, четко показывает, что эти функции принадлежат заданному классу. Учтите, что они не могут использоваться без объектов класса Account. Функции-"друзья" принадлежат классу концептуально, т. е. являются частью операций, предоставля­ емых классом. Синтаксис функций-"друзей" отличается от синтаксиса функцийчленов. Частые обвинения против использования "дружественных" функций, на­ рушения инкапсуляции и создание дополнительных зависимостей между частями программы не являются результатом применения перегруженных операторных функций.

class Account

{

 

 

 

 

/ /

базовый класс иерархии

protected:

 

 

 

 

 

 

 

 

 

 

double

balance;

 

 

 

/ /

защищенные данные

public:

char *owner;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Account(const char* name,

double initBalance)

/ /

общие

{

owner = new char[strlen(name)+1];

/ /

динамическое выделение

 

 

 

 

 

 

 

 

/ /

распределяемой области

 

i f

(owner

== 0) { cout

«

"\nOut

of

memory\n";

exit(O); }

 

strcpy(owner,

name);

 

 

 

/ /

инициализация полей данных

 

balance = initBalance;

}

 

 

 

 

 

double getBalO

const

 

 

 

/ /

общие для обоих счетов

{

return balance; }

 

 

 

 

 

 

const

char*

getOwnerO const

 

/ /

защита данных от изменений

{

return owner;

}

 

 

 

 

 

 

friend void operator-= (Account &a, double amount);

/ / операторы

friend

void operator+= (Account &a,

double amount);

 

Глава 16 • Расширенное использование перегрузки операций

719

Синтаксис выражения в клиентской программе не изменяется с переходом от

операций функций-членов к операциям-"друзьям".

 

switch

(choice)

{

 

 

 

 

 

case

1: * a [ i ]

+= amount;

/ /

operator+=(*a[i],amount);

 

break;

 

 

 

 

 

 

case 2: i f (amount

<= a[i]->getBal())

 

 

 

* a [ i ]

-=

amount;

/ /

operator-=(*a[i], amount);

 

else

 

 

 

 

 

 

 

cout

«

"Insufficient

funcls\n";

 

 

 

break;

}

 

/ /

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

switch

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

switch (choice)

{

 

 

 

e a s e l : operator+=(*a[i], amount);

/ / a . к . a .

*a[i]+=cyMMa;

break;

 

 

 

 

case 2: i f (amount

<= a[i]->getBal())

 

 

operator-=(*a[i],amount);

/ / a . k . a .

*a[i]-=cyMMa;

else

 

 

 

 

cout

«

"Insufficient funcls\n";

 

 

break;

}

 

/ / конец области действия switch

Обратите внимание, что фактический параметр должен быть разыменован, поскольку a[i] является указателем на объект Account, но не самим объектом. Параметр-ссылка должен инициализироваться значением объекта, а не значением указателя. Именно поэтому вызов функции должен быть разыменован.

Использование перегруженных операций предлагает очень хороший способ записи клиентской программы. Однако таким образом сундественные вопросы проектирования ПО не решаются. Все, что можно выполнить с использованием перегруженных операций, можно сделать и с помош,ью обычных функций-членов (см. листинг 16.1).

Унарные операции

Унарные операции содержат только один операнд. К ним относятся операции инкремента и декремента, операции отрицания, логического и побитового отри­ цания, операции плюс и минус, приведения, адресации, операции разыменования и операторы new, delete и sizeof. Все они (за исключением sizeof) могут быть перегружены.

Не все операции могут иметь свое специальное значение для каждого класса.

В главе 10 операция плюса для класса Complex перегружена как операция вывода,

иэта структура сбивает с толку. Именно поэтому перегрузка унарной операции не очень популярна. Однако бывают ситуации, когда эти операции могут помочь

впонимании клиентской программы. Далее обсудим несколько примеров перегру­ женных унарных операций.

Операции инкремента и декремента

Операции инкремента и декремента очень популярны в С + 4-. Они использу­ ются в обработке текстов, когда прираш^ение (или уменьшение) указателя может объединяться с доступом к текуш,ему символу для обработки.

Соседние файлы в предмете Программирование на C++