Выбрать главу

Каждой функции-члену передается указатель на объект, для которого она вызвана, – this. В неконстантной функции-члене это указатель на тип класса, в константной – константный указатель на тот же тип, а в функции со спецификатором volatile указатель с тем же спецификатором. Например, внутри функции-члена move() класса Screen указатель this имеет тип Screen*, а в неконстантной функции-члене List – тип List*.

Поскольку this адресует объект, для которого вызвана функция-член, то при вызове move() для myScreen он указывает на объект myScreen, а при вызове для bufScreen – на объект bufScreen. Таким образом, член _cursor, с которым работает функция move(), в первом случае принадлежит объекту myScreen, а во втором – bufScreen.

Понять все это можно, если представить себе, как компилятор реализует объект this. Для его поддержки необходимо две трансформации:

* Изменить определение функции-члена класса, добавив дополнительный параметр:

// псевдокод, показывающий, как происходит расширение

// определения функции-члена

// ЭТО НЕ КОРРЕКТНЫЙ КОД C++

inline void Screen::move( Screen *this, int r, int c )

{

if ( checkRange( r, c ) )

{

int row = (r-1) * this-_width;

this-_cursor = row + c - 1;

}

}

В этом определении использование указателя this для доступа к членам _width и _cursor сделано явным.

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

myScreen.move( 2, 2 );

транслируется в

move( &myScreen, 2, 2 );

Программист может явно обращаться к указателю this внутри функции. Так, вполне корректно, хотя и излишне, определить функцию-член home() следующим образом:

inline void Screen::home()

{

this-_cursor = 0;

}

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

13.4.1. Когда использовать указатель this

Наша функция main() вызывает функции-члены класса Screen для объектов myScreen и bufScreen таким образом, что каждое действие – это отдельная инструкция. У нас есть возможность определить функции-члены так, чтобы конкатенировать их вызовы при обращении к одному и тому же объекту. Например, все вызовы внутри main() будут выглядеть так:

int main() {

// ...

myScreen.clear().move( 2, 2 ), set( '*' ). display();

bufScreen.reSize( 5, 5 ).display();

}

Именно так интуитивно представляется последовательность операций с экраном: очистить экран myScreen, переместить курсор в позицию (2,2), записать в эту позицию символ '*' и вывести результат.

Операторы доступа "точка" и "стрелка" левоассоциативны, т.е. их последовательность выполняется слева направо. Например, сначала вызывается myScreen.clear(), затем myScreen.move() и т.д. Чтобы myScreen.move() можно было вызвать после myScreen.clear(), функция clear() должна возвращать объект myScreen, для которого она была вызвана. Мы уже видели, что доступ к объекту внутри функции-члена класса производится в помощью указателя this. Вот реализация clear():

// объявление clear() находится в теле класса

// в нем задан аргумент по умолчанию bkground = '#'

Screen& Screen::clear( char bkground )

{ // установить курсор в левый верхний угол и очистить экран

_cursor = 0;

_screen.assign( // записать в строку

_screen.size(), // size() символов

bkground // со значением bkground

);

// вернуть объект, для которого была вызвана функция

return *this;

}

Обратите внимание, что возвращаемый тип этой функции-члена – Screen& – ссылка на объект ее же класса. Чтобы конкатенировать вызовы, необходимо также пересмотреть реализацию move() и set(). Возвращаемый тип следует изменить с void на Screen&, а в определении возвращать *this.

Аналогично функцию-член display() можно написать так:

Screen& Screen::display()

{

typedef string::size_type idx_type;

for ( idx_type ix = 0; ix _height; ++ix )

{ // для каждой строки

idx_type offset = _width * ix; // смещение строки

for ( idx_type iy = 0; iy _width; ++iy )

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

cout _screen[ offset + iy ];

cout endl;

}

return *this;

}

А вот реализация reSize():

// объявление reSize() находится в теле класса

// в нем задан аргумент по умолчанию bkground = '#'