Липпман - C++ для начинающих стр 9.

Шрифт
Фон

то значение 1024 будет передано в конструктор. Если же размер не задан, допустим:

IntArray array2;

то в качестве значения отсутствующего параметра конструктор принимает величину DefaultArraySize. (Не будем пока обсуждать использование ключевого слова static в определении члена DefaultArraySize: об этом говорится в разделе 13.5. Скажем лишь, что такой член данных существует в единственном экземпляре и принадлежит одновременно всем объектам данного класса.)

Вот как может выглядеть определение нашего конструктора по умолчанию:

IntArray::IntArray (int sz)

{

// инициализация членов данных

_size = sz;

ia = new int[_size];

// инициализация элементов массива

for (int ix=0; ix_size; ++ix)

ia[ix] = 0;

}

Это определение содержит несколько упрощенный вариант реализации. Мы не позаботились о том, чтобы попытаться избежать возможных ошибок во время выполнения. Какие ошибки возможны? Во-первых, оператор new может потерпеть неудачу при выделении нужной памяти: в реальной жизни память не бесконечна. (В разделе 2.6 мы увидим, как обрабатываются подобные ситуации.) А во-вторых, параметр sz из-за небрежности программиста может иметь некорректное значение, например нуль или отрицательное.

Что необычного мы видим в таком определении конструктора? Сразу бросается в глаза первая строчка, в которой использована операция разрешения области видимости (::):

IntArray::IntArray(int sz);

Дело в том, что мы определяем нашу функцию-член (в данном случае конструктор) вне тела класса. Для того чтобы показать, что эта функция на самом деле является членом класса IntArray, мы должны явно предварить имя функции именем класса и двойным двоеточием. (Подробно области видимости разбираются в главе 8; области видимости применительно к классам рассматриваются в разделе 13.9.)

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

int ia[10] = {0,1,2,3,4,5,6,7,8,9};

IntArray iA3(ia,10);

Реализация второго конструктора очень мало отличается от реализации конструктора по умолчанию. (Как и в первом случае, мы пока опустили обработку ошибочных ситуаций.)

IntArray::IntArray (int *array, int sz)

{

// инициализация членов данных

_size = sz;

ia = new int[_size];

// инициализация элементов массива

for (int ix=0; ix_size; ++ix)

ia[ix] = array[ix];

}

Третий конструктор называется копирующим конструктором. Он инициализирует один объект типа IntArray значением другого объекта IntArray. Такой конструктор вызывается автоматически при выполнении следующих инструкций:

IntArray array;

// следующие два объявления совершенно эквивалентны:

IntArray ia1 = array;

IntArray ia2 (array);

Вот как выглядит реализация копирующего конструктора для IntArray, опять-таки без обработки ошибок:

IntArray::IntArray (const IntArray rhs )

{

// инициализация членов данных

_size = rhs._size;

ia = new int[_size];

// инициализация элементов массива

for (int ix=0; ix_size; ++ix)

ia[ix] = rhs.ia[ix];

}

В этом примере мы видим еще один составной тип данных – ссылку на объект, которая обозначается символом . Ссылку можно рассматривать как разновидность указателя: она также позволяет косвенно обращаться к объекту. Однако синтаксис их использования различается: для доступа к члену объекта, на который у нас есть ссылка, следует использовать точку, а не стрелку; следовательно, мы пишем rhs._size, а не rhs-_size. (Ссылки рассматриваются в разделе 3.6.)

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

class IntArray {

public:

explicit IntArray (int sz = DefaultArraySize);

IntArray (int *array, int array_size);

IntArray (const IntArray rhs);

// ...

private:

void init (int sz,int *array);

// ...

};

// функция, используемая всеми конструкторами

void IntArray::init (int sz,int *array)

{

_size = sz;

ia = new int[_size];

for (int ix=0; ix_size; ++ix)

if ( !array )

ia[ix] = 0;

else

ix[ix] = array[ix];

}

// модифицированные конструкторы

IntArray::IntArray (int sz) { init(sz,0); }

IntArray::IntArray (int *array, int array_size)

{ init (array_size,array); }

IntArray::IntArray (const IntArray rhs)

{ init (rhs._size,rhs.ia); }

Имеется еще одна специальная функция-член – деструктор, который автоматически вызывается в тот момент, когда объект прекращает существование. Имя деструктора совпадает с именем класса, только в начале идет символ тильды (~). Основное назначение данной функции – освободить ресурсы, отведенные объекту во время его создания и использования. Применение деструкторов помогает бороться с трудно обнаруживаемыми ошибками, ведущими к утечке памяти и других ресурсов. В случае класса IntArray эта функция-член должна освободить память, выделенную в момент создания объекта. (Подробно конструкторы и деструкторы описаны в главе 14.) Вот как выглядит деструктор для IntArray:

class IntArray {

public:

// конструкторы

explicit IntArray (int sz = DefaultArraySize);

IntArray (int *array, int array_size);

IntArray (const IntArray rhs);

// деструктор

~IntArray() { delete[] ia; }

// ...

private:

// ...

};

Теперь нам нужно определить операции доступа к элементам массива IntArray. Мы хотим, чтобы обращение к элементам IntArray выглядело точно так же, как к элементам массива встроенного типа, с использованием оператора взятия индекса:

IntArray array;

int last_pos = array.size()-1;

int temp = array[0];

array[0] = array[last_pos];

array[last_pos] = temp;

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

#include cassert

int IntArray::operator[] (int index)

{

assert (index = 0 index _size);

return ia[index];

}

Обычно для проектируемого класса перегружают операции присваивания, операцию сравнения на равенство, возможно, операции сравнения по величине и операции ввода/вывода. Как и перегруженных функций, перегруженных операторов, отличающихся типами операндов, может быть несколько. К примеру, можно создать несколько операций присваивания объекту значения другого объекта того же самого или иного типа. Конечно, эти объекты должны быть более или менее "похожи". (Подробно о перегрузке операций мы расскажем в главе 15, а в разделе 3.15 приведем еще несколько примеров.)

Ваша оценка очень важна

0
Шрифт
Фон

Помогите Вашим друзьям узнать о библиотеке