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

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
267
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

380

Часть II * Объвктио-ортештрованиое програмыироваиив но С'^-^

 

Как можно видеть, возврат значения-объекта — потенциально медленный

 

метод, и возврат указателя на объект позволяет избежать копирования полей

 

объекта. Когда возвращается ссылка на объект, ситуация недостаточно ясна. Если

 

ближайшим является целевой объект, функция closestPointRef() возвращает

 

ссылку на ближайший объект Point. Когда ближе находится целевой объект,

 

нужно использовать указатель this. Так как нельзя присваивать указатель ссылке,

 

следует применять для целевого объекта запись *this, однако нул^о иметь

 

в виду, что это не означает, что создается копия целевого объекта. Это только

 

запись. Подобно передаче параметров по ссылке, копируется лишь адрес (ссыл­

 

ка), а не поля объекта. Если ближе объект, заданный параметром, то его ссылка р

 

используется непосредственно и с тем же результатом — копируется лишь ссыл­

 

ка, но не поля.

 

 

Point &г = р1.ClosestPointRef(р2);

/ / возвращается ссылка: быстрый метод

 

r.setPoint(0,0);

/ / перемещение р1 или р2 в начало координат

Если же принимающая переменная в клиенте представляет один из объектных типов, а не ссылочный тип, то имеет место копирование:

Point pt = р1.ClosestPointRef(р2); / / р1 или р2 копируется в pt

Как можно видеть, при возврате ссылки на объект не всегда решаются пробле­ мы производительности.

Основной стимул возврата ссылки на объект (когда результат возвращается по значению, по ссылке или по указателю) — возможность организации цепочки сообщений, передачи сообщения возвращаемому функцией объекту.

Point р1, р2; pl.setPoint(20,40); р2.setPoint(30,50)

 

 

int

а =

p1.closestPointVal(p2).getX();

/ /

более медленный способ

int

b =

(*pl.closestPointPtr(p2)).getX();

/ /

быстро

и элегантно

int

с =

p1.closestPointRef(p2)).getX();

/ /

быстро

и элегантно

Здесь объект, возвращаемый функцией closestPointValO, представляет собой временный неименованный объект Point. Он существует достаточно долго, чтобы передать сообщение функции getX(), после чего исчезает. В двух других вызовах функции указатель и ссылка обозначают один из объектов, определенных в кли­ енте. Вопрос срока жизни такого объекта не стоит — он существует здесь и все.

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

р1.ClosestPointRef(р2).setPoint(15,35);

 

/ /

что здесь устанавливается? р1? р2?

p1.closestPointPtr(p2)->setPoint(10,30);

/ / что здесь устанавливается?

В первом из этих примеров клиент изменяет объект р1 или р2 и изменения сохраняются. Во второй строке изменяется временный неименованный объект, который немедленно уничтожается! Такая операция совершенно бесполезна, но в C++ вполне законна.

p1.closestPointVal(p2).setPoint(0,0);

//создание объекта, присваивание

/ /

значений полям и уничтожение объекта

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

Глава 9 • Классы C^-^f кок единицы 1^0Аудьности nporpoiviivibi

[ 381 щ

Еще о ключевом слове const

Данный раздел очень важен. В нем анализируется разный смысл ключевого слова const и показывается, как использовать его для решений одной из наиболее важных задач создания ПО — передачи идей разработчике о свойствах програм­ мных компонентов, чтобы их можно было использовать при сопровождении программы. Неудачное ее решение — один из простейших (и самых распростра­ ненных) способов внести йклад в кризис ПО.

Как указывалось ранее (в главах 4 и 7), ключевое слово const может иметь в языке C++ разный смысл. Он зависит от контекста. Когда это ключевое слово предшествует имени типа переменной, оно указывает, что значение переменной остается постоянным. Переменная должна инициализироваться в определении, и любая попытка присвоить ей другое (или даже то же самое) значение будет по­ мечена как синтаксическая ошибка.

const

int - 5;

/ /

х не будет (и не может) изменяться

X = 20;

/ /

синтаксическая ошибка: нельзя изменять х

int *у

= &х;

/ /

синтаксическая ошибка: предотвращает будущие изменения х

Когда указатель ссылается на "постоянную переменную" (невольный каламбур — она называется переменной, хотя на самом деле не изменяется), его нужно поме­ тить как указатель на константу, для чего используется ключевое слово const, предшествуюш,ее имени типа. После этого любые попытки разыменования ука­ зателя как 1-значения будут давать синтаксическую ошибку.

const int

*р1 = &х;

/ /

ОК, *р1 не будет использоваться для изменения х

*р1 = 0;

 

/ /

синтаксическая ошибка: *р1 не может быть 1-значением

int а = 5;

/ /

обычная переменная: ее можно изменять

р1 = &а;

*р1 = 0;

/ /

синтаксическая ошибка: 'а' нельзя изменять через *р1

Когда ссылка указывает на переменную const, ее нужно пометить как ссылку на константу. Для этого перед именем типа указывается ключевое слово const, после чего любая попытка использовать ссылку как 1-значение считается синтак­ сической ошибкой.

int &г1 = х;

/ /

синтаксическая ошибка: х не должна изменяться через

г1

const int

&г2 = х;

/ /

ОК, ссылка на константу, х изменяться не будет

 

г2 = 0;

 

/ /

синтаксическая ошибка: г2 - ссылка на константу

 

const int

&гЗ = а;

/ /

'а' может изменяться, но не через гЗ

 

гЗ = 0;

 

/ / синтаксическая ошибка: 'а' не может изменяться через

гЗ

Когда ключевое слово const следует за операцией указателя, это означает, что указатель имеет тип const. Такой указатель должен все время ссылаться на одно и то же место, его нельзя переназначить на другой адрес памяти. Однако нет ника­ кой гарантии, что не будет изменяться само значение по указателю.

int*

const

р2 = &а;

/ /

р2 будет указывать только на 'а'

*р2

= 0;

 

/ /

ОК: нет никакой гарантии, что это будет const

i n t

b = 5;

р2 = &b;

/ /

синтаксическая ошибка: нарушение обязательств

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

int& г4 = а;

/ /

г4 указывает только на 'а' , const здесь не нужно

г4 = Ь;

/ /

нет синтаксической ошибки, г не переназначено

382

Часть II • Объектно-ориентированное программирование на С-^-ь

Применение ключевого слова const в интерфейсах функций аналогично его использованию со значениями иуказателями. Оно утверждает, что данный фак­ тический аргумент (или указатель) в результате вызова не изменяется.

void f1(const int& x); void f2(const int x); void f3(int* const y); void f4(int * const *y); void f5(const int *&y);

//X не изменяется функцией

//лишнее: х в любом случае передается позначению

//лишнее: у передается по значению

//ОК, указатель передается по указателю

//ОК, указатель передается по ссылке

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

Если вы пропустили данный раздел, то позвольте напомнить,что то же самое делают три функции: closestPointValO, closestPointPtr() и closestPointRef(). Каждая из них сравнивает расстояние между целевым объектом и началом коор­ динат, между объектом, переданным в параметре, и начальной точкой. Если це­ левой объект ближе к началу координат, то каждая функция возвраш,ает целевой объект. Если ближе объект-параметр, то каждая функция возвраидает объектпараметр. Разница в том, что closestPointValO возвраидаетсам объект, closestPointPtrO возвраш^ает указатель на объект, а closestPointRefO — ссылку на объект. В разделе, посвяш,енном возврату объектов в клиенте, для параметра функции не использовалось ключевое слово const. В следуюидем примере оно добавлено.

class Point

// закрытые данные

{ int X, у;

public:

// общие операции

. . .

//setPointO, getXO, getY(),

. . .

// getPtrO, getRefO

//getDistPtrO, getDistRef()

Point closestPointVal(const Point& pt)

// не подходит: данные копируются

{ if (х*х + у*у < pt.x *pt.x + pt.y * pt.y)

return *this;

// значение объекта:

else

// копирование в объект temp

// значение объекта:

return pt; }

 

// копирование в объект temp

Point closestPointPtr (const Point& pt)

// параметр const

{ return (x*x + y*y < p.x*p.x + p.y* p.y)? this : &р; }

// ошибка

Point& closestPointRef (const Point& p)

// параметр const

{ return (x*x + y*y < p.x*p.x + p.y* p.y)? *this : р; } } ;

// ошибка

Функция ClosestPointValO возвраш.ает либо целевой объект, либо объектпараметр,но влюбом случае это копия объекта Point. Следовательно, ключевое

слово const для параметра функции не ограничивает ее использование. Если

клиент изменяет возвраш.енный объект, это изменение копии фактического аргу­

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

Point р1,р2;

р1.setPoint(20,40); р2.setPoint(30,50);

// устанавливает значения полей Point

Point pt = p1.closestPoint\/al(p2);

// нет нарушения обязательств

pt.setPoint(0,0);

p1.closestPointVal(p2).setPoint(0,0);

// не вредит, но и пользы никакой

Глава 9 • Классы C++ как единицы модульности программы

383 1

Функция closestPointPtrO способна возвращать указатель на свой аргумент Point. Этот указатель может затем использоваться клиентом для изменения состо­ яния объекта-аргумента. Аналогично функция closestPointRef () может возвра­ щать ссылку на свой аргумент Point. Данную ссылку разрешается использовать для изменения состояния объекта-аргумента.

Point *р = р1 .closestPointPtr(p2);

 

/ /

р2 не должен изменяться

p->setPoint(0,0);

/ / р2 может измениться

-

нарушение обязательств

Point &г = р1. ClosestPointRef(р2);

/ /

р2 не должен изменяться

r.setPoint(10,10);

/ / р2 может измениться

-

нарушение обязательств

В данном примере объект р2 реально не изменяется, так как все три функции возвращают объект р1, который ближе к точке начала координат, чем р2. Даже если объект р2 модифицируется, то это происходит вне функций closestPointRef () и ClosestPointPtrO! Тем не менее C4--I- не допускает такое использование объек­ тов-констант. Компилятору очень трудно выявить подобные нарушения при анали­ зе кода клиента (да и человеку — тоже), поэтому он объявляет обе функции ошибочными.

Формальная причина состоит в том, что объект-параметр (например, в функ­ ции closestPointPt г()) имеет ключевое слово const, а возвращаемый тип — нет.

Point* closestPointPtr(const

Point&

р)

/ / несогласованность: вредит const

{ return (х*х + у*у < р.х*р.х

+ р.у*

р.у)

? this : &р; }

 

 

 

//синтаксическая ошибка

C-f-+ предлагает три способа выхода из данной ситуации. Один из них — иск­ лючить ключевое слово const из интерфейса функции. Второй способ избежать синтаксической ошибки состоит в применении операции const_cast, подавляю­ щей свойство const в функциях-членах. Третий способ — использовать ключевое слово const двумя другими способами.

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

Второй метод сложнее. Операция const_cast преобразует свой аргумент-кон­ станту в тот же тип, снимая защиту от изменений. Тип задается в угловых скобках между операцией const_cast и аргументом. Например выражение, записанное таким образом: const_cast<THn_3Ha4eHHfl>(значение-константа), приводит аргумент "значение-константа" типа "тип_значения" к тому же типу "тип_значения", но

снимает

защиту от изменений. Для класса Point с помощью выражения

const_cast<Point*>(&p) указатель на постоянный

объект Point преобразуется

в указатель на непостоянный объект Point.

 

 

 

Вот версия класса Point, в которой отменяется свойство const аргумента при

возврате значения из функций-членов:

 

 

class

Point

 

 

 

{

int

х,у;

 

 

 

public:

 

 

 

 

 

. . .

/AsetPointO, getXO, getY(),

getPtrO, getRefO

 

. . .

/ / g e t D i s t P t r O , getDistRef ()

closestPointValO

 

Point*

closestPointPtr(const

Point& p)

/ / предотвращает изменение p

 

{ return (x*x + y*y < p.x*p.x

+ p.y *p.y) ? this

: const_cast<Point*>(&p); }

 

Point*

ClosestPointRef(const

Point& p)

/ / предотвращает изменение p

{

return

(x*x + y*y < p.x*p.x + p.y*p.y) ? *this

: const_cast<Point&>(p);

}

} ;

 

 

 

 

 

384

Часть II • Объектно-ориентированное орограг^^^лировоние на С'^^

Теперь в клиенте допускается изменение и возврат объектов, но это решение методом "грубой силы". Использовать его не стоит, хотя это лучше, чем удаление ключевого слова const из параметра функции, так как без const параметр в функ­ ции можно использовать как угодно. Применение ключевого слова const_cast снимает защиту только для данной конкретной операции (в нашем примере — возврат значения), но не в общ.ем случае. Однако это довольно неуклюжий и не очень понятный метод.

Лучший способ для успешной компиляции closestPointPtrO и closestPointRef() — не изменять возвраш.аемый объект, объявив его константой. Для этого нужно поставить перед возвраш.аемым функцией значением ключевое с/юво const. Это и есть третий смысл ключевого слова const (о четвертом — чуть далее). При использовании с возвраш,аемым функцией значением оно предотвраш^ает модифи­ кацию этого значения вызываюш,ей программой. Таким образом возвраш^аемое значение может использовать как г-значение, но не как 1-значение.

class

Point

 

 

 

 

{ int

X, у;

 

 

 

 

public:

 

 

 

 

/ /

. . . setPointO, getXO и т. д.

 

 

 

 

const

Point* closestPointPtr(const Point&

p)

 

 

//ОК

{

return (x*x + y*y < p.x*p.x + p.y*p.y)

? this

: &p;

}

const

Point* closestPointRef(const Point&

p)

 

 

//ок

{

return (x*x + y*y < p.x*p.x + p.y*p.y)

? *this

: p;

} } ;

Теперь клиент ограничен в использовании объектов.

 

 

Point р1. р2;p1.setPoint(20,40); p2.setPoint(30,50);

 

 

Point *ptr = p1.closestPointPtr(p2);

/ /

синтаксическая

ошибка:

Point &ref =p1.closestPointRef(p2);

/ /

должно быть const

/ /

синтаксическая

ошибка:

const Point *p = p1.cloststPointPtr(p2);

/ /

должно быть const

/ /

*р -

г-значение

p->setPoint(0,0);

/ /

синтаксическая

ошибка:

const Point &r = p1.closestPointRef(p2);

/ /

нельзя изменять объект

/ /

г не может быть 1-значением

r.setPoint(10,10);

/ /

синтаксическая

ошибка:

 

 

 

/ /

нельзя изменять объект

Так для чего же хорошо использовать указатель р и ссылку г? Ответ очевиден: они не позволяют вызывать функции вида setPointO, модифицируюш,ие целевой объект, но должны иметь возможность вызывать функции типа getX(), этот объект не изменяюш^ие. Примерно так.

int х1 = p->getX();

// р указывает на константу Point

int х2 = r.getXO;

// гссылается на константу Point

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

х1=(р1.closestPointPtг(р2)). getX();

// синтаксическая ошибка

х2=р1.closestPointPtr(p2). getX();

// синтаксическая ошибка

Надеюсь, что, еще не владея бегло деталями этого синтаксиса, вы уловили обш^ий смысл обсуждения. C + + предлагает ключевое слово const,которое ис­

пользуется компилятором и сопровождающим приложение программистом для

определения изменения объекта при выполнении программы. М ы рассмотрели способ предотвраш^ения изменений в значении, указателе, указателе-параметре

и возвращаемом значении функции (указателе или ссылке).

Глава 9 • Классы С-^-^ как единицы модульности программы

[ 385 |

Естественно, указатель или ссылка на объект-константу не могут изменяться при вызове таких функций, как setPointO, ведь setPointO изменяет состояние объекта по указателю или ссылке. Но что плохого в вызове такой невинной функции, как getXO? Она же не изменяет состояния объекта по указателю или ссылке?

Это снова возвращает нас к фундаментальному идеологическому вопросу, обсуждаемому в главе 7 при описании параметров функции. Откуда мы знаем, изменяет функция параметр или нет? Мы не хотим исследовать для этого тело функции, а хотим взглянуть просто на ее заголовок. Если в заголовке говорится, что параметр есть константа, то понятно, что он не изменяется. Если же в заго­ ловке это не сказано, можно считать, что он изменятся, независимо от того, что делает функция.

Компилятор С+Ч- следует той же логике. Он достаточно интеллектуален, чтобы найти ключевое слово const в заголовке и пометить изменения параметра как синтаксическую ошибку. Но он не настолько умен, чтобы анализировать исходный код функции и приходить к независимому заключению об изменениях параметров. Предполагается, что если ключевое слово const отсутствует, то параметры изменяются.

Теперь вернемся к двум функциям setPointO и getX(). Откуда известно, что первая изменяет объект, а вторая — нет? Достаточно взглянуть на программу и заголовок. Это очевидно, не так ли? Но не для компилятора С-+-+. Он помечает вызов setPointO как ошибочный не потому, что знает об изменении объекта в данной функции, а потому, что не видит свидетельства обратного. Для компиля­ тора функция getXO ничем не лучше setPointO. Если ничто не указывает, что getXO оставляет объект без изменений, то компилятор приходит к выводу, что getXO изменяет состояние объекта.

Вот здесь-то в C++ используется четвертый смысл ключевого слова const. Это ключевое слово включается между закрываюндей круглой скобкой списка па­ раметров и открываюндей фигурной скобкой тела функции. В прототипе функции оно вставляется между закрываюидей круглой скобкой и точкой с запятой. Здесь класс Point явно указывает, что его функции-члены делают:

1) с параметрами функции;

2) с возвраш^аемыми функцией значениями;

3) с элементами данных целевого объекта:

class

Point

 

 

 

 

 

 

 

 

 

 

{ int

X, у;

 

 

 

 

 

 

 

 

 

 

public:

 

 

 

 

 

 

 

 

 

 

void setPoint(int

a,

int b)

/ /

модифицирует поля,

не так ли?

 

{

X = а;

у = Ь;

}

 

 

 

 

 

 

 

int

getXO

const

 

 

/ /

не модифицирует поля: где

свидетельство?

{

return

х;

}

 

 

 

 

 

 

 

 

int

getYO

const

 

 

/ /

не модифицирует поля: где

свидетельство?

{

return

у;

}

 

 

 

 

 

 

 

 

const Point&

closestPointRef(const

Point&

p) const

 

/ /

красиво

{

return

(x*x + y*y

< p.x*p.x

+ p.y*p.y)

? *this : p;

}} ;

/ /

OK

Действительно красиво, правда? Обсу>вдение было достаточно запутанным, но ключевое слово const имеет в C++ различные смыслы, с этим ничего не поде­ лаешь. По крайней мере еще один будет обсуждаться в следующем разделе. Так что данное ключевое слово нужно принимать всерьез. При написании серверной части это ваш основной инструмент для передачи своих идей другим людям, а при чтении программы — важное средство, помогающее понять намерения разра­ ботчика. Используйте ключевое слово const там, где это возможно. Если вы не сообщите о своих замыслах (в плане операций функций-членов) программисту, занимающемуся клиентской частью или сопровождением приложения, это будет серьезной ошибкой.

щ 386 I Часть И • Объектио-орыештрошаииое программирование на С-^-^

С о в е т у е м применяйте ключевое слово const для значения (или указателя),

не модифицируемого после инициализации. Используйте его, когда нужно сообщить, что параметр функции (или указатель) не изменяется при выполнении функции, когда возвращаемое функцией значение (указатель или ссылка) не изменяются в клиенте, когда функция-член при передаче сообщения не изменяет состояния целевого объекта. При изучении исходного кода C++ также анализируйте применение

ключевого слова const. Не спешите делать выводы, кажущиеся очевидными.

Итак, нужно обращать на ключевое слово const самое пристальное внимание. Это один из моментов, с которого начинается программирование на C+ + .

Статические компоненты класса

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

При создании экземпляра объекта класса для него формируется отдельный на­ бор элементов данных. Это происходит независимо от того, как создается объект: через определение объекта как локальной или глобальной именованной перемен­ ной, с помощью операции new или неименованной динамической переменной, посредством передачи объекта по значению в параметре функции или возврата объекта по значению из функции. Каждый экземпляр объекта имеет собственный набор значений элементов данных: private, public или protected.

Ме>1^тем, нет никакой необходимости создавать для каждого объекта отдель­ ный набор функций-членов. Для каждой функции объектный код генерируется только один раз. Кроме параметров, предусмотренных программистом, каждая функция-член имеет неявный параметр — указатель на целевой объект. Когда в ее вызове в качестве цели указывается конкретный объект, функции передается указатель this на целевой объект. В результате она работает с элементами данных целевого объекта.

Применение глобальных переменных как характеристик класса

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

Например, приложению может потребоваться счетчик экземпляров класса. Рассмотрим класс Point, содержащий элемент данных count. Логически этот элемент данных принадлежит к классу точно так же, как и любые другие.

class Point {

int

X, у;

/ /

индивидуальные для каждого объекта Point

int

count;

/ /

общий для все объектов Point

. . .

} ;

 

 

С практической точки зрения здесь есть ряд проблем. Нужен только один счет­ чик точек. Если приложение создает тысячи объектов Point, нет смысла тысяче­ кратно дублировать поля count и поддерживать в каждом поле одно и то же значение. Кроме того, как поддерживать такой счетчик count? Его следует увели­ чивать при каждом создании нового объекта Point. Хорошее место для этого — конструктор Point. Аналогично деструктор будет хорошим местом для уменьшения счетчика count.

Point::Point (int

a, int

b)

/ /

общий конструктор

{ X = a; у = b;

count++;

}

/ /

увеличение счетчика объектов

Глава 9 • Классы C^-i- как единицы модульности программы

387

Но это нельзя назвать хорошим решением. Увеличивается только один счетчик во вновь созданном объекте, а элементы данных, принадлежаидие другим объек­ там, не увеличиваются. Кроме того, поле count создаваемого объекта не инициа­ лизируется предыдундим значением поля данных count в ранее созданных объектах. Следовательно, данный конструктор увеличивает неинициализированное значе­ ние. Действительно, хорошего в такой версии мало.

Для решения задачи можно было бы использовать глобальную переменную, инициализируя ее нулем в начале выполнения программы, а затем увеличивая (в конструкторе) при создании очередного объекта и уменьшая (в деструкторе) при уничтожении объекта.

Например, глобальная переменная могла бы подсчитывать число созданных экземпляров точек: конструктор будет увеличивать счетчик, а деструктор — уменьшать его. В листинге 9.5 показан пример реализации класса Point, где при­ меняется такой подход. Конструктор Point может использоваться как конструк­ тор по умолчанию (клиент не передает аргументов), конструктор преобразования (клиент передает один аргумент) и обш^ий конструктор (клиент передает два аргу­ мента — координаты точки). Для иллюстрации в конструктор и деструктор вклю­ чены операторы отладки, так что можно следить за порядком вызовов функций. Функция quantityO возвраш,ает значение count, поэтому клиент не нужно будет менять при изменении имени глобальной переменной. Переменная count явным образом инициализируется нулевым значением. Согласно правилам языка C++, ее можно инициализировать нулем и неявно, но явная инициализация предпочти­ тельнее.

Листинг 9.5. Использование глобальной переменной для подсчета создаваемых объектов

#include <iostream> using namespace std;

int count = 0; / / откуда при сопровождении программы известно, что это принадлежит Point?

class Point {

 

// частные координаты

int X, у;

 

public:

 

 

// общий конструктор

Point (int a=0, int b=0)

 

{

X = a; у = b; count++;

x « " y=" у « endl; }

 

cout « " Создана точка: x=" «

void set (int a, int b)

 

// функция-модификатор

{

X = a; у = b; }

 

// функция-селектор

void get (int& a, int& b) const

 

{

X = a; у = b; }

 

// функция-модификатор

void move (int a, int b)

 

{

X += a; у += b; }

 

 

~Point()

 

 

{

count-;

x « " y="« у « endl; }

cout « " Удалена точка: x=" «

} ;

 

 

 

int quantityO

 

// доступ к глобальной переменной

{ return count; }

 

 

int mainO

"Число точек: " « quantityO

«

endl;

{ cout «

Point *p = new Point(80,90);

 

// динамически распределяемый объект

Point p1, p2(30), p3(50,70);

«

// начало координат, ось x, общая точка

cout «

"Число точек: " « quantityO

endl;

return 0;

 

// динамический объект не удаляется должным образом

}

 

 

 

I

388

Часть II * Объектно-ориентир^

ie nporpaw'ii

:с;ние на C-^-f*

 

Число точек: О

 

 

Результаты выполнения программы показаны на рис. 9.6. Как вид­

 

у=90

 

но, все начинается с создания неименованного объекта Point. Первая

 

Создана

точка: х=80

 

создаваемая именованная точка (р1) инициализируется конструктором

 

Создана

точка: х=0

у=0

 

 

Создана

точка: х=30

у=0

 

по умолчанию, р2 инициализируется конструктором преобразования,

 

Создана

точка: х=50

у=70

 

а рЗ — общим конструктором. Эти именованные переменные Point

 

Число точек: 4

 

 

 

Удалена

точка: х=50

у=70

 

уничтожаются в обратном порядке. Обратите внимание, что здесь не

 

Удалена

точка: х=30

у=0

 

удаляется должным образом неименованная динамическая перемен­

 

Удалена

точка: х=0

у=0

 

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

 

 

 

 

 

 

Рис. 9 . 6 . Результат

 

свидетельствующего о ее уничтожении.

 

 

 

Такая конструкция работает, но имеет ряд недостатков, затрудняю­

 

 

программы

 

щих ее преобразование в большую программу. Любая часть клиента

 

 

из лист,инга

9.5

может обращаться к переменной count и модифицировать ее значение.

 

 

 

 

 

Тем самым создаются зависимости между отдельными частями про­

 

 

 

граммы. Имя глобальной переменной может конфликтовать с другими глобаль­

 

 

 

ными именами проекта или с библиотечными именами. Каждый участвующий

 

 

 

в проекте разработчик должен быть осведомлен о данном имени, хотя большинст­

 

 

 

ву из них даже не нужно знать о классе Point. Тем самым без какой бы то ни было

 

 

 

необходимости расширяется область знания программистов о разных частях про­

 

 

 

екта, увеличивается сложность его разработки и сопровождения.

 

 

 

 

Между тем, основная проблема данного решения в том, что в нем знание раз­

 

 

 

работчика не передается сопровождающему приложение программисту. При опре­

 

 

 

делении переменной count известно, что эта переменная должна подсчитывать

 

 

 

число объектов Point, а не число

объектов Rectangle

или что-то еще, однако

в синтаксисе ничто не указывает, что данная переменная ассоциирована с конк­ ретным классом. При сопровождении программы узнавать об этом придется из комментариев (а они могут быть непонятными, устаревшими или вовсе отсутство­ вать) или путем исследования больших сегментов программного кода.

Четвертый смысл ключевого слова static

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

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

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

В данном разделе ключевое слово static применяется к элементам данных класса. Оно означает как раз то, что нам нужно — для всех объектов класса суще­ ствует только один экземпляр таких данных. Данные будут общими для всех объектов типа класса.

Во всех других отношениях статические элементы данных — это обычные данные. При необходимости они могут определяться как public, private или protected. Синтаксис определения статических элементов данных и доступа к ним такой же, как и у других элементов данных. Единственная разница — в ключевом

слове

static.

 

class

Point

{

 

int X,

у;

/ / закрытые координаты

static

int

count;

/ /

еще один смысл этого ключевого слова

public:

 

 

 

 

Point

(int

a=0, int b=0)

/ /

многосторонний конструктор

{ X = a;

у = b; count++;

}

 

Глава 9 • Классы C^-f как ВА^ИЫЦЫ модульности nporpat^^bi

389

void

set (int a,

int b)

/ /

функция-модификатор

 

{

X = a;

у = b;

}

 

 

 

 

void

get (int& a,

int&

b) const

/ /

функция-селектор

 

{

X = a;

у = b;

}

 

/ /

функция-модификатор

 

void

move (int a,

int

b)

 

{

X += a;

у += b; }

 

/ /

деструктор

 

"PointO

 

 

 

 

{

count-;

}} ;

 

 

 

 

 

Здесь элемент данных count — это единое совместно используемое значение, доступное для всех экземпляров объекта Point. Этот элемент данных находится в той же области действия, где и определение класса, и доступен в ней.

Инициализация статических элементов данных

Подобно глобальным переменным, не являющимся элементами данных, стати­ ческие элементы данных инициализируются вне спецификации класса. В отличие от глобальной переменной count, статический элемент данных count является компонентом класса и должен инициализироваться явным образом. Для элемен­ тов данных (статических или нет) неявной инициализации не предусмотрено. Чтобы использовать имя любого компонента класса вне его фигурных скобок, нужно применять операцию области действия класса, показывающую, к какому классу относятся эти данные.

int Point::count = 0;

/ / это не присваивание (видите здесь имя типа?)

Выше уже говорилось о различии между присваиванием и инициализацией в С4-+. Часто операция присваивания может обозначать или присваивание, или инициализацию. Инициализация распознается по наличию рядом с именем пере­ менной имени типа. Здесь разница между инициализацией и присваиванием очень важна. Инициализация вполне законна и имеет один и тот же синтаксис для общедоступных и не общедоступных статических элементов данных класса. Для статических элементов данных, не являющихся общедоступными, присваивание незаконно.

Point::count = 0; / / присваивание (незаконно для закрытых данных 'count')

Для статическихэлементов данных допускается только один оператор инициа­ лизации. Следовательно, этот оператор нужно помещать в файл реализации . срр вместе с определениями функций-членов, но не в заголовочный файл класса.

Доступ к статическим элементам данных идентичен доступу к нестатическим элементам данных. Функции, не являющиеся членами класса (например, функция quantityO), не могут обращаться к закрытым статическим элементам данных непосредственно. Чтобы избежать этой проблемы, можно оформить функцию quantityO как член класса:

class Point {

int X,

у;

static

int count;

public:

(int a=0, int b=0)

Point

{X = a; у = b; count++; ] int quantityO.const

{return count; }

. . . } ;

/ /

закрытые координаты

/ /

еще один смысл этого ключевого слова

/ /

многосторонний конструктор

/ /

не изменяет состояние объекта

Обратите внимание, что предыдущая (глобальная) версия quantityOne имела модификатора const. Лишь функция-член может обещать не изменять элементы данных своего целевого объекта. Глобальные функции целевого объекта не имеют.

Соседние файлы в предмете Программирование на C++