<< Пред. стр. 59 (из 121) След. >>
{// пока еще ни одного объекта класса Account не объявлено
// правильно: вызов статической функции-члена
return limit <= Account::interest() ;
}
int main() {
double limit = 0.05;
if ( limitTest( limit ) )
{
// указатель на статическую функцию-член
// объявлен как обычный указатель
void (*psf)(double) = &Account::raiseInterest;
psf( 0.0025 );
}
Account ac1( 5000, "Asterix" );
Account ac2( 10000, "Obelix" );
if ( compareRevenue( ac1, &ac2 ) > 0 )
cout << ac1.owner()
<< " is richer than "
<< ac2.owner() << "\n";
else
cout << ac1.owner()
<< " is poorer than "
<< ac2.owner() << "\n";
return 0;
}
Упражнение 13.8
Пусть дан класс Y с двумя статическими данными-членами и двумя статическими функциями-членами:
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
};
// example.c
#include "example.h"
double Example::rate;
vector
13.6. Указатель на член класса
Предположим, что в нашем классе Screen определены четыре новых функции-члена: forward(), back(), up() и down(), которые перемещают курсор соответственно вправо, влево, вверх и вниз. Сначала мы должны объявить их в теле класса:
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() перемещает курсор в правый нижний угол экрана и является парной по отношению к функции-члену home():
inline Screen& Screen::end()
{
_cursor = _width * _height - 1;
return *this;
}
Функции up() и down() перемещают курсор вверх и вниз на одну строку. По достижении верхней или нижней строки курсор остается на месте и подается звуковой сигнал:
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(), которая повторяет указанное действие n раз. Ее реализация могла бы выглядеть так:
Screen &repeat( char op, int times )
{
switch( op ) {
case DOWN: // n раз вызвать Screen::down()
break;
case DOWN: // n раз вызвать Screen::up()
break;
// ...
}
}
Такая реализация имеет ряд недостатков. В частности, предполагается, что функции-члены класса Screen останутся неизменными, поэтому при добавлении или удалении функции-члена repeat() необходимо модифицировать. Вторая проблема – размер функции. Поскольку приходится проверять все возможные функции-члены, то исходный текст становится громоздким и неоправданно сложным.
В более общей реализации параметр op заменяется параметром типа указателя на функцию-член класса Screen. Теперь repeat() не должна сама устанавливать, какую операцию следует выполнить, и всю инструкцию switch можно удалить. Определение и использование указателей на члены класса – тема последующих подразделов.
13.6.1. Тип члена класса
Указателю на функцию нельзя присвоить адрес функции-члена, даже если типы возвращаемых значений и списки параметров полностью совпадают. Например, переменная pfi – это указатель на функцию без параметров, которая возвращает значение типа int:
int (*pfi)();
Если имеются глобальные функции HeightIs() и WidthIs() вида:
int HeightIs();
int WidthIs();
то допустимо присваивание pfi адреса любой из этих переменных:
pfi = HeightIs;
pfi = WidthIs;
В классе Screen также определены две функции доступа, height() и width(), не имеющие параметров и возвращающие значение типа int:
inline int Screen::height() { return _height; }
inline int Screen::width() { return _width; }
Однако попытка присвоить их переменной pfi является нарушением типизации и влечет ошибку компиляции:
// неверное присваивание: нарушение типизации
pfi = &Screen::height;
В чем нарушение? У функций-членов есть дополнительный атрибут типа, отсутствующий у функций, не являющихся членами, – класс. Указатель на функцию-член должен соответствовать типу присваиваемой ему функции не в двух, а в трех отношениях: по типу и количеству формальных параметров; типу возвращаемого значения; типу класса, членом которого является функция.
Несоответствие типов между двумя указателями – на функцию-член и на обычную функцию – обусловлено их разницей в представлении. В указателе на обычную функцию хранится ее адрес, который можно использовать для непосредственного вызова. (Указатели на функции рассматривались в разделе 7.9.) Указатель же на функцию-член должен быть сначала привязан к объекту или указателю на объект, чтобы получить 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;
Использование typedef может облегчить чтение объявлений указателей на члены. Например, для типа “указатель на функцию-член класса Screen без параметров, которая возвращает ссылку на объект Screen”, т.е.
Screen& (Screen::*)()
Следующий typedef определяет Action как альтернативное имя:
typedef Screen& (Screen::*Action)();
Action default = &Screen::home;
Action next = &Screen::forward;
Тип “указатель на функцию-член” можно использовать для объявления формальных параметров и типа возвращаемого значения функции. Для параметра того же типа можно также указать значение аргумента по умолчанию:
Screen& action( Screen&, Action)();
action() объявлена как принимающая два параметра: ссылку на объект класса Screen и указатель на функцию-член Screen без параметров, которая возвращает ссылку на его объект. Вызвать action() можно любым из следующих способов:
Screen meScreen;
typedef Screen& (Screen::*Action)();
Action default = &Screen::home;
extern Screen& action( Screen&, Sction = &Screen::display );
void ff()
{
action( myScreen );
action( myScreen, default );
action( myScreen, &Screen::end );
}
В следующем подразделе обсуждается вызов функции-члена посредством указателя.
13.6.2. Работа с указателями на члены класса
К указателям на члены класса можно обращаться только с помощью конкретного объекта или указателя на объект типа класса. Для этого применяется любой из двух операторов доступа (.* для объектов класса и ссылок на них или ->* для указателей). Например, так вызывается функция-член через указатель на нее:
int (Screen::*pmfi)() = &Screen::height;
Screen& (Screen::*pmfS)( const Screen& ) = &Screen::copy;
Screen myScreen, *bufScreen;
// прямой вызов функции-члена
if ( myScreen.height() == bufScreen->height() )
bufScreen->copy( myScreen );
// эквивалентный вызов по указателю
if ( (myScreen.*pmfi)() == (bufScreen->*pmfi)() )
(bufScreen->*pmfS)( myScreen );
Вызовы
(myScreen.*pmfi)()
(bufScreen->*pmfi)();
требуют скобок, поскольку приоритет оператора вызова () выше, чем приоритет взятия указателя на функцию-член. Без скобок
myScreen.*pmfi()
интерпретируется как
myScreen.*(pmfi())
Это означает вызов функции pmfi() и привязку возвращенного ей значения к оператору (.*). Разумеется, тип pmfi не поддерживает такого использования, так что компилятор выдаст сообщение об ошибке.
Указатели на данные-члены используются аналогично:
typedef short Screen::*ps_Screen;
Screen myScreen, *tmpScreen = new Screen( 10, 10 );
ps_Screen pH = &Screen::_height;
ps_Screen pW = &Screen::_width;
tmpScreen->*pH = myScreen.*pH;
tmpScreen->*pW = myScreen.*pW;
Приведем реализацию функции-члена repeat(), которую мы обсуждали в начале этого раздела. Теперь она будет принимать указатель на функцию-член:
typedef Screen& (Screen::Action)();
Screen& Screen::repeat( Action op, int times )
{
for ( int i = 0; i < times; ++i )
(this->*op)();
return *this;
}
Параметр op – это указатель на функцию-член, которая должна вызываться times раз.
Если бы нужно было задать значения аргументов по умолчанию, то объявление repeat() выглядело бы следующим образом:
class Screen {
public:
Screen &repeat( Action = &Screen::forward, int = 1 );
// ...
};
А ее вызовы так:
Screen myScreen;
myScreen.repeat(); // repeat( &Screen::forward, 1 );
myScreen.repeat( &Screen::down, 20 );
Определим таблицу указателей. В следующем примере Menu – это таблица указателей на функции-члены класса Screen, которые реализуют перемещение курсора. CursorMovements – перечисление, элементами которого являются номера в таблице Menu.
Action::Menu() = {
&Screen::home,
&Screen::forward,
&Screen::back,
&Screen::up,
&Screen::down,
&Screen::end
};
enum CursorMovements {
HOME, FORWARD, BACK, UP, DOWN, END
};
Можно определить перегруженную функцию-член move(), которая принимает параметр CursorMovements и использует таблицу Menu для вызова указанной функции-члена. Вот ее реализация:
Screen& Screen::move( CursorMovements cm )
{
( this->*Menu[ cm ] )();
return *this;
}
У оператора взятия индекса ([]) приоритет выше, чем у оператора указателя на функцию-член (->*). Первая инструкция в move() сначала по индексу выбирает из таблицы Menu нужную функцию-член, которая и вызывается с помощью указателя this и оператора указателя на функцию-член. move() можно применять в интерактивной программе, где пользователь выбирает вид перемещения курсора из отображаемого на экране меню.
13.6.3. Указатели на статические члены класса
Между указателями на статические и нестатические члены класса есть разница. Синтаксис указателя на член класса не используется для обращения к статическому члену. Статические члены – это глобальные объекты и функции, принадлежащие классу. Указатели на них – это обычные указатели. (Напомним, что статической функции-члену не передается указатель this.)
Объявление указателя на статический член класса выглядит так же, как и для указателя на объект, не являющийся членом класса. Для разыменования указателя никакой объект не требуется. Рассмотрим класс Account:
class Account {
public:
static void raiseInterest( double incr );
static double interest() { return _interestRate ; }
double amount() { return _amount; }
private:
static double _interestRate;
double _amount;
string _owner;
};
inline void Account::raiseInterest( double incr )
{
_interestRate += incr;
}
Тип &_interestRate – это double*: