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

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

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

I 400

Часть II • Объектно-ориентировонное орогрс:

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

Complex X, у,

z;

 

/ /

объекты типа Complex

X. real

= 20;

х.imag

= 40;

/ /

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

у. real

= 30;

у.imag

= 50;

/ /

это абсолютно законно

z = operator+(x,y);

 

Если имя функции включает в себя ключевое слово operator и символ опера­ ции, то компилятор воспринимает или вызов функции, или синтаксис операции

игенерирует один тот же код. Когда используется синтаксис z = operator + (х, у);, он сопоставляет типы фактических аргументов с типами параметров (как для любого вызова функции). Если же применяется синтаксис z = х + у;, то компиля­ тор понимает, что используются операнды определяемого программистом типа,

иищет функцию, у которой имя содержит ключевое слово operator и указанный

вклиенте знак операции. Если эта функция найдена, то он проверяет соответствие ее параметров числу и типам операндов в клиентском выражении.

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

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

Это очень гибкий и мощный механизм. Как и многое другое в C+ + , програм­ мист получает больше, чем может ожидать. Мы начали с такой цели, как исполь­ зование объектов определяемых программистом типов как переменных встроенных типов, а закончим более мощными средствами. Теперь можно делать с объектами собственных типов то, о чем и мечтать не приходилось при работе со встроенными числовыми типами. Ведь язык не ограничивает программиста в реализации пере­ груженных операторных функций. Ограничивается только интерфейс функции — ее имя и число параметров. Их нельзя выбирать произвольно. Они должны эмули­ ровать перегруженные встроенные операции.

Перегрузка операторной функции проиллюстрирована в листинге 10.2. Кроме перегруженной операции сложения он показывает применение функции operator+=(), которая складывает один объект Complex с другим. Хотя имена этих двух оператор­ ных функций одинаковы, их список параметров различен. Это законное использо­ вание перегрузки имен (о совмещении имен функции см. в главе 7).

Листинг 10.2. Пример перегрузки операторных функций

#inclucle <iostream>

 

using

namespace std;

 

struct

Complex {

/ / тип, определяемый программистом

double real, imag;

} ;

Complex operator+(const Complex &a, const Complex &b)

{Complex c;

сreal = a.real + b. real; c.imag = a. imag + b.imag; return c; }

/ /

волшебное имя

/ /

локальный объект

/ /

сложение

вещественных компонентов

/ /

сложение

мнимых компонентов

void operator += (Complex &а, const Complex &b)

{a. real = a.real + b. real; a.imag = a. imag + b.imag;}

void operator

+= (Complex &a, double b)

{ a.real += b;

}

/ /

еще одно волшебное имя

/ /

сложение

вещественных компонентов

/ /

сложение

мнимых компонентов ^.'''^

//другой интерфейс

// сложение только вещественных компонентов

 

 

 

 

Глава 10 • Операторные функции

401

void showComplex(const Complex &х)

 

{ cout «

"(" «

x. real « "

«

x.imag « " ) " « endl; }

 

int mainO

 

 

 

// объекты типа Complex

 

{ Complex X, y, z1, z2;

 

 

X. real = 20; x.imag = 40;

 

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

 

y. real = 30; y.imag = 50;

showComplex(x);

 

cout «

"Первое значение:

 

cout «

"Второе значение:

showComplex(y);

 

z1 = operator+(x,y);

 

// использование в вызове функции

 

cout «

"Сумма каквызов функции: "; showComplex(zl);

 

z2 = X + у;

 

 

// использование как операции

 

cout «

"Сумма как операция: ";

showComplex(zl);

 

z1 += х;

 

 

 

// тоже, чтоoperator+=(z1,x);

 

cout «

"Сложение первого значения с суммой: "; showComplex(zl);

 

z2 += 30.0;

 

 

// тоже, что operator+=(z2, 30.0);

 

cout «

"Сложение 30 с суммой: "; showComplex(z2);

 

return 0;

 

 

 

 

 

 

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

 

 

const: в showComplexO, в operator+() и в operator+=(), а также на его отсутствие

 

 

в первой функции operator+=() и второй operator+=(). Отметьте в данном при­

 

 

мере некоторые преимущества объектно-ориентированного программирования.

 

 

Клиент не зависит от архитектуры сервера и имен полей данных (не требующих

 

 

инициализации), а вычисления нижнего уровня перенесены в серверные функции.

 

 

Смысл вычислений высокого уровня выражается с помощью вызовов функций.

 

 

Вычисления нижнего и высокого уровня решают разные вопросы. На нижнем

 

 

уровне обрабатываются поля комплексных чисел (согласно правилам комплекс­

 

 

ной арифметики), на верхнем — операции с комплексными числами выполняются

 

 

согласно стоящим перед приложением целям. Соответственно для представления

 

 

данных и для алгоритма приложения области изменения различны. Если меняется

 

 

архитектура класса Complex, то придется модифицировать только перегруженные

 

 

операции, а не клиент. При изменении алгоритма приложения модифицируется

 

 

клиент, но не перегруженные операции.

 

 

 

В то же время здесь отсутствуют некоторые преимущества объектно-ориенти­

 

 

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

 

 

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

 

 

бальны. Звучит знакомо? Хорошо. Вот мы и добрались до цели.

 

Результат данной

программы

представлен на

 

рис. 10.2.

Как видно из листинга 10.2, ключевое слово operator можно отделять (или не отделять) от знака операции пробелами. Допускается запись operator+() и operator + (). Если знак операции содержит два символа, то эти символы не должны разделяться пробелами.

Первое

значение:

<20,

40>

 

 

Второе

значение:

<30,

50>

 

90>

Сумма

как вызов

функции:

<50,

Сумма

как операция:

<50,

90>

Сложение

первого

значения с суммой: <70,

130>

Сложение

30 с суммой:

<80,

90>

Рис. ] Ош2. Результат

выполнения

 

 

 

 

программы

из листинга

10.2

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

чтение программы). Такое разделение не считается синтаксической ошибкой.

402

Часть II • Объектно-ориентированное програ1^мирование но С-^Ф

Нужно иметь в виду, что в случае использования перегруженных операций в пространстве клиента, они реализуются как вызовы функций. Применение пере­ груженных операций не ускорит выполнение программы. Это лишь синтаксиче­ ский прием, позволяющий сделать ее более удобной для чтения. Во* всех случаях вместо синтаксиса перегруженных операций допустимо использовать вызовы функций. Например, последнюю часть клиентской программы из листинга 10.2 можно записать так:

operator+=(z1, у);

 

/ / то же, что z1 += х;

cout «

"Сложение первого значения с суммой: "

;

.showComplex(zl);

operator+=(z2,30.0);

 

/ / то же, что z2 +- 30.0;

cout «

"Сложение 30 с суммой:

";

showCofTiplex(z2);

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

Ограничения перегрузки операций

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

Какие операции не могут быть перегруженными

Некоторые ограничения на перегрузку операций не имеют для практики про­ граммирования особо важного значения. Не допускается перегрузка операций

::(область действия), . * (выбор компонента объекта), . (выбор объекта класса)

и?: (условный оператор или арифметическое ИЛИ). Трудно представить, для чего кому-то может понадобиться совмещать операцию области действия или условия, как и операцию выбора компонента объекта и объекта класса. (На самом деле операцию выбора компонента объекта мы не используем здесь даже в своем первоначальном смысле.)

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

Complex operator**(const Complex &а, const Complex &b); / / ошибка

Это ошибка, так как в C++ нет встроенной операции **.

Перегруженные операции нельзя применять для встроенных числовых типов, придавая им новый смысл. Например, в приложении может потребоваться огра­ ничить результаты целочисленного сложения конкретным числом, допустим 60

Глава 10 • Операторные функции

| 403 |

(арифметика по модулю), для чего делается попытка переопределить операцию сложения целых чисел, установив данное ограничение:

i nt operator + (int а, int b)

/ /

синтаксическая ошибка

{ return (a+b) % 60)}

/ /

сложение по модулю 60

Хорошая идея, но она не сработает по нескольким причинам. Основная причи­ на в том, что при перегрузке нескольких операций компилятор запутается в их разных дополнительных смыслах. Так, в перегруженной операции для целых чисел в последнем примере в теле функции используется операция сложения. Хотелось бы применять ее здесь в стандартном смысле, но как сообщить об этом компиля­ тору? Откуда он узнает, что это не рекурсивный вызов функции operator+()?

Такие же трудности возникают в коде клиента.

int a,b,c; float d,e, f;

a = 20, b = 30; d = 40.0; e = 50.0;

/ / встроенная или

перегруженная операция?

с = а + b; f = d +

е;

Здесь никак нельзя сообщить компилятору, что нужно использовать — встроен­ ную операцию сложения или перегруженную операцию.

Именно поэтому С+-}- не допускает перегрузки операций для встроенных типов. Можно перегружать операции только для типов, определяемых программистом. Фактически, в CH--f это ограничение обобидается: требуется, чтобы как минимум один параметр в перегруженной операторной функции был параметром определя­ емого программистом типа (классом). В показанной выше попытке ввести допол­ нительную операцию для сложения целых чисел это ограничение нарушается.

О с т о р о ж н о ! Набор операций C++ нельзя расширить за счет символов перегруженных операций, не используемых в качестве встроенных

операций C++. Можно перегружать только существующие операции C++. Нельзя также менять смысл операций со встроенными типами. Разрешается перегружать только операции для типов, определяемых программистом (классов).

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

и чисел с плавающей точкой. Перегруженная операция просто добавляется

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

Complex operator+(const Complex &а,

const

Complex &b)

/ / волшебное имя

{ Complex с;

 

/ /

локальный

объект

с. real = а. real

+ b. real;

/ /

сложение

вещественных компонентов

c.imag = a.imag

+ b.imag;

/ /

сложение

мнимых компонентов

return с; }

 

 

 

 

 

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

Complex X,

у, z;

/ / объекты типа Complex

X. real=20;

x.imag=40;

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

404

Часть II ^ Обьектно-ориентировонное програттироваитв на С4--ь

 

у.геа1=30;

у.imag=50;

 

 

 

 

Z = X + у;

 

/ /

использование

в выражении

 

double

а,

Ь, с;

/ /

переменные типа double

 

а = 20;

b = 30;

/ /

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

 

 

с = а + Ь;

 

/ /

использование

в выражении

Встречая первую операцию сложения, компилятор устанавливает, что операн­ ды имеют тип Complex, и вызывает перегруженную операторную функцию. Для второй операции сложения он вызывает встроенную операцию, так как операнды имеют тип double.

Ограничения на возвращаемые типы

Обычно перегруженные операторные функции возвраидают либо void, либо булево значение, либо значение того типа, для которого предназначена операция. Часто возврандаются значения типа класса. Это особенно популярно в случае операций, вычисляющих для применения в других выражениях новое значение того же типа. Например, operator+() в приведенных выше примерах возвраидает значение типа Complex, что позволяет использовать данное значение с операцией присваивания. Если бы возвращалось значение типа void, то такое было бы невозможно. Кроме того, возврат значения открывает путь к построению более сложных выражений, аналогичных выражениям со встроенными типами.

Complex а, Ь, с, d; а. геа1=20; a.imag=40; Ь.геа1=30; b.imag=50;

с.геа1=0; с.imag=20;

/ / объекты типа Complex //инициализация

d = а + b + с;

/ / использование в выражении

Требуется некоторое время, чтобы уяснить суть этого программного кода. Арифметические выражения С+4- ассоциируются слева направо. Если бы а, b и с были числами, то выражение а + b + с означало бы (а + Ь) + с. Перегруженные операторные функции не меняют ассоциативности операций. Если а, b и с — объекты типа Complex, то смысл выражения будет тем же:

d = (а + Ь) + с;

/ / использование в выражении

В терминах синтаксиса функций выражение означает следующее:

d = operator+((a + b),c);

/ / использование в выражении

Осталось представить выражение а + ь как вызов функции:

d = operator+(operator+(a,b),c);

/ / использование в выражении

Смысл данного кода в том, что здесь вызывается функция operator+() с пе­ ременными а и Ь, передаваемыми в фактических аргументах, а возвращаемое функцией значение становится первым аргументом функции при втором вызове operator+().

Две перегруженные операторные функции operator+=() из листинга 10.2 возвращают тип void, следовательно, не могут применяться в цепочке, где для дальнейших операций в выражении используется полученное значение.

Complex а, Ь, с, d;

a.real=20;

a.imag=40;

b.real=30;

b.imag=50;

с. real = 0;

с.imag = 20;

d = a + b + с; а+= b;

d = с + (b+= 30.0);

/ / объекты типа Complex //инициализация

/ / использование в выражении

//OK, operator+=(a, b); возвращает void //нехорошо: operator+=(b, 30. 0); возвращает void

/ / использование в выражении
//OK, operator+=(a, b); возвращает void
//OK, operator+=(b,30,0); возвращаемый тип не используется //OK: operator+=(c, b); возвращает Complex

Глава 10 • Операторные функции

405

Чтобы эту операцию можно было использовать в цепочке выражений, нужно построить программу так:

Complex operator += (Complex &a double b)

/ /

класс возвращает тип

{ a.real += b;

/ /

сложение с real

return a; }

 

 

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

Complex а, b, с, d;

а.real=20;

а.imag=40;

b.real=30;

b.imag=50;

0. real = 0;

c.imag = 20;

d a + b + c; a += b;

b += 30.0 d = с + b

/ /

объекты типа Complex

/ /

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

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

Ограничения на число параметров

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

Изменять арность операции нельзя, т. е. нужно точно задавать число операн­ дов, которые будут с нею использоваться (два для бинарной операции, один — для унарной). Арность перегруженной операции должна быть такой же, как у ориги­ нальной встроенной операции. Нельзя определить бинарную операцию, работаю­ щую с двумя операндами, и использовать ее для создания унарной операции с одним операндом.

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

Для этого требуется лишь заменить имя showComplex на operator <.

void operator

< (const

Complex &x)

/ /

плохая идея: синтаксическая ошибка

{ cout « " ( "

« x.real

« ", " «

x.imag «

" ) " « endl; }

Нетрудно увидеть, как можно использовать такую функцию в клиенте. Она вызывается так же, как функция showComplex():

Complex X, у,

z1, z2;

X. real

= 20;

x.imag

= 40;

y.real

= 30;

y.imag

= 50;

cout «

 

"Первое значение:

operator

< (x);

 

cout «

 

"Второе значение:

operator

< (у);

 

/ /

объекты типа Complex

/ /

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

/ / то же, что showComplex(;

/ / то же, что showComplex(;

406

Часть II • Объектно-ориентированное програ1М1^ировани

 

 

 

Между тем операция "меньше чем" — двухместная операция (операция с дву­

 

мя операндами), и использование этой функции с синтаксисом операции требует

 

второго операнда, который пропущен.

 

 

 

 

 

Complex X, у,

z1, z2;

/ /

объекты типа Complex

 

x.real

= 20;

x.imag

= 40;

/ /

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

 

 

 

у. real

= 30;

у.imag

= 50;

 

 

 

 

 

 

cout

«

"Первое значение:

";

 

 

 

 

 

< х;

 

 

 

 

/ /

нонсенс,

если х

-

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

 

cout

«

"Второе значение:

";

 

 

 

 

 

< у;

 

 

 

 

/ /

нонсенс,

если у

-

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

Итак, первая попытка не удалась. Операторная функция operator < () должна иметь два параметра, а здесь только один — выводимый объект типа Complex. Если неизвестно, что делать с другим операндом, пришлось бы найти другую, "одноместную" операцию.

ВС+-\- есть несколько операций, которые можно использовать как бинарные

иунарные: плюс, минус, звездочка и знак @. Можно перегружать каждую из них как операцию с одним или двумя операндами. Ведь для встроенных типов воз­ можны оба варианта. Например, в нашем примере операция 4- перегружается как бинарная. Поскольку она доступна и как унарный плюс, можно совместить ее, используя функцию с одним параметром. Такая замена для showComplexO вполне законна:

void operator

+ (const

Complex &x)

/ /

то же, что и showComplexO

{ cout « " ( "

« x.real

« ", " « x.imag «

" ) " «

endl; }

Можно без проблем использовать эту функцию, применяя в клиенте синтаксис операции:

Complex X, у,

z1, z2;

//объекты типа Complex

X.real

= 20;

x.imag

= 40;

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

у.real

= 30;

у.imag

= 50;

 

cout

«

"Первое

значение: ";

// operator+(x); или showComplex(x);

+ х;

«

"Второе

значение: ";

cout

// тоже, что operator+(y); или showComplex(y);

+ у;

 

 

 

 

 

Ограничение на старшинство операций

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

Независимо от типа объектов х и у и смысла операций + и /, деление в выра­ жении X + у/2 будет выполняться перед сложением. Если нужен другой порядок, следует, как обычно, использовать скобки.

О с т о р о ж н о ! При перегрузке операторных функций нельзя изменить число операндов в операции, старшинство операций или их ассоциативность. Разрешается лишь изменять смысл операции для определяемых программистом типов. Это позволяет клиенту применять для определяемых программистом классов и стандартных встроенных типов один и тот же синтаксис выражений.

Перегруженные операции как компоненты класса

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

Глава 10 • Операторные функции

407

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

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

Замена глобальной функции компонентом класса

Правила перегрузки операторных функций-членов класса совпадают с прави­ лами перегрузки операторных функций как некомпонентных функций. Имя функ­ ции-члена заменяется именем со словом operator и знаком определяемой операции.

Например, бинарные операции operator+() и operator+=(), реализованные как функции-члены класса Complex, должны иметь только один параметр, а не два, как операции в листинге 10.2, реализованные через глобальные функции. Эле­ менты данных параметра, "исчезнувшего" из интерфейса функции, стали элемен­ тами данных адресата сообщения.

class Complex {

 

/ /

тип, определенный программистом

double

real, imag;

/ /

закрытые данные

public:

 

 

/ /

общий конструктор

Complex(double

r, double 1)

{ real

=r; imag

= i ; }

 

 

Complex operator+(const

Complex &b)

/ /

только один параметр

 

{ Complex c;

 

 

/ /

работает?

 

c.real = real + b. real;

/ /

сложение вещественных

компонентов

c.imag = imag + b.imag;

/ /

сложение мнимых компонентов

return c; }

 

 

 

 

 

void operator

+= (const

Complex &b)

/ /

только один параметр

 

{ real = real

+ b. real;

 

/ /

сложение вещественных

компонентов

imag = imag + b.imag;

 

/ /

сложение мнимых компонентов

 

 

 

/ /

остальная часть класса Complex

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

Complex operator+(const Complex &а, const Complex &b)

{

Complex с; / / работает?

 

 

/ / только один параметр

 

 

 

 

/ / сложение компонентов: симметричная запись

 

 

с. real

= а. real

+ b. real;

 

 

 

 

с.imag = а.imag + b.imag;

 

 

 

 

return

с; }

 

 

 

 

void operator += (Complex &a, const

Complex &b)

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

{

a.real

= a. real

+ b. real;

/ /

сложение

вещественных компонентов

 

a.imag

= a.imag

+ b.imag; }

/ /

сложение мнимых компонентов

При преобразовании первой операторной функции в функцию-член возникает одна проблема. Здесь используется локальная переменная типа Complex, которая не инициализируется, поскольку неважно, какие значения имеют ее элементы

408

Часть II • Объектно-ориентированное программирование на С+4-

данных. Эти значения все равно перезаписываются функцией до возврата резуль­ тата клиенту. Такая же схема использовалась в листинге 10.2, и там она работала. Для класса Complex не предусматривались конструкторы — система просто исполь­ зовала конструктор по умолчанию (не выполняющий операций). В новом вариан­ те, приведенном в данном разделе, класс Complex имеет общий конструктор, и, следовательно, компилятор использует его и сообщает о попытке вызова несуще­ ствующей функции в первой строке операторной функции. Таким образом, никог­ да не нужно забывать о конструкторах.

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

Complex operator+(const

Complex &b)

/ /

только один параметр

{ Complex с(0,0);

 

/ /

попытка умиротворить компилятор

с. real = real + b.real;

/ / сложение компонентов: несимметричная реализация

с. imag = imag + b.imag;

 

 

 

return с; }

 

 

 

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

Complex operator-i-(const Complex

&b)

/ /

только

один параметр

{ return Complex (real + b. real;

imag + b.imag); }

/ /

хорошо:

быстро и изящно

^^ 1

Чг

Внимание Убедитесь, что архитектура класса поддерживает не только клиент, но и свои собственные методы. Отсутствие необходимых конструкторов — распространенный источник проблем при проектировании классов.

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

Complex x(20,40),y(30,60),z1(0,0),z2(0,0);

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

z1

= x.operator-b(y);

/ /

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

z2

= X + у;

/ /

то же, что z2=x.operator-b(y);

z1.operator-H=(y);

/ /

используется как сообщение, передаваемое zl

z2 += х;

/ /

то же, что

z2.operator-i-=(x);

Синтаксис операции для функции-члена в точности такой же, как для не члена. Компилятор различает эти вызовы и может интерпретировать выражения, вклю­ чающие в себя вызовы функций-методов с именами, которые содержат ключевое слово operator, и соответствующий знак встроенной операции:

z2 = X -I- у;

/ / то же,

что

z2=x. operator-b(y);

z2-ь= х;

/ / т о ж е ,

что

z2. operator-i-=(x);

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

z2=x.operator-b(y);

/ /

то

же,

что

z2

= х -н у;

z2.operator-b=(x);

/ /

то

же,

что

z2

-•-= у;

Глава 10 « Операторные функции

409

Что происходит при перегрузке для одной и той же операции и глобальной функции, и функции-члена? Такую идею хорошей не назовешь. Если эти функции вызываются с помош,ью синтаксиса вызова функций, то компилятор не поймет, что именно имеется в виду. Если же применяется синтаксис операций, то компи­ лятор все равно запутается. Ведь подойдет и та, и другая функция. Обе имеют одинаковое старшинство, и компилятор отвергнет такое выражение как неодно­ значное.

Использование членов класса для цепочек операций

Аналогично некомпонентной реализации возвраш^аемый функцией-членом тип void препятствует использованию синтаксиса операции в цепочке операций. При возврате объекта класса можно организовать выражение-цепочку:

Complex а(20,40),

Ь(30,50), с(0,20), с1(0,0); //определение и инициализация

d = а + b + с;

/ / использование в цепочке выражений

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

d=(a + b) + с;

Синтаксис бинарной операции скрывает из виду то, что мы имеем дело с сооб- ш,ением operator+(), передаваемым экземпляру объекта класса Complex. Смысл а + b для реализации функции-члена — это а. operator+(b). Следовательно, кли­ ентское выражение d = (a + b)+G; в точности эквивалентно следуюш.ему:

d = a.operator+(b) + с; / / сообщение возвращаемому значению a.operator+(b)

Здесь operator+ также представляет сообш^ение, передаваемое объекту и возвраш^аемое первым вызовом функции. Следовательно, выражение-цепочка имеет такой смысл:

 

d=a.operator+(b).operator+(c);

/ / сообщение возвращаемому значению

 

Нужно обязательно освоить интерпретацию таких цепочек, как вызовов функций.

 

Переопределение операции имеет место только в контексте класса, где опре­

 

делена перегруженная операция. Оно используется, когда объекту типа Complex

 

передается сообш^ение (с параметром типа Complex), поэтому в самом определении

 

функции-члена в с. real = real + b. real; используется стандартный смысл симво­

 

ла +. Это не рекурсивный вызов перегруженной операции +. Во встроенной функ­

 

ции операция + применяется к значениям типа double. Компилятор знает, что

 

левый операнд имеет тип double. Это не объект, и он не может быть получателем

 

сообш,ения operator+(). Для значений double используется встроенная операция +.

 

В листинге 10.3 показана новая версия класса Complex и реализация других

 

перегруженных операций как функций-членов. Вторая функция operator+=() те­

 

ряет свой параметр Complex. Вместо него используется целевой объект Complex.

Значение х:

<20, 40>

Унарная функция operator+(), реализующая функцию showComplexO,

теперь вовсе не имеет параметров. Это не противоречит правилу,

Значение у:

<30, 50>

что перегруженная

операторная функция (реализуемая, как не

z1 = X + у:

<50, 90>

член) должна иметь по крайней мере один объект-параметр типа

z2 = X + у:

<50, 90>

Сложение х с z1: <70, 130>

класса. Данный объект и есть адресат сообш.ения. Для реализации

Сложение 30 с z2: <80. 90>

операции вывода большинство программистов предпочли бы со-

Рис. 10.3.

 

вмеидать операцию « , а не операцию +. Ниже будет показано, как

 

это сделать. Результат данной программы представлен на рис. 10.3.

Результат выполнения программы из лист,инга 10.3

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