Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

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

610

void Screen::copy( const Screen& sobj )

{

//если этот объект Screen и sobj - одно и то же,

//копирование излишне

if ( this != sobj )

{

// скопировать значение sobj в this

}

}

Указатель this хранит адрес объекта, для которого была вызвана функция-член. Если адрес, на который ссылается sobj, совпадает со значением this, то sobj и this относятся к одному и тому же объекту, так что операция копирования не нужна. (Мы еще встретимся с этой конструкцией, когда будем рассматривать копирующий оператор присваивания в разделе 14.7.)

Упражнение 13.7

Указатель this можно использовать для модификации адресуемого объекта, а также для его замены другим объектом того же типа. Например, функция-член assign() класса

classType& classType::assign( const classType &source )

{

if ( this != &source )

{

this->~classType();

new (this) classType( source );

}

return *this;

classType выглядит так. Можете ли вы объяснить, что она делает?

}

Напомним, что ~classType это имя деструктора. Оператор new выглядит несколько причудливо, но мы уже встречались с подобным в разделе 8.4.

Как вы относитесь к такому стилю программирования? Безопасна ли эта операция? Почему?

13.5. Статические члены класса

Иногда нужно, чтобы все объекты некоторого класса имели доступ к единственному глобальному объекту. Допустим, необходимо подсчитать, сколько их было создано; глобальным может быть указатель на процедуру обработки ошибок для класса или, скажем, указатель на свободную память для его объектов. В подобных случаях более эффективно иметь один глобальный объект, используемый всеми объектами класса, чем отдельные члены в каждом объекте. Хотя такой объект является глобальным, он существует лишь для поддержки реализации абстракции класса.

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

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

611

По сравнению с глобальным объектом у статического члена есть следующие преимущества:

статический член не находится в глобальном пространстве имен программы, следовательно, уменьшается вероятность случайного конфликта имен с другими глобальными объектами;

остается возможность сокрытия информации, так как статический член может быть закрытым, а глобальный объект никогда.

Чтобы сделать член статическим, надо поместить в начале его объявления в теле класса ключевое слово static. К ним применимы все правила доступа к открытым, закрытым и защищенным членам. Например, для определенного ниже класса Account член

class Account { // расчетный счет Account( double amount, const string &owner ); string owner() { return _owner; }

private:

 

// процентная ставка

static double _interestRate;

double

_amount;

// сумма на счету

string

_owner;

// владелец

_interestRate объявлен как закрытый и статический типа double:

};

Почему _interestRate сделан статическим, а _amount и _owner нет? Потому что у всех счетов разные владельцы и суммы, но процентная ставка одинакова. Следовательно, объявление члена _interestRate статическим уменьшает объем памяти, необходимый для хранения объекта Account.

Хотя текущее значение _interestRate для всех счетов одинаково, но со временем оно может изменяться. Поэтому мы решили не объявлять этот член как const. Достаточно модифицировать его лишь один раз, и с этого момента все объекты Account будут видеть новое значение. Если бы у каждого объекта была собственная копия, то пришлось бы обновить их все, что неэффективно и является потенциальным источником ошибок.

В общем случае статический член инициализируется вне определения класса. Его имя во внешнем определении должно быть специфицировано именем класса. Вот так можно

// явная инициализация статического члена класса

#include "account.h"

инициализировать _interestRate:

double Account::_interestRate = 0.0589;

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

Вобъявлении статического члена можно указать любой тип. Это могут быть константные объекты, массивы, объекты классов и т.д. Например:

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

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 );

функции-члена класса, а для нестатического это запрещено:

};