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

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

617

class X { public:

X( int i ) { _val = i; } int val() { return _val; }

private: int _val;

};

class Y { public:

Y( int i ); static X xval();

static int callsXval(); private:

static X _xval; static int _callsXval;

};

Инициализируйте _xval значением 20, а _callsXval значением 0.

Упражнение 13.9

Используя классы из упражнения 13.8, реализуйте обе статические функции-члена для класса Y. callsXval() должна подсчитывать, сколько раз вызывалась xval().

Упражнение 13.10

// example.h class Example { public:

static double rate = 6.5;

static const int vecSize = 20; static vector<double> vec(vecSize);

};

// example.c #include "example.h" double Example::rate;

Какие из следующих объявлений и определений статических членов ошибочны? Почему? vector<double> Example::vec;

13.6. Указатель на член класса

Предположим, что в нашем классе Screen определены четыре новых функции-члена: forward(), back(), up() и down(), которые перемещают курсор соответственно вправо, влево, вверх и вниз. Сначала мы должны объявить их в теле класса:

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

618

class Screen { public:

inline Screen& forward(); inline Screen& back(); inline Screen& end(); inline Screen& up(); inline Screen& down();

//другие функции-члены не изменяются private:

inline int row();

//другие функции-члены не изменяются

};

Функции-члены forward() и back() перемещают курсор на один символ. По

достижении правого нижнего или левого верхнего угла экрана курсор переходит в

inline Screen& Screen::forward()

{// переместить _cursor вперед на одну экранную позицию

++_cursor;

// если достигли конца экрана, перепрыгнуть в противоположный угол if ( _cursor == _screen.size() )

home();

return *this;

}

inline Screen& Screen::back()

{// переместить _cursor назад на одну экранную позицию

//если достигли начала экрана, перепрыгнуть в противоположный угол

if ( _cursor == 0 ) end();

else --_cursor;

return *this;

противоположный угол.

}

end() перемещает курсор в правый нижний угол экрана и является парной по

inline Screen& Screen::end()

{

_cursor = _width * _height - 1; return *this;

отношению к функции-члену home():

}

Функции up() и down() перемещают курсор вверх и вниз на одну строку. По достижении верхней или нижней строки курсор остается на месте и подается звуковой сигнал:

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

619

const char BELL = '\007';

inline Screen& Screen::up()

{// переместить _cursor на одну строку вверх

// если уже наверху, остаться на месте и подать сигнал

if ( row() == 1 ) // наверху? cout << BELL << endl;

else

_cursor -= _width;

return *this;

}

inline Screen& Screen::down()

{

if ( row() == _height ) //внизу? cout << BELL << endl;

else

_cursor += _width;

return *this;

}

row() это закрытая функция-член, которая используется в функциях up() и down(),

inline int Screen::row()

{ // вернуть текущую строку

return ( _cursor + _width ) / height;

возвращая номер строки, где находится курсор:

}

Пользователи класса Screen попросили нас добавить функцию repeat(), которая

Screen &repeat( char op, int times )

{

switch( op ) {

case DOWN: // n раз вызвать Screen::down() break;

case DOWN: // n раз вызвать Screen::up() break;

// ...

}

повторяет указанное действие n раз. Ее реализация могла бы выглядеть так:

}

Такая реализация имеет ряд недостатков. В частности, предполагается, что функции- члены класса Screen останутся неизменными, поэтому при добавлении или удалении функции-члена repeat() необходимо модифицировать. Вторая проблема размер функции. Поскольку приходится проверять все возможные функции-члены, то исходный текст становится громоздким и неоправданно сложным.

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

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

620

операцию следует выполнить, и всю инструкцию switch можно удалить. Определение и использование указателей на члены класса тема последующих подразделов.

13.6.1. Тип члена класса

Указателю на функцию нельзя присвоить адрес функции-члена, даже если типы возвращаемых значений и списки параметров полностью совпадают. Например, переменная pfi это указатель на функцию без параметров, которая возвращает значение типа int:

int (*pfi)();

int HeightIs();

Если имеются глобальные функции HeightIs() и WidthIs() вида: int WidthIs();

pfi = HeightIs;

то допустимо присваивание pfi адреса любой из этих переменных: pfi = WidthIs;

В классе Screen также определены две функции доступа, height() и width(), не

inline int Screen::height() { return _height; }

имеющие параметров и возвращающие значение типа int: inline int Screen::width() { return _width; }

Однако попытка присвоить их переменной pfi является нарушением типизации и влечет

// неверное присваивание: нарушение типизации

pfi = &Screen::height;

ошибку компиляции:

В чем нарушение? У функций-членов есть дополнительный атрибут типа, отсутствующий у функций, не являющихся членами, – класс. Указатель на функцию-член должен соответствовать типу присваиваемой ему функции не в двух, а в трех отношениях: по типу и количеству формальных параметров; типу возвращаемого значения; типу класса, членом которого является функция.

Несоответствие типов между двумя указателями на функцию-член и на обычную функцию обусловлено их разницей в представлении. В указателе на обычную функцию хранится ее адрес, который можно использовать для непосредственного вызова. (Указатели на функции рассматривались в разделе 7.9.) Указатель же на функцию-член

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

621

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

Синтаксис объявления указателя на функцию-член должен принимать во внимание тип класса. То же верно и в отношении указателей на данные-члены. Рассмотрим член _height класса Screen. Его полный тип таков: член класса Screen типа short. Следовательно, полный тип указателя на _height это указатель на член класса Screen

типа short:

short Screen::*

Определение указателя на член класса Screen типа short выглядит следующим образом:

short Screen::*ps_Screen;

Переменную ps_Screen можно инициализировать адресом _height:

short Screen::*ps_Screen = &Screen::_height;

или присвоить ей адрес _width:

short Screen::*ps_Screen = &Screen::_width;

Переменной ps_Screen разрешается присваивать указатель на _width или _height, так как они являются членами класса Screen типа short.

Несоответствие типов указателя на данные-члены и обычного указателя также связано с различием в их представлении. Обычный указатель содержит всю информацию, необходимую для обращения к объекту. Указатель на данные-члены следует сначала привязать к объекту или указателю на него, а лишь затем использовать для доступа к члену этого объекта. (В книге “Inside the C++ Object Model” ([LIPPMAN96a]) также описывается представление указателей на члены.)

Указатель на функцию-член определяется путем задания типа возвращаемого функцией значения, списка ее параметров и класса. Например, следующий указатель, с помощью которого можно вызвать функции height() и width(), имеет тип указателя на функцию-член класса Screen без параметров, которая возвращает значение типа int:

int (Screen::*)()

// всем указателям на функции-члены класса можно присвоить значение 0 int (Screen::*pmf1)() = 0;

int (Screen::*pmf2)() = &Screen::height; pmf1 = pmf2;

Указатели на функции-члены можно объявлять, инициализировать и присваивать: pmf2 = &Screen::width;