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

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

606

К дополнительным перемещениям курсора можно отнести его передвижение вперед и назад на один символ. Из правого нижнего угла экрана курсор должен попасть в левый верхний угол. Реализуйте функции 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, то и обращения производятся к членам данного

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

607

объекта. Каким же образом _cursor, которым манипулирует move(), оказывается членом то myScreen, то bufScreen? Дело в указателе this.

Каждой функции-члену передается указатель на объект, для которого она вызвана, – 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;

}

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

}

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

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

myScreen.move( 2, 2 );

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

move( &myScreen, 2, 2 );

Программист может явно обращаться к указателю this внутри функции. Так, вполне

inline void Screen::home()

{

this->_cursor = 0;

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

}

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

608

Однако бывают случаи, когда без такого обращения не обойтись, как мы видели на примере функции-члена 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, для которого она была вызвана. Мы уже видели, что доступ к объекту внутри функции-члена

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

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

Screen& Screen::clear( char bkground )

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

_cursor = 0;

// записать в строку

_screen.assign(

_screen.size(),

// size() символов

bkground

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

);

 

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

класса производится в помощью указателя this. Вот реализация clear():

}

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

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

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

609

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() находится в теле класса

//в нем задан аргумент по умолчанию bkground = '#' Screen& Screen::reSize( int h, int w, char bkground )

{ // сделать высоту экрана равной h, а ширину - равной w

//запомнить содержимое экрана

string local(_screen);

// заменить строку _screen

_screen.assign(

// записать в строку

h * w,

// h * w символов

bkground

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

);

 

typedef string::size_type idx_type; idx_type local_pos = 0;

// скопировать содержимое старого экрана в новый for ( idx_type ix = 0; ix < _height; ++ix )

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

idx_type offset = w * ix; // смещение строки for ( idx_type iy = 0; iy < _width; ++iy )

// для каждой колонки присвоить новое значение

_screen[ offset + iy ] = local[ local_pos++ ];

}

_height = h; _width = w;

// _cursor не меняется

return *this;

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

}

Работа указателя this не исчерпывается возвратом объекта, к которому была применена функция-член. При рассмотрении copy() в разделе 13.3 мы видели и другой способ его использования: