Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]
.pdfС++ для начинающих |
612 |
#include <string> class Account {
// ...
private:
static const string name;
};
const string Account::name( "Savings Account" );
Константный статический член целого типа инициализируется константой внутри тела класса: это особый случай. Если бы для хранения названия счета мы решили использовать массив символов вместо строки, то его размер можно было бы задать с
// заголовочный файл class Account {
//...
private:
static const int nameSize = 16; static const string name[nameSize];
};
// исходный файл
const string Account::nameSize; // необходимо определение члена
помощью константного члена типа int:
const string Account::name[nameSize] = "Savings Account";
Отметим, что константный статический член целого типа, инициализированный константой, – это константное выражение. Проектировщик может объявить такой статический член, если внутри тела класса возникает необходимость в именованной константе. Например, поскольку константный статический член nameSize является константным выражением, проектировщик использует его для задания размера члена- массива с именем name.
Даже если такой член инициализируется в теле класса, его все равно необходимо задать вне определения класса. Однако поскольку начальное значение уже задано в объявлении, то при определении оно не указывается.
Так как name – это массив (и не целого типа), его нельзя инициализировать в теле класса.
class Account { //...
private:
static const int nameSize = 16; // правильно: целый тип
static const string name[nameSize] = "Savings Account"; // ошибка
Попытка поступить таким образом приведет к ошибке компиляции:
};
Член name должен быть инициализирован вне определения класса.
Обратите внимание, что член nameSize задает размер массива name в определении, находящемся вне тела класса:
С++ для начинающих |
613 |
const string Account::name[nameSize] = "Savings Account";
nameSize не квалифицирован именем класса Account. И хотя это закрытый член, определение name не приводит к ошибке. Как такое может быть? Определение статического члена аналогично определению функции-члена класса, которое может ссылаться на закрытые члены. Определение статического члена name находится в области видимости класса и может ссылаться на закрытые члены, после того как распознано квалифицированное имя Account::name. (Подробнее об области видимости класса мы поговорим в разделе 13.9.)
Статический член класса доступен функции-члену того же класса и без использования
inline double Account::dailyReturn()
{
return( _interestRate / 365 * _amount );
соответствующих операторов:
}
Что же касается функций, не являющихся членами класса, то они могут обращаться к
class Account { // ...
private:
friend int compareRevenue( Account&, Account* ); // остальное без изменения
};
//мы используем ссылочный и указательный параметры,
//чтобы проиллюстрировать оба оператора доступа
int compareRevenue( Account &ac1, Account *ac2 );
{
double ret1, ret2;
ret1 = ac1._interestRate * ac1._amount; ret2 = ac2->_interestRate * ac2->_amount; // ...
статическому члену двумя способами. Во-первых, посредством операторов доступа:
}
Как ac1._interestRate, так и ac2->_interestRate относятся к статическому члену Account::_interestRate.
Поскольку есть лишь одна копия статического члена класса, до нее необязательно добираться через объект или указатель. Другой способ заключается в том, чтобы
// доступ к статическому члену с указанием квалифицированного имени
обратиться к статическому члену напрямую, квалифицировав его имя именем класса: if ( Account::_interestRate < 0.05 )
Если обращение к статическому члену производится без помощи оператора доступа, то его имя следует квалифицировать именем класса, за которым следует оператор разрешения области видимости:
С++ для начинающих |
614 |
Account::
Это необходимо, поскольку такой член не является глобальным объектом, а значит, в глобальной области видимости отсутствует. Следующее определение дружественной
int compareRevenue( Account &ac1, Account *ac2 );
{
double ret1, ret2;
ret1 = Account::_interestRate * ac1._amount; ret2 = Account::_interestRate * ac2->_amount; // ...
функции compareRevenue эквивалентно приведенному выше:
}
Уникальная особенность статического члена – то, что он существует независимо от объектов класса, – позволяет использовать его такими способами, которые для нестатических членов недопустимы.
∙ статический член может принадлежать к типу того же класса, членом которого он является. Нестатические объявляются лишь как указатели или ссылки на объект
class Bar { |
|
public: |
|
// ... |
|
private: |
// правильно |
static Bar mem1; |
|
Bar *mem2; |
// правильно |
Bar mem3; |
// ошибка |
своего класса:
};
∙статический член может выступать в роли аргумента по умолчанию для
extern int var;
class Foo { private:
int var;
static int stcvar; public:
//ошибка: трактуется как Foo::var,
//но ассоциированного объекта класса не существует
int mem1( int = var );
//правильно: трактуется как static Foo::stcvar,
//ассоциированный объект и не нужен
int mem2( int = stcvar );
// правильно: трактуется как глобальная переменная var int mem3( int = :: var );
функции-члена класса, а для нестатического это запрещено:
};
С++ для начинающих |
615 |
13.5.1. Статические функции-члены
Функции-члены raiseInterest() и interest() обращаются к глобальному
class Account { public:
void raiseInterest( double incr );
double interest() { return _interestRate; } private:
static double _interestRate;
};
inline void Account::raiseInterest( double incr )
{
_interestRate += incr;
статическому члену _interestRate:
}
Проблема в том, что любая функция-член должна вызываться с помощью оператора доступа к конкретному объекту класса. Поскольку приведенные выше функции обращаются только к статическому _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.
С++ для начинающих |
616 |
Статическую функцию-член можно вызвать для объекта класса, пользуясь одним из операторов доступа. Ее также можно вызвать непосредственно, квалифицировав ее имя, даже если никаких объектов класса не объявлено. Вот небольшая программа,
#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 с двумя статическими данными-членами и двумя статическими функциями-членами:
С++ для начинающих |
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;