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

const Screen cs ( 5, 5 );

Если мы хотим прочитать символ, находящийся в позиции (3,4), то попробуем сделать так:

// прочитать содержимое экрана в позиции (3,4)

// Увы! Это не работает

cs.move( 3, 4 );

char ch = cs.get();

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

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

{

if ( checkRange( r, c ) )

{

int row = (r-1) * _width;

_cursor = row + c - 1; // модифицирует _cursor

}

}

Обратите внимание, что move()изменяет член класса _cursor, следовательно, не может быть объявлена константной.

Но почему нельзя модифицировать _cursor для константного объекта класса Screen? Ведь _cursor – это просто индекс. Изменяя его, мы не модифицируем содержимое экрана, а лишь пытаемся установить позицию внутри него. Модификация _cursor должна быть разрешена несмотря на то, что у класса Screen есть спецификатор const.

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

class Screen {

public:

// функции-члены

private:

string _screen;

mutable string::size_type _cursor; // изменчивый член

short _height;

short _width;

};

Теперь любая константная функция способна модифицировать _cursor, и move() может быть объявлена константной. Хотя move() изменяет данный член, компилятор не считает это ошибкой.

// move() - константная функция-член

inline void Screen::move( int r, int c ) const

{

// ...

// правильно: константная функция-член может модифицировать члены

// со спецификатором mutable

_cursor = row + c - 1;

// ...

}

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

Отметим, что изменчивым объявлен только член _cursor, тогда как _screen, _height и _width не имеют спецификатора mutable, поскольку их значения в константном объекте класса Screen изменять нельзя.

Упражнение 13.3

Объясните, как будет вести себя copy() при следующих вызовах:

Screen myScreen;

myScreen.copy( myScreen );

Упражнение 13.4

К дополнительным перемещениям курсора можно отнести его передвижение вперед и назад на один символ. Из правого нижнего угла экрана курсор должен попасть в левый верхний угол. Реализуйте функции forward() и backward().

Упражнение 13.5

Еще одной полезной возможностью является перемещение курсора вниз и вверх на одну строку. По достижении верхней или нижней строки экрана курсор не перепрыгивает на противоположный край; вместо этого подается звуковой сигнал, и курсор остается на месте. Реализуйте функции up() и down(). Для подачи сигнала следует вывести на стандартный вывод cout символ с кодом '007'.

Упражнение 13.6

Пересмотрите описанные функции-члены класса Screen и объявите те, которые сочтете нужными, константными. Объясните свое решение.

13.4. Неявный указатель this

У каждого объекта класса есть собственная копия данных-членов. Например:

int main() {

Screen myScreen( 3, 3 ), bufScreen;

myScreen.clear();

myScreen.move( 2, 2 );

myScreen.set( '*' );

myScreen.display();

bufScreen.resize( 5, 5 );

bufScreen.display();

}

У объекта myScreen есть свои члены _width, _height, _cursor и _screen, а у объекта bufScreen – свои. Однако каждая функция-член класса существует в единственном экземпляре. Их и вызывают myScreen и bufScreen.

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

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

{

if ( checkRange( r, c ) ) // позиция на экране задана корректно?

{

int row = (r-1) * _width; // смещение строки

_cursor = row + c - 1;

}

}

Если функция move() вызывается для объекта myScreen, то члены _width и _height, к которым внутри нее имеются обращения, – это члены объекта myScreen. Если же она вызывается для объекта bufScreen, то и обращения производятся к членам данного объекта. Каким же образом _cursor, которым манипулирует move(), оказывается членом то myScreen, то bufScreen? Дело в указателе this.