<< Пред.           стр. 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 vec(vecSize);
 };
 
 // example.c
 #include "example.h"
 double Example::rate;
 vector Example::vec;
 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*:

<< Пред.           стр. 59 (из 121)           След. >>

Список литературы по разделу