Конструкторы и деструкторы
В предыдущей лекции в определениях класса complex и класса goods есть недостатки. Первый из них – отсутствие автоматической инициализации создаваемых объектов. Для каждого вновь создаваемого объекта класса complex необходимо вызвать функцию def( ), либо явным образом с помощью уточнённых имён присваивать значения данным объекта, то есть переменным re и im. Ещё два способа – получение начальных значений при инициализации и присвоение значений с помощью явного вызова компонентной функции.
Для инициализации объектов класса в его определение явно включать специальную компонентную функцию, называемую конструктор. Формат определения конструктора в теле класса может быть таким:
<имя класса> (<список формальных параметров>)
{
<операторы тела конструктора>
};
Имя этой компонентой функции по правилам языка Си++ должно совпадать с именем класса. Такая функция автоматически вызывается при определении или размещении в памяти с помощью оператора new каждого объекта класса. Основное назначение конструктора – инициализация объектов. Для класса complex можно ввести конструктор, эквивалентный функции def( ), но отличающийся от неё только названием:
complex def(float re1, float im1)
{
re=re1; im=im1;
}
В соответствии с синтаксисом языка для конструкторов не определён тип возвращаемого значения. Даже тип voidнедопустим. С помощью параметров конструкторы могут быть переданы любые данные, необходимые для создания и инициализации объектов класса. В конструкторе complex передаются значения элементов объекта «комплексное число». По умолчанию за счёт начальных значений параметров формируется комплексное число с нулевыми мнимой и вещественной частями. В общем случае конструктор может быть как угодно сложным. Например, в классе «матрица» конструктор будет выделять память для массивов, с помощью которых представляется матрица - объект данного класса, а затем инициализировать эти массивы. Размер матрицы и начальные значения их элементов такой конструктор может получать через аппарат параметров, как и значения составных частей комплексного числа в конструкторе complex.
Второй недостаток класса complex - это общедоступность компонентов. В любом месте программы, где «видно» определение класса, можно с помощью уточнённых имён (например, <имя объекта>. re или <имя объекта>. im) или с помощью указателя на объект и операции косвенного выбора ‘- >’получить доступ к компонентным данным этого объекта. Тем самым не выполняется основной принцип абстракции данных – инкапсуляция (сокрытие) данных внутри объектов. Для изменения видимости компонент в определении класса можно использовать спецификаторы доступа. Спецификатор доступа – это одно из трёх служебных слов private(собственный),public(общедоступный),protected(собственный), за которым помещается двоеточие.
Форма записи спецификатора:
<спецификатор1>:
компонент 1; // Область действия спецификатора1
компонент 2; //
компонент 3; //
…
<спецификатор2>:
…
Как правило, к свойствам класса (компонентным данным) приписывается спецификатор private, а методам (компонентным функциям) – public. Если спецификатор доступа не указан, то для структур по умолчанию доступ – public, для всех остальных типов – private.Появление любого из спецификаторов доступа в тексте определения класса означает, что до конца определения либо до другого спецификатора доступа все компоненты класса имеют указанный статус. Защищённые (protected) компоненты класса нужны только в случае построения иерархии классов. При использовании классов без порождения на основе одних классов других (производных), применение спецификатора protectedэквивалентно использованию спецификатора private.Так как класс, все компоненты которого недоступны вне его определения, редко может оказаться полезным, то изменить статус доступа к компонентам позволяют спецификаторы доступаprivate(собственный),public(общедоступный),protected(собственный).
Итак, для сокрытия данных внутри объектов класса, достаточно перед их появлением, в определении типа (в определении класса) поместить спецификатор private. При этом необходимо, чтобы некоторые или все принадлежащие классу функции остались доступными извне, что позволило манипулировать с данными объектов класса. Этим требованиям будет удовлетворять следующее определение класса «комплексное число»:
// COMPLEX. H – определение класса «комплексное число»
#include “iostream.h”
// Класс с конструктором и инкапсуляцией данных:
class complex1
{
// Методы класса (все общедоступны – public):
// Конструктор объектов класса:
complex (float re1, float im1)
{
re=re1; im=im1;
}
// Вывести на дисплей значение комплексного числа:
void disp( )
{
cout << “real=” << re;
cout << “,imag=” <<im;
}
// Получить доступ к вещественной части комплексного числа:
float & re (void) { return re; }
// Получить доступ к мнимой части числа:
float & im ( ) { return im; }
// Данные класса (скрыты от прямых внешних обращений):
private: // Изменить статус доступа на «собственный»
float real; // Вещественная часть
float imag; // Мнимая часть
};
По сравнению с классом complex в новом классе complex1, кроме конструктора, дополнительно введены компонентные функции re( ) и im( ), с помощью которых можно получать доступ к данным объекта. Они возвращают ссылки соответственно на вещественную и мнимую части того объекта, для которого они будут вызваны.
Для конструктора не задан тип возвращаемого значения. Существуют особенности и в вызове конструктора. Без явного указания программиста конструктор всегда автоматически вызывается при определении (создании) объектов класса. При этом используется умалчиваемые значения параметров конструктора. Например, определив объект СС с неявным вызовом конструктора
сomplex1 CC;
получим при вызове СС.re ( ) значение 1.0. Функция СС. im ( ) вернёт ссылку на CC.imag, и этот элемент будет иметь значение 0.0, заданное как умалчиваемое значение параметра конструктора.
Итак, конструктор превращает фрагмент памяти в объект того типа, который предусмотрен определением класса.
Конструктор существует для любого класса, причём он может быть создан без явных указаний программиста. Таким образом, для классов complex и goods существуют автоматически созданные конструкторы.
По умолчанию формируется конструктор без параметров и конструктор копирования вида:
T : : T (const T&)
где Т – имя класса. Например,
class F
{
…
public: F (const F&);
…
}
Такой конструктор существует всегда. По умолчанию конструктор копирования создаётся общедоступным.
В классе может быть несколько конструкторов (перегрузка), но только один с умалчиваемыми значениями параметров.
Нельзя получить адрес конструктора. Параметром конструктора не может быть его собственный класс, но может быть ссылка на него, как у конструктора копирования.
Конструктор нельзя вызывать как обычную компонентную функцию. Для явного вызова конструктора можно использовать две разные синтаксические формы:
<имя класса> <имя объекта> (<фактические параметры конструктора>);
<имя класса> (<фактические параметры конструктора>);
Первая форма допускается только при непустом списке фактических параметров. Она предусматривает вызов конструктора при определении нового объекта данных класса:
complex1 SS (10.3, 0.22); // SS. real ==10.3, SS. imag==0.22
complex1 EE (2.345); // EE. real==2.345, по умолчанию EE. imag==0.0
complex1 DD ( );
В последнем примере произойдет ошибка - компилятор решит, что это прототип функции без параметров, возвращающей значения типа complex1.
Вторая форма явного вызова конструктора приводит к созданию объекта, не имеющего имени. Созданный таким вызовом безымянный объект может использоваться в тех выражениях, где допустимо использование объектов данного класса. Например:
complex1 ZZ=complex1 (4.0, 5.0);
Этим определением создаётся объект ZZ, которому присваивается значение безымянного объекта ( с элементами real==4.0, imag==5.0), созданного за счёт явного вызова конструктора.
Существует два способа инициализации данных объекта с помощью конструкторов. Первый способ, а именно передача значений параметров в тело конструктора, уже продемонстрирован на примерах.
Второй способ предусматривает применение списка инициализаторов данного объекта. Этот список помещается между списком параметров и телом конструктора:
<имя класса> (<список параметров):
<список инициализаторов компонентных данных>
{
тело конструктора
}
Каждый инициализатор списка относится к конкретному компоненту и имеет вид:
<имя компонента данных> (выражение)
Например:
сlass AZ
{
int ii; float ee; char cc;
public:
AZ (int in, float en, char cn): ii (5),
ee (ii * en + in),
cc (cn) { }
…
};
AZ A (2, 3.0, ‘d’); // Создаётся именованный объект A с компонентами А.ii==5,
// A.ee==17, A.ii==’d’
AZ X=AZ(0, 2.0, ‘z’); // Создаётся безымянный объект, в котором ii==5, ee==10,
//Cc==’z’, и копируется в объект X
Перечисленные особенности конструкторов, соглашения о статусах доступа компонентов и новое понятие «деструктор» иллюстрирует следующее определение класса «символьная строка»:
// STROKA.CPP – файл с определением класса «символьная строка»
#include <string.h> // Для библиотечных строковых функций
#include <iostream.h>
class stroka
{ // Скрытые от внешнего доступа данные:
char *ch; // Указатель на текстовую строку
int len; // Длина текстовой строки
public: // Общедоступные функции:
// Конструкторы объектов класса:
// Создаёт объект как новую пустую строку:
stroka (int N=80) :
// Строка не содержит информации
{
ch=new char [N+1]; // Память выделена для массива
ch[0]=’\0’;
}
// Создаёт объект по заданной строке:
stroka (const char *arch)
{
len=strlen (arch); ch= new char [len+1];
strcpy (ch, arch);
}
int & len_str (void) // Возвращает ссылку на длину строки
{ return len; }
char *string (void) // Возвращает ссылку на строку
{ return ch; }
void display (void) // Печатает информацию о строке
{
cout << “\nДлина строки” << len;
cout << “\nСодержимое строки “ << ch;
}
// Деструктор – освобождает память объекта:
~ stroka ( )
{
delete [ ] ch;
}
};
Так как класс stroka введён с помощью служебного слова class, то элементы char *ch и int len недоступны для непосредственного обращения. Чтобы получить значение длины строки из конкретного объекта, нужно использовать общедоступную компонентную функцию len_str ( ). Указатель на строку, принадлежащую конкретному объекту класса stroka, возвращает функция string ( ). У класса stroka два конструктора – перегруженные функции, при выполнении каждой из которых динамически выделяется память для символьного массива. При вызове конструктора с параметром int N массив из N+1 элементов остаётся пустым, а длина строки устанавливается равной 0. При вызове с параметром char *arch длина массива и его содержание определяется уже существующей строкой, которую адресует фактический параметр, соответствующий указателю-параметру arch.
Компонентная функция ~stroka ( ) – деструктор. Объясню его назначение и рассмотрю его свойства.
Динамическое выделение памяти для объектов какого-либо класса создаёт необходимость в освобождении этой памяти при уничтожении объекта. Например, если объект некоторого класса формируется как локальный внутри блока, то целесообразно, чтобы при выходе из блока, когда уже объект перестаёт существовать, выделенная память была возвращена системе. Желательно, чтобы освобождение памяти происходило автоматически и не требовало вмешательства программиста. Такую возможность обеспечивает специальный компонент класса – деструктор (разрушитель объектов) класса. Для него предусматривается стандартный формат:
~ <имя класса> ( )
{
<операторы тела деструктора>
};
Название деструктора в Си++ всегда начинается с символа тильда ‘~’, за которым без пробелов или других разделительных знаков помещается имя класса. У деструктора не может быть параметров (даже типа void). Деструктор не имеет возвращаемого значения (даже типа void). Вызов деструктора выполняется неявно, автоматически, как только один оператор, освобождающий память, выделенную для символьного массива при создании объекта класса stroka. Статус доступа деструктора по умолчанию public, то есть деструктор доступен во всей области действия определения класса.
Дата добавления: 2015-08-08; просмотров: 1092;