Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

#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() );

//...

}

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

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

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;

вызываемого из main(), могла бы выглядеть так:

}

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

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

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

Допустим, мы хотим определить тип класса для представления указателя на объект Screen (см. главу 13):

class ScreenPtr

{

// ...

private: Screen *ptr;

};

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

class ScreenPtr { public:

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

// ...

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

};

В любом определении объекта класса 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*:

#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, запоминающий смещение от начала данного массива:

class ScreenPtr {

 

public:

 

// ...

 

private:

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

int size;

объект

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

int offset;

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-- ();

// ...

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

};

Такие операторы определяются как унарные операторные функции. Использовать

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. В следующем фрагменте объявлены префиксные и постфиксные варианты операторов инкремента и декремента для класса

ScreenPtr:

class ScreenPtr { public:

Screen&

operator++();

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

Screen&

operator--();

 

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

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

};

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++ );