Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по С++ глава 5.doc
Скачиваний:
3
Добавлен:
05.11.2018
Размер:
166.91 Кб
Скачать

5.1.2. Общие принципы перегрузки операторов

Можно перегрузить едва ли не любой из существующих бинарных и унарных операторов языка C++. Ниже приведен список операторов, допускающих перегрузку. Перегрузка оператора для класса определяет смысл указанной операции при ее использовании в выражении, содержащем хотя бы один объект класса. Тем не менее, нельзя изменить синтаксис оператора. И тем более нельзя изменить приоритет операторов, их ассоциативность или число операндов. Также нельзя переопределить оператор, если он используется в выражении, содержащем данные только встроенных типов.

+

*

/

%

^

&

|

~

!

=

<

>

+=

– =

*=

/=

%=

^=

&=

|=

<<

>>

>>=

<<=

==

!=

<=

>=

&&

||

++

– –

,

–>*

–>

<–

( )

[ ]

new

delete

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

// задается в определении класса CCurrency:

CCurrency operator- () // унарный оператор - (отрицание)

{

return CCurrency (-Dollars, Cents);

}

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

// определяется глобально:

CCurrency operator- (CCurrency &Curr)

{

return CCurrency (-Curr.Dollars, Curr.Cents);

}

В этом примере предполагается, что знак денежной суммы определяется переменной Dollars, Например, величина –5,75 $ будет сохранена в виде значения –5 в переменной Dollars и значения +75 в переменной Cents.

Приведенные примеры иллюстрируют общие принципы перегрузки оператора. Однако при перегрузке некоторых операторов (например, «++») нужно соблюдать специальные правила. В следующем параграфе рассмотрены основные правила перегрузки оператора присваивания.

5.1.3. Перегрузка оператора присваивания

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

CCurrency Money1 (12, 95);

CCurrency Money2;

Money2 = Money1;

то компилятор скопирует переменную Moneyl.Dollars в Money2.Dollars, a Moneyl.Cents – в Money2.Cents (аналогично в последней версии языка С происходит присваивание одной структуры другой). Можно создать временный объект класса и присвоить его объявленному объекту, что является удобным способом повторной инициализации существующего объекта.

CCurrency Money(85, 25); // создание объекта

Money.PrintAmount (); // печать его содержимого

Money = CCurrency(24, 65); // создание временного объекта и присваивание

// его существующему объекту

Если стандартная операция присваивания для написанного класса не подходит, то следует перегрузить оператор присваивания. Например, рассмотрим класс CMessage, предназначенный для хранения и вывода сообщений:

#include <string.h>

#include <iostream.h>

class CMessage

{

private:

char *Buffer; // хранит строку сообщения

public:

CMessage()

{

Buffer = new char('\0'}; // инициализирует переменную

// Buffer пустой строкой

}

~CMessage()

{

delete[]Buffer; // освобождает память

}

void Display()

{

cout << Buffer << '\n'; // печатает сообщение

}

void Set (char *String) // сохраняет новую строку сообщения

{

delete [] Buffer;

Buffer = new char [strlen (String) + 1];

strcpy (Buffer, String);

}

};

Если один объект класса CMessage присвоить другому, то переменная-член Buffer обоих объектов будет указывать на один и тот же блок памяти. Если затем вызвать функцию-член Set для изменения сообщения, хранимого в одном объекте, то этот блок памяти освободится, вследствие чего в переменной Buffer другого объекта будет храниться указатель на случайные данные. То же самое произойдет, если один объект будет уничтожен раньше другого, так как деструктор освобождает блок памяти, на который указывает переменная Buffer.

Чтобы обеспечить корректное присваивание одного объекта класса CMessage другому, добавим в определение класса следующую функцию-оператор.

class CMessage

{

// другие объявления:

public:

void operator = (const CMessage &Message)

{

delete [] Buffer;

Buffer = new char [strlen (Message.Buffer) + 1];

strcpy (Buffer, Message.Buffer};

}

// другие объявления:

}

В отличие от простого копирования адреса блока памяти, хранящегося в поле Buffer, из объекта-источника в объект-приемник, перегруженный оператор «=» создает совершенно новый блок памяти для объекта-приемника, а затем копирует строку в память. Таким образом, каждый объект имеет собственную копию строки.

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

void main ()

{

CMessage Message1;

Messagel.Set ("initial Message1 message");

Messagel.Display ();

CMessage Message2;

Message2.Set ("initial Message2 message");

Message2.Display ();

Messagel = Message2;

Messagel.Display ();

}

На печать будет выведено

initial Message1 message

initial Message2 message

initial Message2 message

Примечание

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

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

CMessage & operator= (const CMessage &Message)

{

delete [] Buffer;

Buffer = new char [strlen (Message.Buffer) +1];

strcpy (Buffer, Message.Buffer);

return *this;

}

то в строке можно объединить несколько операторов присваивания.

void main (} {

CMessage Message1;

CMessage Message2;

CMessage Message3;

Messagel.Set ("hello") ;

Message3 = Message2 = Message1;

}

Компилятор выполняет последовательные операции присваивания, продвигаясь справа налево. В данном примере первой вызывается функция-оператор «=», присваивающая объект Message1 объекту Message2. Она возвращает ссылку на Message2, содержащий копию строки "hello". Далее компилятор вызывает функцию-оператор, присваивающую ссылку на Message2 объекту Message3. В результате все три объекта класса CMessage имеют отдельные копии строки "hello". Обратите внимание: функция-оператор «=» может возвращать объект класса CMessage, а не ссылку на него. Однако при этом генерируется избыточная операция копирования и, следовательно, несколько снижается эффективность программы.

В окончательном варианте функции operator= должна быть предусмотрена защита от случайного вызова, присваивающего объект самому себе. Такое действие приведет к удалению содержимого буфера Buffer с последующей попыткой скопировать из него строку. Если объект присваивается самому себе, адрес объекта-источника (&Message) будет таким же, как и адрес текущего объекта (this). Если функция определит, что выполняется самоприсваивание, то она сразу же должна возвратить значение, не выполняя операцию копирования.

CMessage & operator= (const CMessage &Message)

{

if (&Message == this)

return *this;

delete [] Buffer;

Buffer = new char [strlen (Message.Buffer) + 1];

strcpy (Buffer, Message.Buffer);

return *this;

}

Ниже приведен полный листинг класса CMessage с окончательной версией функции operators

#include <string.h>

#include <iostream.h>

class CMessage

{

private:

char *Buffer; // хранит строку сообщения

public:

CMessage()

{

Buffer = new char ('\0'); // инициализирует переменную

// Buffer пустой строкой

}

~CMessage ( )

{

delete [] Buffer; // освобождает место в памяти

}

void Display ()

{

cout << Buffer << '\n'; // выводит сообщение

}

void Set (char *String) // сохраняет новую строку сообщения

{

delete [] Buffer;

Buffer = new char [strlen (String) + 1];

strcpy (Buffer, String);

}

CMessage & operator= (const CMessage &Message)

{

if (&Message == this)

return *this;

delete [] Buffer;

Buffer = new char [strlen (Message.Buffer) + 1];

strcpy (Buffer, Message.Buffer);

return *this;

}

};