Создание объекта
Для объекта требуется наличие памяти и некоторое начальное значение. Базовый язык обеспечивает это условие с помощью объявлений, которые в то же время являются определениями. В большинстве случаев, когда мы описываем объявления, мы подразумеваем объявления-определения. Например, в
void foo()
{int n = 5;
double z[10]={ 0.0};
struct gizmo { int i, j; } w = {3,4};
}
все объекты создаются при входе в блок, при вызове foo(). Эта типичная реализация во время выполнения использует стек системы. В каждом случае создание и инициализацию этих объектов обеспечивает система. Освобождение памяти происходит автоматически, после выхода из foo ().
Создавая сложные агрегаты, пользователь нуждается в подобном управлении объектом, определенным как класс. Для класса необходим механизм, который определяет его поведение при создании и разрушении таким образом, что пользователь может использовать объекты аналогично встроенным типам.
Конструктор – функция-член с таким же именем, как у класса. Он создает значения типа класса. Этот процесс включает в себя инициализацию членов-данных и, часто, распределение свободной памяти с использованием new.
Деструктор – функция-член, имя которой – то же, что и имя класса, с предшествующим ему символом ~ (тильда). Обычно он применяется для того, чтобы уничтожать значения типа класса, чаще всего с использованием delete.
Из этих двух специальных функций-членов более сложными являются конструкторы. Они могут быть перегружены и могут получать параметры, чего нельзя сказать о деструкторах. Конструктор вызывается в том случае, когда в определении использован связанный тип. Он вызывается также тогда, когда доя передачи значения функции используется вызов по значению. Конструкторы и деструкторы не имеют возвращаемого типа и не могут использовать операторы return (выражение). Вызов деструктора происходит тогда, когда должен быть уничтожен объект класса; обычно после выхода из блока или из функции деструкторы вызываются неявно.
Классы с конструкторами
Простейший случай использования конструкторов – для инициализации. В этом и последующих разделах приведены несколько примеров, в которых конструкторы используются для инициализации значений данных-членов класса. Первый пример представляет собой реализацию типа данных mod_int для хранения чисел, которые вычисляются по модулю.
//модули чисел и инициализация с помощью конструктора
>
#include
const int modulus = 60;
class mod_int
{private:
int v;
public:
mod_int(int i) { v = i t modulus; }
void assign(int i) { v = i t modulus; }
void print() { cout<<<"\t"; }
};
Целое число v ограничивается значениями 0, 1, 2, …, modulus – 1. Программист должен добиться, чтобы это ограничение обеспечивалось всеми функциями-членами.
Функция-член mod_int::mod_int() – конструктор. У нее нет возвращаемого типа. Она вызывается во время объявления объектов типа mod_int. Эта функция имеет один параметр. При вызове для нее необходимо выражение, совместимое по присвоению с параметром типа int, который необходимо передать. Тогда конструктор создает и инициализирует объявленную переменную (экземпляр объекта).
Рассмотрим примеры объявлений, где используется этот тип:
mod_int a(0); //a.v = 0;
mod_int b(61); //b.v = 1;
но не
mod_int а; //без списка параметров
Используя этот тип, можно написать код для преобразования секунд в минуты и секунды следующим образом.
main()
{int seconds = 400;
mod int z(seconds);
cout<<<" секунд эквивалентно "<<< " минут";
z.print();
cout<<" секунд\п";
}
Конструктор по умолчанию
Конструктор, не требующий параметров, называется конструктором по умолчанию. Это может быть конструктор с пустым списком параметров или конструктор, в котором все параметры имеют значения по умолчанию. Он специально предназначен для инициализации массивов объектов класса.
Часто бывает удобно перегружать конструктор несколькими объявлениями функций. В нашем примере, было бы желательно чтобы по умолчанию v имела значение 0. Добавляя конструктор по умолчанию
mod_int() {v = 0; }
в качестве функции-члена mod int, можно получить следующие объявления:
mod_int si, s2; //оба инициализируют private член v нулем mod_int d[5]; //массив правильно инициализирован
В обоих объявлениях вызывается конструктор с пустым списком параметров.
Если класс не имеет конструктора, то массивы объектов конкретного типа распределяются системой автоматически. Если класс имеет конструктор, но не имеет конструктора по умолчанию, то распределение массива будет синтаксической ошибкой.
Обратите внимание, что наш пример mod_int мог иметь один конструктор, служащий не только общим инициализатором, но и конструктором по умолчанию.
inline mod_int::mod int(int i=0){v=i%modulus; }
Конструктор копирования
Допустим, необходимо исследовать стек и посчитать число вхождений данного символа. Для этого можно многократно выталкивать стек, проверяя каждый элемент, до тех пор, пока стек не будет пуст. Но как выйти из положения, если содержимое стека необходимо сохранить. Это можно сделать с помощью вызова параметров по значению.
int cnt_char(char с, stack s)
{int count = 0;
while (!s.empty())
count += (c == s.pop());
return (count);
}
Семантика вызова по значению требует, чтобы локальная копия типа параметра создавалась и инициализировалась от значения выражения, переданного как фактический параметр. Для этого необходим конструктор копии (constructor copy). Компилятор предоставляет такой конструктор по умолчанию. Его сигнатура:
stack::stack(const stacks);
Компилятор производит копирование путем почленной инициализации. Для сложных агрегатов, члены которых сами по себе являются указателями, такой способ не всегда эффективен. В большинстве случаев, указатель является адресом объекта, удаляемого при выходе из контекста. Однако, код, в котором производится действие по дублированию значения указателя, а не объекта, на который он указывает, может быть ошибочным. Дело в том, что удаление воздействует на другие экземпляры, все еще предполагающие, что объект существует. Поэтому важно, чтобы класс имел свою собственную явно определенную копию конструктора.
//конструктор копии для стека символов
stack::stack(const stacks str) //конструктор копии
{s = new char[str.max len];
max len = str.max_len;
top = str.top;
memcpy(s, str.s, max_len);
}
Инициализатор конструктора
Существует определенная синтаксическая конструкция для инициализации подэлементов объектов с конструкторами. Инициализаторы для структуры и членов класса могут определяться списком после списка параметров конструктора, через запятую и перед телом кода. Мы можем переписать предыдущий пример следующим образом:
//Конструктор копии для стека символов
stack::stack(const stacks str)
:max_len(str.max len), top(str.top)
{s = new char[str.max_len];
memcpy(s, str.s, max len);
}
Обратите внимание на то, как инициализация заменяет присвоение. Индивидуальные члены должны инициализироваться так:
имя члена (список выражений)
Если члены сами по себе являются классами с конструкторами, то список выражений должен быть согласован с соответствующей сигнатурой конструктора для правильного вызова перегруженного конструктора.
Такая форма инициализации члена требуется тогда, когда нестатический член является const или ссылкой.
Классы с деструкторами
Деструктор — это функция-член с таким же именем, как имя класса, перед которым стоит знак тильда. Они почти всегда вызываются неявно, обычно при выходе из блока, в котором был объявлен объект. Они также вызываются при вызове оператора delete для указателя на объект, имеющий деструктор, или в том случае, когда необходимо удалить объект, вложенный в удаляемый.
Расширим наш пример стека деструктором.
//Реализация стека с конструктором и деструктором
class stack
{private:
enum { EMPTY = -1} ;
char* s;
int max len;
int top; public:
public:
stack(); //конструктор по умолчанию
stack(int size)
{ s = new char[size];max_len = size; top = EMPTY; }
stack(const stacks str) //конструктор копии
stack(int size, const char str[]);
~stack() { delete [] s; } //деструктор
};
Внешний интерфейс этого класса остается таким же. Иными словами, все public функции-члены играют те же роли, что и прежде. Разница в том, что при вькоде из блока и функции неявно вызывается деструктор для освобождения той памяти, которая больше не доступна. Это – хороший стиль программирования, так как программы могут использовать меньшее количество доступной памяти.
ПРИМЕР: Динамически размещаемые строки
В C++ отсутствует встроенный строчный тип. Строки представляются как указатели на char, и это влияет на манипуляции с ними. В таком представлении конец строки обозначается “\0″. Основной недостаток этого заключается в том, что большинство базовых манипуляций со строками зависят от их длин. Когда длина строки известна, эффективность строковых операций значительно выше.
В этом разделе описывается полезный строковый АТД, хранящий длину строки в виде private. Тип динамически распределяется и способен представлять строки произвольной длины. Для инициализации и распределения памяти под строки составлен ряд конструкторов. Набор операций над строками закодирован как функции-члены. Для управления основным представлением строковых указателей применяются библиотечные функции из string.h.
// реализация динамически распределяемых строк.
#include
#include
class string
{private:
char* s;
int len;
public:
string() { s = new char[l]; s[0] = 0; len = 0; }
string(const strings str); //конструктор копии
string(const char* p)
{len=strlen(p); s=new char[len+1]; strcpy(s,p);}
~string() { delete [] s; }
void assign(const strings str);
void print() const { cout<<
void concat(const strings a, const strings b);
};
void sring::string(const strings str)
{len = str.len;
s = new char[len + 1] ;
strcpy(s, str.s) ;
}
void string::assign(const string& str)
{if (this == &str) return;
else delete [] s; // восстановление старой строки
len = str.len;
s = new char[len + 1];
strcpy(s, str.s);
}
void string::concat(const string& a,const string& b) {len = a.len + b.len;
delete [] s;
s = new char[len + 1];
strcpy(s, a.s);
strcat(s, b.s);
}
Этот тип позволяет объявлять строки, определять копирование одной строки в другую, печатать строку и объединять две строки. Скрытое представление – указатель на char, имеет переменную len, в которой хранит теку-щую длину строки. Конструкторы распределяют все динамически из свободной памяти.
АНАЛИЗ КЛАССА string
string(){s=new char[1]; s[0] = 0; len = 0; }
string(const string& str); //конструктор копии string(const char* p)
{len=strlen(p);s=new char[len+1]; strcpy(s,p);}
Рассмотрим три перегруженных конструктора. Первый – с пустыми параметрами по умолчанию. Он используется для объявления массива строк. Второй – конструктор копии. Третий включает указатель на параметр char, который может использоваться .для преобразования представления char* строк в тип нашего class. Он использует две библиотечных функции: strlen и strcpy. Распределяем один дополнительный символ для хранения символа “\0″ конца строки (EOL), хотя этот символ не считается strlen. Конструктор копии объясняется ниже.
~string() { delete [] s; }
Деструктор автоматически возвращает обратно память, распределенную для строк, для того, чтобы освободить память для повторного использования. Пара пустых квадратных скобок в delete стоит потому, что использовалось распределение для массива. Оператору delete [] объем памяти, связанный с указателем s, известен как базовый адрес массива.
string::string(const string& str)
{len = str.len;
s = new char[len+1];
strcpy(s, str.s);
}
Представленный конструктор копии используется для того, чтобы выполнить копирование одного значения строки в другое, когда строка
• инициализируется другой строкой;
• передается в виде параметра в функцию;
• возвращается в виде значения функции.
В C++, если такой конструктор отсутствует, то описанные операции представляют собой почленное переназначение. Это обсуждается далее в разделе 6.10.
void string::assign(const strings str)
{
if (this == Sstr) return;
else
delete [] s; //восстанавливает старое значение
len = str.len;
s = new char[len + 1];
strcpy(s, str.s);
}
Семантика назначения основывается на “семантике глубокого копирования” (deep copy semantics). При копировании необходимо контролировать, чтобы оно не производилось над одной и той же строкой. Каждый раз, при копировании значения строки, происходит физическое копирование значения с использованием strcopy().
void string::concat(const string& a, const string& b)
{len=a.len+b.len;
delete [] s;
s = new char[len+1];
strcopy(s, a.s);
strcopy(s, b.s);
}
ЧЛЕНЫ, ИМЕЮЩИЕ ТИП КЛАССА
В этом разделе тип vect используется как часть нового класса. Необходимо хранить множества значений для каждого индекса. Например, может возникнуть потребность хранить возраст, вес и рост группы лиц. Для этого можно сгруппировать вместе три массива внутри нового класса.
#include "vect.h"
class multi_v
{public:
vect a, b, c;
multi_v(int i): a(i), b(i), c(i) {}
};
В классе есть три члена vect и конструктор, который имеет пустое тело и список (через запятую) вызываемых конструкторов. Они выполняются с целым аргументом i, создавая три объекта класса а, b и с. Члены типа class инициализируются в порядке объявления.
Похожие записи
No user прокомментировали сообщение
Оставить комментарий