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

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

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

520

Часть III • Програг^1М1ирование с агрегировониам и наследованиег^

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

Если общую переменную нужно использовать в разных методах, принадлежа­ щих одному классу, применяйте коммуникации через элементы данных класса. Например, класс Sample предусматривает для каждого объекта класса память для хранения элемента данных value. На этой стадии изучения C-f + такое архи­ тектурное решение должно вызывать у вас беспокойство, но нужно иметь в виду, что оно поддерживает коммуникации между двумя методами Sample — set() и get(). Какое бы значение ни устанавливала функция set() (например, при вы­ зове set() в классе History), оно сохраняется с течением времени. Когда клиент класса Sample позднее вызывает функцию get() (например, в методах print () или averageO в классе History), функция get() получает значение, сохраненное в этом конкретном объекте Sample его методом set().

Элемент данных clata[] в классе History используется для коммуникаций моед^ функциями-членами History. Какое бы значение ни устанавливала функция set() класса History, функции print() и averageO будут применять именно его.

Кэтому можно прийти другими способами. Например, определить массив clate[ ]

вmain О как локальную переменную или глобальную переменную в файле и пере­ давать его методам класса History как параметр.

Посмотрите на следующую версию класса History. Она выполняет все те же функции, что и версия из листинга 12.4, но не хранит в качестве своего компо­ нента данных массив объектов Sample. Вместо этого класс History получает дан­ ные для работы от клиента main().

class

History {

 

 

 

 

 

 

 

 

 

 

enum { size = 8 };

 

 

 

 

 

 

 

public:

 

 

 

 

 

 

 

 

 

 

 

 

void set(Sample[]; double, int) const;

 

/ /

модификация значения

 

void print (const Sample[]) const;

 

 

/ /

вывод предыстории

 

void

average(const Sample[])

const;

 

 

/ /

вывод среднего значения

 

} ;

 

 

 

 

 

 

 

 

 

 

 

 

void History::set(Sample data[],

double s,

int i )

const

 

{

data[i] . set(s);

}

 

 

 

 

 

/ /

или просто: data[i]

void History::print

(const

Sample data[])

const

/ /

вывод предыстории

{

cout

«

"\n История измерений:"

« endl

«

endl;

 

 

 

 

for

(int

i

= 0; i

< size;

i++)

 

 

 

/ /

локальный

индекс

 

cout «

" " «

data[i] . get();

}

 

 

 

 

 

void History::average (const Sample data[])

const

 

 

 

{

cout

«

"\n Среднее значение: ";

 

 

 

/ /

вывод среднего значения

 

double sum = 0;

 

 

 

 

 

 

/ /

локальное

значение

 

for

(int

i

= 0; i

< size;

i++)

 

 

 

/ /

локальный

индекс

 

 

sum += data[i] . get();

 

 

 

 

 

 

 

cout

«

sum/size

«

endl;

}

 

 

 

 

 

 

Как бы ни была плоха данная архитектура, она синтаксически корректна и се­ мантически надежна. Ее недостаток — чрезмерные коммуникации между классом History и клиентом. Клиенту приходится поддерживать информацию, которая в листинге 12.4 использовалась классом History.

Глава 12 • Преимущества и недостатки составных классов

521

i nt mainO

 

 

 

19,

23, 29 } ;

/ / 9

значений

{ double а[] = {3

5,

7, 11, 13, 17,

Sample clata[9];

 

 

/ /

кому должны принадлежать эти данные?

History

h;

 

 

/ /

конструктор по умолчанию

for (int

i=0; i

< 9;

i++)

/ /

доступно

8 слотов

 

h.set(clata,a[i], i ) ;

/ /

установка

предыстории

 

h.print(data);

 

 

/ /

вывод предыстории

 

h.average(data);

 

 

/ /

вычисление среднего значения

return 0; }

 

 

 

 

 

 

Незначительные ошибки такого рода накапливаются, в результате ухудшается качество программ C+ + . Продумывая архитектуру, не забывайте о коммуника­

циях между классами.

 

Последний способ коммуникаций в программе С+Н

взаимодействие через

локальную переменную в методе. Его следует применять, когда функции-члену нужно сохранить значение для будуш.его использования в течение того же вызова. Например, функция averageO в листинге 12.4 использует локальные переменные sum и i для отслеживания обработанных компонентов массива и их накапливае­ мого остатка на данный момент выполнения.

Подобно предыдуш,ему примеру, такая конструкция может реализовываться по-разному. Рассмотрим следуюшую версию класса History, предусматриваюшую специализированные элементы данных для отслеживания компонентов и хранения остатка.

class History { enum { size = 8 }; Sample clata[size]; int i ;

double sum; public-

void set(double, int); void print 0 const; void averageO const;

} ;

void History::set(double s, int { data[i].set(s); }

//контейнерный класс

//массив значений (фиксированного размера)

//индекс для метода averageO

//остаток для метода averageO

//модификация значения

//вывод предыстории

//вывод среднего значения

i)

// или просто: data[i] = s;

void History::print

()

/ /

модифицирует i

{ cout

«

"\n

История измерений:" «

endl « endl;

for

( i

= 0;

i

< size; i++)

/ /

глобальный, a не локальный индекс

 

cout «

"

" «

data[i] . get();

}

void History::average ()

{cout « "\n Среднее значение sum = 0;

for

( i

= 0; i

< size; i++)

 

sum +=

data[i] . get();

cout

«

sum/size « endl; }

/ /

модифицирует sum

 

/ /

вывод среднего значения

/ /

глобальное

значение

/ /

глобальный

индекс

В данной версии метод averageO обраш^ается к глобальным переменным (эле­ ментам данных) sum и i, а не к автоматическим переменным, распределяемым во время выполнения метода. Конечно, это отражается на производительности. При каждом вызове функции averageO не нужно выделять память, поэтому данная версия работает быстрее. С другой стороны, память выделяется для каждого объекта History на время его суи;ествования, а не только для выполнения функ­ ции averageO. Такую версию averageO написать легче — можно использовать доступные переменные. Кроме того, их можно повторно применять в других функциях, например в print().

522

Часть Hi • Програ1У1Ш1ирование с агрегированием и наследованием

Одним из важных вопросов является качество ПО. В данном варианте все не так плохо, как в случае использования для взаимодействия глобальных перемен­ ных других функций. Функция averageO применяет глобальные переменные (элементы данных) sum и i для взаимодействия с собой (на следующей итерации цикла), а не с другими функциями. Тем не менее такая конструкция свидетельству­ ет о низком качестве программирования, и ее следует избегать. Один из примеров потенциальных осложнений — желание использовать глобальные элементы дан­ ных для других целей (подобно использованию индекса в функции print(), чтобы избежать описаний переменных), а это часто ведет к конфликтам. С + + поддер­ живает следующую теорию разработчика ПО: "Пусть каждая функция использует свои отдельные локальные переменные и применяет их так, как считает необходи­ мым, без риска возникновения каких-либо конфликтов".

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

С о в е т у е м Для взаимодействия отдельных сегментов программы C++ используйте связность через локальные переменные в методе.

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

Применим эти принципы разработки ПО к архитектуре класса из листин­ га 12.4. Класс History — упрощенный контейнерный класс. Он не предусматри­ вает никакой защиты клиента от переполнения контейнера или от ссылки на несуществующий элемент массива. Данная версия контейнера имеет только во­ семь слотов (ячеек) для хранения объектов Sample. Несмотря на это ограничение, клиент в mainO помещает в контейнер девять значений. Конечно, компилятор такое поведение не волнует. Операционная система также выполняет программу без всяких проблем, хотя она некорректна (см. рис. 12.7). Это распространенная проблема для приложений, использующих контейнеры. Распределение обязаннос­ тей между клиентом и контейнерным классом может быть различным, но защита контейнера от переполнения должна быть реализована, что является обязанностью класса-контейнера, а не клиента.

Когда новое значение Sample помещается в контейнер, клиент в листинге 12.4 задает и само значение, и индекс, который будет использоваться для вставки. Между тем этот подход противоречит принципу разработки ПО, который состоит в переносе обязанностей в серверный класс (в данном случае — контейнер History). Возможно, для такого простого алгоритма это не важно (все вводимые значения поступают сразу, не прерывая операции объекта-контейнера), однако у клиента есть другие важные обязанности. Он не должен отслеживать, сколько места оста­ лось в контейнере. Мониторингом состояния контейнера должен заниматься сам объект-контейнер.

Скорее всего, интерфейс между клиентом и History: :set() прочно связаны. Клиент в дополнительном параметре вынужден передавать информацию об ин­ дексе. Согласно правилам взаимодействия классов, следующая по силе степень взаимодействия — коммуникации через элементы данных класса. Чтобы усовер­ шенствовать имеющуюся архитектуру, нужно хранить информацию об индексе

Глава 12 • Преимущества и недостатки составных к л а с с о в

[ 523 |

следующего объекта Sample в классе History, а не в клиенте. Необходимо коорди­ нировать работу программистов, если разделено то, что должно быть вместе.

Улучшенная архитектура контейнера представлена в листинге 12.5. Метод History: :set() с двумя параметрами заменен на метод History: :ас1с1() с един­ ственным параметром — значением, добавляемым в конец контейнера. Контей­ нер содержит один дополнительный элемент данных — индекс idx, позволяюш.ий отслеживать используемую контейнером память. Клиент не знает, полон контей­ нер или нет. Он просто передает мето/iy addO добавляемое значение.

Так как клиент теперь не следит за использованием памяти в контейнере, от­ слеживание занятой и доступной памяти и контроль за переполнением являются обязанностями контейнера. Соответственно, контейнер знает о структуре своей памяти и ограничениях. В версии, представленной в листинге 12.4, где клиент должен решить, куда поместить следуюш.ее значение, не было необходимости инициализировать контейнерный объект. В данной версии, где контейнер сам решает, куда попадает следуюш,ее значение, он должен инициализироваться пустой областью, что обеспечит попадание в первый слот первого значения. Сле­ довательно, класс History имеет используемый по умолчанию конструктор. В нем History устанавливает индекс idx в значение 0. В методе add() контейнерный класс проверяет, заполнен ли массив. Если есть свободное место, add() исполь­ зует еш.е один свободный слот и увеличивает значение индекса idx для ссылки на следуюидий свободный слот. Если с^юты для поступающих данных недоступны, метод add О ничего не делает и игнорирует запрос клиента.

Конечно, было бы хорошо сообш.ить клиенту, успешна ли попытка добавления значения Sample в History. Клиент смог бы инициировать некоторые меры вос­ становления или уведомить о ситуации пользователя программы. Однако про­ граммисту не стоит тратить на это время. Все массивы фиксированного размера следует применять лишь для быстрого макетирования, а после отладки алгоритма заменить их на динамические массивы (см. главу 6).

Результат программы из листинга 12.5 будет тем же, что и программы из лис­ тинга 12.4.

Листинг 12.5. Контейнерный класс с массивом фиксированного размера и ко>1троль за переполнением

#include <iostream> using namespace std;

class

Sample {

 

 

// класс компонента

double value;

 

 

// значение для примера

public:

 

 

 

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

Sample (double x = 0)

 

{

value

= x;

}

 

// метод-модификатор

void set (double x)

 

{

value

= x;

}

 

// метод-селектор

double get

()

const

 

{

return

value; } }

;

 

class

History

{

 

 

// контейнерный класс

enum { size = 8 };

 

// массив значений (фиксированного размера)

Sample data[size];

 

int idx;

 

 

 

// индекс текущего значения

public:

: idx(O) {

}

// массив первоначально пуст

HistoryO

void add(double);

 

// добавление значения в конец

void print 0 const;

 

// вывод предыстории

void averageO const;

 

// вывод среднего значения

524

Часть III • Программирование с агрегированием и наследованием

void History::add(double s)

 

 

 

 

{ if (idx < size)

// илипросто: data[idx++] = s;

 

 

 

data[idx++].set(s); }

 

 

void History::print () const

endl;

 

 

 

{

cout «

"\n История измерений:" « endl «

 

 

 

 

for (int i = 0; i < size; i++)

// локальный индекс

 

 

 

cout «

" " « data[i].get(); }

 

 

 

 

void History::average () const

 

 

 

 

{

cout «

"\n Среднее значение:

// локальное значение

 

 

 

double sum = 0;

 

 

 

for (int i = 0; i < size; i++)

// локальный индекс

 

 

 

sum += data[i].get();

 

 

 

 

 

cout «

sum/size « endl; }

 

 

 

 

int mainO

 

{3, 5. 7, 11. 13, 17. 19. 23,29 } ;

// исходные данные

 

 

{ double a[]

 

 

 

History h;

 

// конструктор по умолчанию

 

 

 

for (int i=0; i < 9; i++)

// доступно 8 слотов

 

 

 

h.add(a[i]);

// добавление значения к предыстории

 

 

h.printO;

 

// вывод предыстории

 

 

 

h.averageO;

// вычисление среднего значения

 

 

 

return 0;

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

Контейнерный класс History демонстрирует клиенту минимально

возможную

 

 

 

часть своей внутренней структуры и ограничений памяти.

12.4 и 12.5

 

 

 

Одно изважных ограничений контейнерного класса в листингах

 

 

 

состоит в том, что он должен быть заполнен до своей емкости, и лишь после этого

 

 

 

клиент получает осмысленный доступ к компонентам в контейнере.Методы кон­

 

 

 

тейнера print О и average О итеративно перебирают массив до конца контейнера.

 

 

 

Е щ е одно важное ограничение заключается в том,что с точки зрения клиента все

 

 

 

операции с компонентами выполняются как одна операция. Между тем, нередко

 

 

 

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

 

 

 

кая операции для каждого компонента.

классу еще

 

 

 

Первый недостаток можно устранить,добавив к контейнерному

 

 

 

один элемент данных — count.

 

 

 

 

 

 

 

class History {

// контейнерный класс

 

 

 

 

 

enum {size = 8 };

// массив значений (фиксированного

размера)

 

 

 

Sample clata[size];

 

 

 

int count;

// число действительных элементов

 

 

 

 

 

int idx;

// индекс текущего значения

 

 

 

 

 

public:

 

 

 

 

 

 

 

HistoryO : count(O), idx(O)

{ }

/ / массив первоначально

пуст

 

 

 

void add(double);

/ /

добавление значения в конец

 

 

 

 

 

...} ;

 

 

 

 

В конструкторе (в списке инициализации) компонент данных count устанавли­ вается в значение 0. Обновлять count следует при каждом добавлении нового компонента в контейнер.

void

History::add(double

s)

 

 

{ i f

(count < size

 

/ /

проверка доступного места

 

data[count++]set(s);

}

/ /

следующая ячейка, обновление счетчика count

Глава 12 • Преимущества и недостатки составных классов

525

Если даже контейнер не полон, его компонентные функции могут использовать значение count для обработки корректного числа компонентов. Ниже функция averageO применяет его для ограничения числа участвующих в вычислении ком­ понентов.

void History::average () const

{cout « "\n Среднее значение: "; double sum = 0;

for

(int

i = 0; i

< count;

i++)

 

sum += data[i] . get();

 

cout

«

sum/count

« endl;

}

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

Для перебора компонентов используется суш,ествуюш,ий элемент данных idx, который устанавливается в О в начале итерации и увеличивается на 1 на каждом шаге. Чтобы начать итерацию для клиента, добавим в контейнерный класс метод getFirstO.

void getFirstO

 

{ idx = 0; }

/ / устанавливается на начало набора данных

Чтобы клиент мог сделать следуюш,ий шаг итерации, добавим в контейнерный класс метод getNext():

void getNextO

 

{ +-bidx; }

/ / перемещение к следующему элементу

Чтобы клиент мог обращаться к текуш^ему компоненту в контейнере, добавим метод getComponent ():

Sample& getComponentO

{ return data[idx]; } / / получение ссылки, a не значения

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

Чтобы остановить итерацию, нужен метод, возвраидаюидий true, когда итера­ ция может продолжаться, и false, когда в контейнере больше нет элементов для итерации.

bool atEndO

{ return idx < count; } / / true, если остались еще элементы

Тогда цикл итерации в клиенте может выглядеть так:

for (h . getFirstO; h.atEndO; h.getNextO)

//перебор до конца

cout « " " « h. getComponentO. get();

/ / вывод компонентов

Часто разработчики контейнеров объединяют функции getNextO и atEnd() в одну функцию, увеличиваюшую индекс и возвраш.аюшую true, если больше элементов для итерации нет.

bool getNextO

 

{ return ++idx < count; }

/ / переход к следующему элементу в наборе

526

Часть III • Программирование с агрегированием и наследованием

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

Результат программы из листинга 12.6 будет тем же, что и результаты про­ грамм из листингов 12.4 и 12.5.

Листинг 12.6. Контейнерный класс с массивом фиксированного размера и итератором

#include <iostream> using namespace std;

class Sample {

// класс компонента

double value;

// значение для примера

public:

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

Sample (double x = 0)

{ value = x; }

// метод-модификатор

void set (double x)

{ value = x; }

// метод-селектор

double get () const

{ return value; } }

;

class History {

// контейнерный класс

enum { size = 8 };

 

Sample data[size]; int count;

int idx; public:

HistoryO : count(O), idx(O) { } void add(double);

Sample& getComponentO

{return data[idx]; } void getFirstO

{idx =0; }

bool getNextO

{ return ++idx < count; } void averageO const;

} ;

void History::set(double s) { if (count < size)

data[count++].set(s); }

void History::average () const

{

cout «

"\n Среднее значение: "

 

double sum = 0;

 

for (int i =0; i <count; i++)

 

cout «

sum +=data[i].get();

 

sum/count « endl; }

int mainO

 

{

double а[] {3, 5, 7, 11, 13, 17, 19,

 

History h;

 

for (int i=0; i < 9; i++)

 

h.add

(a[i]);

 

cout «

" /n Measurement history:" «

h.getFirst ();

//массив значений (фиксированного размера)

//число действительных элементов

//индекс текущего значения

//массив первоначально пуст

//добавление значения в конец

//возвращает ссылку на Sample

//может быть целью сообщения

//установка наначало набора данных

//переход кследующему элементу внаборе

//вывод среднего

//или просто: data[i++] = s;

//вывод среднего значения

23,29 } ; //исходные данные

//конструктор поумолчанию

//добавить историю

endl « endl;

 

 

 

 

 

Глава 12 • Преимущества и недостатки составных классов

527

 

do {

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

cout

«

""

«

h.getComponent().get();

 

 

 

 

 

 

 

 

 

 

} while

(h.getNextO);

 

 

 

 

 

/ /

вычисление среднего значения

 

 

h.averageO;

 

 

 

 

 

 

 

 

 

 

return 0;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Некоторые программисты предпочитают связывать методы-итераторы с отдель­

 

 

 

 

 

ным итераторным классом и ассоциируют его с классом-контейнером. В данном

 

 

 

 

 

примере это не делается, чтобы не ус^южнять программу. Устраним свойственное

 

 

 

 

 

подобной архитектуре контейнера ограничение на число компонентов, которые он

 

 

 

 

 

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

 

 

 

 

 

ванной и задавалась во время создания контейнера. Если клиент пытался помес­

 

 

 

 

 

тить в контейнер больше элементов, чем тот мог содержать, то, увы, контейнер

 

 

 

 

 

с этим ничего не мог поделать.

 

 

 

 

 

 

 

 

 

 

 

 

 

На самом деле проблема легко решается. Контейнерный класс должен выде­

 

 

 

 

 

лить новое пространство, скопировать в него дополнительные данные, освободить

 

 

 

 

 

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

 

 

 

 

 

будет исчерпана. Хорошей стратегией для выделения новой памяти является

 

 

 

 

 

удвоение ее объема (размера массива).

 

 

 

 

 

 

 

 

 

 

 

void

History: :add(clouble s)

 

 

 

 

 

 

 

 

 

 

 

 

 

{

i f

(count

==

size)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{ size

= size

* 2;

 

 

/ /

удвоение

размера, если нет памяти

 

 

 

 

 

 

 

Sample *p = new Sample[size];

 

 

 

 

 

 

 

 

 

 

 

 

i f (p == NULL)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{

cout

«

" Нет памяти\п";

exit(1);

}

 

 

/ / проверка на успех

 

 

 

 

 

 

for

(int

i=0;

i

< count;

i++)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

p [ i ]

= data[i];

 

 

/ /

копирование существующих

элементов

 

 

 

 

 

 

delete

[

] data;

 

 

/ /

удаление существующего массива

 

 

 

 

 

 

data

= р;

 

 

 

 

 

 

/ /

замена его на новый массив

 

 

 

 

 

 

cout

«

"

новый размер: "

« size

« endl;

}

 

/ / отладка

 

 

 

 

 

 

 

data[count++].set(s); }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

/ / использование

следующего доступного пространства

 

 

 

 

 

Чтобы алгоритм работал, элемент данных data должен обозначать динамически

 

 

 

 

 

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

 

 

 

 

 

class

History

{

 

 

 

 

/ /

контейнерный

класс: установка

значения

 

 

 

 

 

 

int size,

count,

idx;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Sample

*data;

 

 

 

/ /

динамическая

память

 

 

 

 

 

 

 

public:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

HistoryO

: size(3), count(O)

idx(O)

 

 

/ /

сделать массив пустым

 

 

 

 

 

 

{

data

= new Sample[size];

/ /

выделение новой памяти

 

 

 

 

 

 

 

 

i f

 

(data

== NULL)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{

cout

«

" Нет памяти\п";

exit(1);

}

}

 

 

 

 

 

 

 

 

.

. . }

;

 

 

 

 

 

/ /

остальная

часть класса History

новый

размер: 6

 

 

 

 

 

 

 

 

 

Данная версия контейнера показана в листинге 12.7. Для

новый

размер: 12

 

 

 

 

 

 

 

 

 

простоты примера в качестве начального размера контей­

История

измерений:

 

 

 

 

 

 

 

 

нера задается очень маленькое значение (три компонента).

3

5

7

11

13

17

19

23 29

 

 

 

 

Показана

работа алгоритма. Результат программы проде­

 

 

 

 

монстрирован на рис. 12.8. Сначала выводятся отладочные

Среднее

значение: 14.1111

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

сообидения об увеличении размера контейнера с 3 до 6 (когда

Рис. 12.8.

 

 

 

 

 

 

 

 

 

 

в контейнер помендается четвертый элемент), а затем с б

выполнения

 

 

 

 

 

до 12 (когда в контейнере оказывается седьмое значение).

Результат

 

 

 

 

 

программы

из лист^инга 12.7

 

 

 

 

 

 

 

 

 

 

 

 

 

 

// добавление значения к предыстории
// вывод значения // возвращает динамическую память

528

 

Часть IIU Программирование с огрегированиегм! и носдедованйег^!

Листинг 12.7. Контейнерный класс с динамически распределяемой памятью

#inclucle <iostream>

 

/ /

динамический контейнер переменного размера

using

namespace std;

 

 

class

Sample {

 

 

/ /

класс компонента

double value;

 

 

/ /

значение для примера

public:

 

 

 

 

 

 

Sample (double x = 0)

/ /

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

 

{

value

 

= x;

}

 

 

 

void set (double

x)

/ /

метод-модификатор

 

{

value

 

= x;

}

 

 

 

double get

 

()

const

/ /

метод-селектор

 

{

return

value;

} }

 

 

class

History

{

 

 

// контейнерный класс: установка значения

int

size,

count,

idx;

 

 

Sample *data;

 

 

 

 

public:

 

 

 

 

// сделать массив пустым

HistoryO

: size(3), count(O), idx(O)

{

data = new Sample[size];

// выделение новой памяти

 

if (data == NULL)

 

}

 

 

{ cout «

" Нет памяти\п"; exit(1); }

void add(double);

 

// добавление значения в конец

Sample& getComponentO

// возвращает ссылку на Sample

{

return data[idx]; }

// может быть, целью сообщения

void getFirstO

{idx = 0; } bool getNextO

{return ++idx < count; }

void average () const; "HistoryO {delete [ ]data; }

} ;

void History::add(double s)

 

 

 

{

if (count == size)

 

// удвоение размера, если нет памяти

 

{ size = size * 2;

 

 

Sample *p = new Sample[size];

 

 

 

if (p == NULL)

 

}

 

 

{ cout « " Нет памяти\п"; exit(1)

// проверка науспех

 

for (int i=0; i < count;

i++)

// копирование существующих элементов

 

p[i] = data[i];

 

 

delete [ ] data;

 

// освобождение существующей памяти

 

data = p;

« size «

// замена на новый массив

 

cout « " новые размеры:

endl; }

// отладочный вывод

 

data[count++].set(s); }

 

// использование следующей доступной области

void History::average () const

 

 

{

cout «

"\n Среднее значение: ";

 

 

 

double sum = 0;

i++)

 

 

 

for (int i = 0; i < count;

 

 

 

sum +=data[i].get();

 

 

 

 

cout «

sum/count « endl; }

 

 

int mainO

{ double a[] = {3, 5, 7, 11, 13, 17, 19, 23,^ 29 } ; // исходные данные History h;

for (int i=0; i < 9; i++) h.add(a[i]);

Глава 12 • Преимущества и недостатки составных классов

529 3

cout « "\История измерений:" « endl; «

endl;

 

 

h.getFirstO;

/ /

перенос обязанностей

 

do {

/ /

вывод каждого компонента

 

cout « " " « h.getComponent().get();

 

} while (h.getNextO);

 

 

 

h.averageO;

 

 

 

return 0;

 

 

 

}

 

 

 

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

Нетрудно придумать и более сложные конструкции: компоненты можно сорти­ ровать, искать в контейнере, удалять, вставлять, обновлять и сравнивать. Значи­ тельное число классов-контейнеров содержится в библиотеке Standard Template Library.

Вложенные классы

Вернемся к программе из листинга 12.5. Обратите внимание, что вместо до­ бавления функций-итераторов в данной версии программы клиент не использует

объекты Sample.

int mainO

;

//исходные данные

{ double a[] = {35, 7, 11, 13, 17, 19, 23, 29

History h;

// конструктор по умолчанию

for (int 1=0; i < 9 i++)

// доступно 8 слотов

h.add(a[i]);

// установка предыстории

h.printO;

// вывод предыстории

h.averageO;

// вычисление среднего

return 0; }

 

 

Вместо этого к классу Sample имеет доступ объект History. С+-1- позволяет

программисту определить серверный класс внутри клиентского класса. В резуль­

тате имя вложенного класса будет невидимо вне составного,агрегатного класса.

class History {

// невидим вне области действия клиента

class Sample {

double value;

// закрытые данные: здесь они могут

public:

// быть общедоступными

0)

Sample (double х

{value =х; } void set (double x)

{value =x; }

double show () const { return value; }

}

 

// конец определения вложенного класса

int size, count. idx;

 

 

Sample *data;

 

 

public:

: size(3), count(O), idx(O)

// сделать массив пустым

HistoryO

{ data = new Sample[size];

 

// выделение новой памяти

if (data == NULL)

exit(l); }

}

{ cout « " Нет памяти\п"

. . . }

;

// остальная часть класса History

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