Сделай Сам Свою Работу на 5

Статические функции-члены





Функции-члены raiseInterest() и interest() обращаются к глобальному статическому члену _interestRate:

class Account { public: void raiseInterest( double incr ); double interest() { return _interestRate; } private: static double _interestRate; };   inline void Account::raiseInterest( double incr ) { _interestRate += incr;

}

Проблема в том, что любая функция-член должна вызываться с помощью оператора доступа к конкретному объекту класса. Поскольку приведенные выше функции обращаются только к статическому _interestRate, то совершенно безразлично, для какого объекта они вызываются. Нестатические члены при вызове этих функций не читаются и не модифицируются.

Поэтому лучше объявить такие функции-члены как статические. Это можно сделать следующим образом:

class Account { public: static void raiseInterest( double incr ); static double interest() { return _interestRate; } private: static double _interestRate; };   inline void Account::raiseInterest( double incr ) { _interestRate += incr;

}

Объявление статической функции-члена почти такое же, как и нестатической: в теле класса ему предшествует ключевое слово static, а спецификаторы const или volatile запрещены. В ее определении, находящемся вне тела класса, слова static быть не должно.

Такой функции-члену указатель this не передается, поэтому явное или неявное обращение к нему внутри ее тела вызывает ошибку компиляции. В частности, попытка обращения к нестатическому члену класса неявно требует наличия указателя this и, следовательно, запрещена. Например, представленную ранее функцию-член dailyReturn() нельзя объявить статической, поскольку она обращается к нестатическому члену _amount.



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

#include <iostream> #include "account.h"   bool limitTest( double limit ) { // пока еще ни одного объекта класса 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<double> vec(vecSize); };   // example.c #include "example.h" double Example::rate;

vector<double> Example::vec;

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

Предположим, что в нашем классе 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 можно удалить. Определение и использование указателей на члены класса – тема последующих подразделов.

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

Указателю на функцию нельзя присвоить адрес функции-члена, даже если типы возвращаемых значений и списки параметров полностью совпадают. Например, переменная 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 );

}

В следующем подразделе обсуждается вызов функции-члена посредством указателя.

 








Не нашли, что искали? Воспользуйтесь поиском по сайту:



©2015 - 2024 stydopedia.ru Все материалы защищены законодательством РФ.