Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
234
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

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

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;