Программирование >>  Структурное программирование 

1 ... 171 172 173 [ 174 ] 175 176 177 ... 342


Затем программа использует перегруженную операцию индексации, чтобы найти по ссылке элемент integers[5], принадлежащий integersl. Это индексированное имя затем используется как R-величина для печати значения integersl[5] и как L-величина с левой стороны оператора присваивания, чтобы присвоить новое значение 1000 элементу integersl[5]. Заметим, что operator[] возвращает ссылку и присваивает значение.

Затем программа пытается присвоить значение 1000 элементу inte-gersl[15], индекс которого находится вне допустимого диапазона. Программа отлавливает эту ошибку и аварийно завершается.

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

Теперь, после того, как мы разобрались с работой этой программы, перейдем к рассмотрению заголовка класса и определений функций-элементов. Строки

int *ptr; указатель на первый элемент массива

int size; размер массива

static int arrayCount; число экземпляров массивов

представляют закрытые элементы класса. Массив состоит из указателя ptr на соответствующий тип (в данном случае int), элемента size, обозначающего количество элементов в массиве, и статического элемента arrayCount, обозначающего количество экземпляров объектов массива, которые были образованы. Строки

friend ostream &operator<< (ostream &, const Array &); friend istream &operator>> (istream &, Array &);

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

cout arrayObject

он активизирует функцию operator путем генерации вызова

operator<<(cout, arrayObject)

Когда компилятор видит выражение, подобное

cin arrayObject

он активизирует функцию operator путем генерации вызова

operator (cin, arrayObject)

Еще раз отметим, что эти функции-операции не могут быть элементами класса Array, так как объект Array всегда упоминается с правой стороны операций поместить в поток и взять из потока. Функция operator печатает число элементов, указанное с помощью size, из массива, начало которого хранится в ptr. Функция operator непосредственно помещает вводимые значения в массив, указанный в ptr. Каждая из этих функций-операций возвращает соответствующую ссылку, чтобы обеспечить возможность сцепленных вызовов.

Строка

Array(int = 10); конструктор с умолчанием



объявляет конструктор класса с умолчанием и указывает, что размер массива по умолчанию равен 10 элементам. Когда компилятор видит объявление, подобное

Array integersl(7);

ИЛИ эквивгшентную форму

Array integersl = 7;

он активизирует конструктор с умолчанием. Функция-элемент конструктор с умолчанием класса Array увеличивает на 1 arrayCount, копирует аргумент в элемент данных size, с помощью new выделяет память для хранения внутреннего представления этого массива и указатель, возвращенный операцией new, присваивает элементу данных ptr, применяет assert для проверки нормального завершения new, а затем использует цикл for для задания нулевых начальных значений элементам массива. Можно, конечно, сделать класс Array, который не инициализирует свои элементы, если, например, эти элементы все равно должны будут позднее вводиться извне. Но это рассматривается, как плохой стиль программирования. Массив должен быть всегда должным образом инициализирован согласованными данными. Строка

Array(const Array &); конструктор копии

является конструктором копии. Она задает начальные значения объекту класса Array путем копирования существующего объекта класса Array. Такое копирование должно выполняться весьма тщательно, чтобы избежать ловушки, состоящей в том, что оба объекта типа Array указывают на одну и ту же динамически распределенную область памяти; точно такая же проблема возникла бы с побитовым копированием по умолчанию. Конструкторы копии вызываются всякий раз, когда возникает необходимость в копировании объекта, например, при вызове по значению, когда объект возвращается из вызванной функции, или при инициализации объекта, который должен быть копией другого объекта того же самого класса. Конструктор копии вызывается в объявлении, когда объект класса Array создается и инициализируется другим объектом класса Array, как, например, в следующем объявлении:

Array integers3(integersl);

или в эквивалентном объявлении

Array integersS = integersl;

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

Функция-элемент конструктор копии класса Array увеличивает на 1 arrayCount, копирует элемент size массива, использованного для инициализации, в элемент данных size нового объекта, с помощью new выделяет память для размещения внутреннего представления массива и присваивает указатель, возвращенный операцией new, элементу данных ptr, применяет assert для проверки успешного завершения new, затем использует цикл for, чтобы скопировать все элементы инициирующего массива в новый массив. Важно отметить, что если бы конструктор копии просто скопировал ptr из исходного



Хороший стиль программирования 8.7

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

Строка

~Аггау(); деструктор

объявляет деструктор класса. Деструктор активизируется автоматически по окончании жизни объекта класса Array. Деструктор уменьшает на 1 array-Count, затем использует delete для освобождения динамической области памяти, созданной в конструкторе с помощью new. Строка

const Array &operator= (const Array &); присваивание массивов

объявляет для класса перегруженную функцию-операцию присваивания. Когда компилятор встречает выражение, подобное

integersl = integers2;

ОН активизирует функцию operator=, генерируя вызов

integersl.operator=(integers2)

Функция-элемент operator= сначала осуществляет проверку самоприсваивания. Если имеет место попытка самоприсваивания, присваивание пропускается (т.е. объект уже есть сам по себе; вскоре мы увидим, почему самоприсваивание опасно). Если самоприсваивания нет, то функция-элемент использует delete, чтобы освободить память, ранее выделенную в массиве-адресате, копирует size исходного массива в size массива-адресата, использует new, чтобы выделить требуемую память массиву-адресату, и помещает указатель, возвращенный new, в элемент ptr массива, используя при этом assert для проверки успешного завершения new. Затем используется цикл for для копирования элементов исходного массива в массив-адресат. Независимо от того, есть самоприсваивание или нет, функция элемент затем возвращает текущий объект (т.е. *this) как константную ссылку; это делает возможным сцепленное присваивание, такое как x=y=z. Если бы проверки самоприсваивания не было, функция-элемент должна была бы начать с уничтожения пространства массива-адресата. Поскольку при самоприсваивании это также и исходный массив, то массив оказался бы разрушенным.

Типичная ошибка программирования 8.5

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

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



1 ... 171 172 173 [ 174 ] 175 176 177 ... 342

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика