Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

723

строки, на которую ссылается _string. (Возбуждение и обработка исключений обсуждались в главе 11.)

15.5. Оператор вызова функции

Оператор вызова функции может быть перегружен для объектов типа класса. (Мы уже видели, как он используется, при рассмотрении объектов-функций в разделе 12.3.) Если определен класс, представляющий некоторую операцию, то для ее вызова перегружается соответствующий оператор. Например, для взятия абсолютного значения числа типа int

class absInt { public:

int operator()( int val ) {

int result = val < 0 ? -val : val; return result;

}

можно определить класс absInt:

};

Перегруженный оператор operator() должен быть объявлен как функция-член с произвольным числом параметров. Параметры и возвращаемое значение могут иметь любые типы, допустимые для функций (см. разделы 7.2, 7.3 и 7.4). operator() вызывается путем применения списка аргументов к объекту того класса, в котором он определен. Мы рассмотрим, как он используется в одном из обобщенных алгоритмов, описанных в главе 12. В следующем примере обобщенный алгоритм transform()

вызывается для применения определенной в absInt операции к каждому элементу

#include <vector> #include <algoritm>

int main() {

int ia[] = { -0, 1, -1, -2, 3, 5, -5, 8 }; vector< int > ivec( ia, ia+8 );

//заменить каждый элемент его абсолютным значением transform( ivec.begin(), ivec.end(), ivec.begin(), absInt() );

//...

вектора ivec, т.е. для замены элемента его абсолютным значением.

}

Первый и второй аргументы transform() ограничивают диапазон элементов, к которым применяется операция absInt. Третий указывает на начало вектора, где будет сохранен результат применения операции.

Четвертый аргумент это временный объект класса absInt, создаваемый с помощью конструктора по умолчанию. Конкретизация обобщенного алгоритма transform(), вызываемого из main(), могла бы выглядеть так:

С++ для начинающих

724

typedef vector< int >::iterator iter_type;

//конкретизация transform()

//операция absInt применяется к элементу вектора int

iter_type transform( iter_type iter, iter_type last, iter_type result, absInt func )

{

while ( iter != last )

*result++ = func( *iter++ ); // вызывается absInt::operator()

return iter;

}

func это объект класса, который предоставляет операцию absInt, заменяющую число типа int его абсолютным значением. Он используется для вызова перегруженного оператора operator() класса absInt. Этому оператору передается аргумент *iter, указывающий на тот элемент вектора, для которого мы хотим получить абсолютное значение.

15.6. Оператор стрелка

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

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

class ScreenPtr { // ...

private: Screen *ptr;

Screen (см. главу 13):

};

Определение ScreenPtr должно быть таким, чтобы объект этого класса гарантировано указывал на объект Screen: в отличие от встроенного указателя, он не может быть нулевым. Тогда приложение сможет пользоваться объектами типа ScreenPtr, не проверяя, указывают ли они на какой-нибудь объект Screen. Для этого нужно определить класс ScreenPtr с конструктором, но без конструктора по умолчанию

class ScreenPtr { public:

ScreenPtr( const Screen &s ) : ptr( &s ) { } // ...

(детально конструкторы рассматривались в разделе 14.2):

};

С++ для начинающих

725

В любом определении объекта класса

ScreenPtr должен присутствовать

 

ScreenPtr p1; // ошибка: у класса ScreenPtr нет конструктора по

 

 

умолчанию

 

 

Screen myScreen( 4, 4 );

 

инициализатор объект класса Screen, на который будет ссылаться объект ScreenPtr:

ScreenPtr ps( myScreen ); // правильно

Чтобы класс ScreenPtr вел себя как встроенный указатель, необходимо определить некоторые перегруженные операторы разыменования (*) и стрелкудля доступа к

// перегруженные операторы для поддержки поведения указателя class ScreenPtr {

public:

Screen& operator*() { return *ptr; } Screen* operator->() { return ptr; } // ...

членам:

};

Оператор доступа к членам унарный, поэтому параметры ему не передаются. При

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

point->action();

исследуется тип point. Если это указатель на некоторый тип класса, то применяется семантика встроенного оператора доступа к члену. Если же это объект или ссылка на объект, то проверяется, есть ли в этом классе перегруженный оператор доступа. Когда перегруженный оператор стрелкаопределен, он вызывается для объекта point, иначе инструкция неверна, поскольку для обращения к членам самого объекта (в том числе по ссылке) следует использовать оператор точка”.

Перегруженный оператор стрелкадолжен возвращать либо указатель на тип класса, либо объект класса, в котором он определен. Если возвращается указатель, то к нему применяется семантика встроенного оператора стрелка”. В противном случае процесс продолжается рекурсивно, пока не будет получен указатель или определена ошибка. Например, так можно воспользоваться объектом ps класса ScreenPtr для доступа к членам Screen:

ps->move( 2, 3 );

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

Ниже приводится небольшая программа для тестирования класса ScreenPtr. Объект типа ScreenPtr используется точно так же, как любой объект типа Screen*:

С++ для начинающих

726

#include <iostream> #include <string> #include "Screen.h"

void printScreen( const ScreenPtr &ps )

{

cout << "Screen Object ( "

<<ps->height() << ", "

<<ps->width() << " )\n\n";

for ( int ix = 1; ix <= ps->height(); ++ix )

{

for ( int iy = 1; iy <= ps->width(); ++iy ) cout << ps->get( ix, iy );

cout << "\n";

}

}

int main() {

Screen sobj( 2, 5 );

string init( "HelloWorld" ); ScreenPtr ps( sobj );

// Установить содержимое экрана string::size_type initpos = 0;

for ( int ix = 1; ix <= ps->height(); ++ix ) for ( int iy = 1; iy <= ps->width(); ++iy )

{

ps->move( ix, iy );

ps->set( init[ initpos++ ] );

}

// Вывести содержимое экрана printScreen( ps );

return 0;

}

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

15.7. Операторы инкремента и декремента

Продолжая развивать реализацию класса ScreenPtr, введенного в предыдущем разделе, рассмотрим еще два оператора, которые поддерживаются для встроенных указателей и которые желательно иметь и для нашего интеллектуального указателя: инкремент (++) и

декремент (--). Чтобы использовать класс ScreenPtr для ссылки на элементы массива объектов Screen, туда придется добавить несколько дополнительных членов.

Сначала мы определим новый член size, который содержит либо нуль (это говорит о том, что объект ScreenPtr указывает на единственный объект), либо размер массива, адресуемого объектом ScreenPtr. Нам также понадобится член offset, запоминающий смещение от начала данного массива:

С++ для начинающих

727

 

 

class ScreenPtr {

 

 

 

 

 

 

public:

 

 

 

// ...

 

 

 

private:

// размер массива: 0, если единственный объект

 

 

int size;

 

 

int offset;

// смещение ptr от начала массива

 

 

Screen *ptr;

 

 

 

};

 

 

 

 

 

 

 

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

class ScreenPtr { public:

ScreenPtr( Screen &s , int arraySize = 0 )

: ptr( &s ), size ( arraySize ), offset( 0 ) { }

private: int size;

int offset; Screen *ptr;

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

};

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

Screen myScreen( 4, 4 );

ScreenPtr pobj( myScreen ); // правильно: указывает на один объект const int arrSize = 10;

Screen *parray = new Screen[ arrSize ];

нового класса ScreenPtr можно определять следующим образом:

ScreenPtr parr( *parray, arrSize ); // правильно: указывает на массив

Теперь мы готовы определить в ScreenPtr перегруженные операторы инкремента и декремента. Однако они бывают двух видов: префиксные и постфиксные. К счастью, можно определить оба варианта. Для префиксного оператора объявление не содержит

class ScreenPtr { public:

Screen& operator++(); Screen& operator--(); // ...

ничего неожиданного:

};

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

С++ для начинающих

728

const int arrSize = 10;

Screen *parray = new Screen[ arrSize ]; ScreenPtr parr( *parray, arrSize );

for ( int ix = 0; ix < arrSize;

++ix, ++parr ) // эквивалентно parr.operator++()

}

printScreen( parr );

Screen& ScreenPtr::operator++()

{

if ( size == 0 ) {

cerr << "не могу инкрементировать указатель для одного объекта\n"; return *ptr;

}

if ( offset >= size - 1 ) {

cerr << "уже в конце массива\n"; return *ptr;

}

++offset; return *++ptr;

}

Screen& ScreenPtr::operator--()

{

if ( size == 0 ) {

cerr << "не могу декрементировать указатель для одного объекта\n"; return *ptr;

}

if ( offset <= 0 ) {

cerr << "уже в начале массива\n"; return *ptr;

}

--offset; return *--ptr;

Определения этих перегруженных операторов приведены ниже:

}

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

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

class ScreenPtr {

 

public:

// префиксные операторы

Screen& operator++();

Screen& operator--();

 

Screen& operator++(int); // постфиксные операторы

Screen& operator--(int);

 

// ...

 

ScreenPtr:

};

С++ для начинающих

729

Screen& ScreenPtr::operator++(int)

{

if ( size == 0 ) {

cerr << "не могу инкрементировать указатель для одного объекта\n"; return *ptr;

}

if ( offset == size ) {

cerr << "уже на один элемент дальше конца массива\n"; return *ptr;

}

++offset; return *ptr++;

}

Screen& ScreenPtr::operator--(int)

{

if ( size == 0 ) {

cerr << "не могу декрементировать указатель для одного объекта\n"; return *ptr;

}

if ( offset == -1 ) {

cerr << "уже на один элемент раньше начала массива\n"; return *ptr;

}

--offset; return *ptr--;

Ниже приведена возможная реализация постфиксных операторов:

}

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

const int arrSize = 10;

Screen *parray = new Screen[ arrSize ]; ScreenPtr parr( *parray, arrSize );

for ( int ix = 0; ix < arrSize; ++ix)

использования постфиксного оператора: printScreen( parr++ );

При его явном вызове необходимо все же передать значение второго целого аргумента. В случае нашего класса ScreenPtr это значение игнорируется, поэтому может быть любым:

parr.operator++(1024); // вызов постфиксного operator++

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

ScreenPtr: