Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Когда использовать указатель this



Наша функция main() вызывает функции-члены класса Screen для объектов myScreen и bufScreen таким образом, что каждое действие – это отдельная инструкция. У нас есть возможность определить функции-члены так, чтобы конкатенировать их вызовы при обращении к одному и тому же объекту. Например, все вызовы внутри main() будут выглядеть так:

int main() {

//...

 

myScreen.clear().move( 2, 2 ), set( '*' ). display();

bufScreen.reSize( 5, 5 ).display();

}

Именно так интуитивно представляется последовательность операций с экраном: очистить экран myScreen, переместить курсор в позицию (2, 2), записать в эту позицию символ '*' и вывести результат.

Операторы доступа “точка” и “стрелка” левоассоциативны, т.е. их последовательность выполняется слева направо. Например, сначала вызывается myScreen.clear(), затем myScreen.move() и т.д. Чтобы myScreen.move() можно было вызвать после myScreen.clear(), функция clear() должна возвращать объект myScreen, для которого она была вызвана. Мы уже видели, что доступ к объекту внутри функции-члена класса производится в помощью указателя this. Вот реализация clear():

// объявление clear() находится в теле класса

// в нем задан аргумент по умолчанию bkground = '#'

Screen& Screen:: clear( char bkground )

{ // установить курсор в левый верхний угол и очистить экран

 

_cursor = 0;

_screen.assign(  // записать в строку

_screen.size(), // size() символов

bkground      // со значением bkground

);

 

// вернуть объект, для которого была вызвана функция

return *this;

}

Обратите внимание, что возвращаемый тип этой функции-члена – Screen& – ссылка на объект ее же класса. Чтобы конкатенировать вызовы, необходимо также пересмотреть реализацию move() и set(). Возвращаемый тип следует изменить с void на Screen&, а в определении возвращать *this.

Аналогично функцию-член display() можно написать так:

Screen& Screen:: display()

{

typedef string:: size_type idx_type;

 

for ( idx_type ix = 0; ix < _height; ++ix )

{ // для каждой строки

 

idx_type offset = _width * ix; // смещение строки

 

for ( idx_type iy = 0; iy < _width; ++iy )

     // для каждой колонки вывести элемент

     cout < < _screen[ offset + iy ];

 

     cout < < endl;

   }

  return *this;

}

А вот реализация reSize():

// объявление reSize() находится в теле класса

// в нем задан аргумент по умолчанию bkground = '#'

Screen& Screen:: reSize( int h, int w, char bkground )

{ // сделать высоту экрана равной h, а ширину - равной w

// запомнить содержимое экрана

string local(_screen);

 

// заменить строку _screen

_screen.assign( // записать в строку

h * w,       // h * w символов

bkground    // со значением bkground

);

 

typedef string:: size_type idx_type;

idx_type local_pos = 0;

 

// скопировать содержимое старого экрана в новый

for ( idx_type ix = 0; ix < _height; ++ix )

{ // для каждой строки

 

  idx_type offset = w * ix; // смещение строки

  for ( idx_type iy = 0; iy < _width; ++iy )

     // для каждой колонки присвоить новое значение

     _screen[ offset + iy ] = local[ local_pos++ ];

}

 

_height = h;

_width = w;

// _cursor не меняется

 

return *this;

}

Работа указателя this не исчерпывается возвратом объекта, к которому была применена функция-член. При рассмотрении copy() в разделе 13.3 мы видели и другой способ его использования:

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& classType:: assign( const classType & source )

{

if ( this! = & source )

{

this-> ~classType();

new (this) classType( source );

}

return *this;

}

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

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

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

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

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

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

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

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

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

class Account {             // расчетный счет

Account( double amount, const string & owner );

string owner() { return _owner; }

private:

static double _interestRate; // процентная ставка

double   _amount;    // сумма на счету

string   _owner;     // владелец

};

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

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

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

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

 

#include " account.h"

double Account:: _interestRate = 0.0589;

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

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

#include < string>

class Account {

//...

private:

static const string name;

};

 

const string Account:: name( " Savings Account" );

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

// заголовочный файл

class Account {

//...

private:

static const int nameSize = 16;

static const string name[nameSize];

};

 

// исходный файл

const string Account:: nameSize; // необходимо определение члена

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 в определении, находящемся вне тела класса:

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 )

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

Account::

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

int compareRevenue( Account & ac1, Account *ac2 );

{

double ret1, ret2;

ret1 = Account:: _interestRate * ac1._amount;

ret2 = Account:: _interestRate * ac2-> _amount;

//...

}

Уникальная особенность статического члена – то, что он существует независимо от объектов класса, – позволяет использовать его такими способами, которые для нестатических членов недопустимы.

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

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

};


Поделиться:



Последнее изменение этой страницы: 2019-04-09; Просмотров: 276; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.064 с.)
Главная | Случайная страница | Обратная связь