- •Д. Н. Лясин, с. Г. Саньков
- •Оглавление
- •1.Обзор стилей программирования
- •1.1. Процедурное программирование
- •Структурное программирование
- •Функциональное программирование
- •Логическое программирование
- •1.5. Объектно-ориентированное программирование
- •Основные принципы объектно-ориентированного программирования
- •3.1. Объявление классов и объектов
- •3.2. Конструкторы и деструкторы
- •3.3. Область видимости компонент класса
- •3.4. Определение компонентных функций класса
- •3.5. Статические компоненты классов
- •Дружественные функции
- •3.7. Перегрузка операций
- •4. Наследование классов
- •4.1. Повторное использование классов: наследование и агрегирование
- •4.3. Множественное наследование
- •4.4. Виртуальные классы
- •4.5. Виртуальные функции. Полиморфизм
- •4.6. Абстрактные классы
- •Список литературы
3.2. Конструкторы и деструкторы
Приведенный в листинге 1 пример класса-массива обладает рядом недостатков. В частности, возможна такая работа с объектом:
//Листинг 4.Пример неверного обращения к методам класса «массив целых чисел»
//из листинга 1
main()
{ array m;
m.ReadMas();
m.WriteMas();
}
Проблема здесь заключается в том, что для класса не предусмотрена защита от некорректных вызовов методов, и метод чтения массива ReadMas может быть вызван еще до инициализации массива, то есть без выделения памяти под него. Это обязательно в дальнейшем приведет к потере данных. Таким образом, можно сказать, что для данного класса не продуман как следует интерфейс, который бы обеспечивал целостность объекта при любых операциях с ним.
Решением проблемы могло бы стать введение дополнительного члена данных, который своим значением определял, проинициализирован ли массив или нет. Переопределим класс array:
//Листинг 5. Решение проблемы некорректности интерфейса класса введением дополнительного //компонентного данного
struct array
{ int *mas, n;
int present;
void InitMas(int k)
{if (!present)
{if (k>0)
{ n=k;
mas=new int[n];
present=1;
}
}
else cout<<”Память уже выделена”;
}
void DelMas() //функция уничтожения массива
{if (present)
{
delete []mas;
present=0;
n=0;
}
else cout<<”Память не была выделена”;
}
void ReadMas() //функция ввода массива в клавиатуры
{if(present)
{ cout<<"Ввод массива";
for (int i=0;i<n;i++)
cin>>mas[i];
}
else cout<<”Ошибка! Память под массив не выделена”;
}
};
В программе из листинга 5 в класс введен дополнительный компонент present, который принимает единичное значение, когда память под массив выделена, и нулевое – в противном случае. При такой реализации методов класса их можно вызывать в программе в любой последовательности. Необходимо только позаботиться, чтобы при определении класса начальное значение свойства present было равно нулю. Начальная инициализация члена данных может быть осуществлена аналогично инициализации полей структуры.
array m={NULL, 0, 0};
Однако, такой способ инициализации компонентных данных не всегда удобен, поскольку при создании объекта зачастую необходимо не просто присвоить некоторые начальные значения компонентным данным, но и выполнять ряд действий: выделить динамическую память, открыть файл и т.п. В рассматриваемом примере с классом-массивом, например, при создании объекта было бы полезно сразу выделить под него динамическую память, что позволит избавиться от проблемы работы с неинициализированным объектом без введения дополнительной компоненты present. Для этих целей в класс вводится специальная компонентная функция, называемая конструктором.
Конструктор – это метод класса, имя которого совпадает с именем класса. Конструктор вызывается автоматически после выделения памяти для переменной и обеспечивает инициализацию компонент-данных. Конструктор не имеет никакого типа (даже типа void) и не возвращает никакого значения в результате своей работы. Конструктор нельзя вызывать как обычную компонентную функцию в программе. Вызов конструктора в программе выглядит следующим образом:
имя_класса имя_объекта ( фактические_параметры_конструктора );
имя_класса * имя_указателя = new имя_класса(фактические_ параметры_ конструктора );
Для класса может быть объявлено несколько конструкторов, различающихся числом и типами параметров. При этом даже если для объектного типа не определено ни одного конструктора, компилятор создает для него конструктор по умолчанию, не использующий параметров, а также конструктор копирования, необходимый в том случае, если переменная объектного типа передается в конструктор как аргумент. В этом случае создаваемый объект будет точной копией аргумента конструктора.
//Листинг 6. Конструкторы по умолчанию
struct MyClass
{//конструкторы по-умолчанию (создаются компилятором)
MyClass() // без параметров
{…}
MyClass(MyClass ©) //конструктор копирования
{…}
};
main()
{
MyClass m; //вызов конструктора без параметров
MyClass m1(m);//вызов конструктора копирования
}
Для класса array вместо метода InitMas необходимо определить конструктор, который выделял бы динамически память под массив.
//Листинг 7. Переопределение класса «массив целых чисел» с использованием
//конструктора
struct array
{ …
array(int k)
{if(k>0)
{ n=k;
mas=new int[n];
}
else
{cout<<Неверный размер массива”;
n=0;
}
}
…
};
main()
{ array m(5); //вызов конструктора. Память выделяется под 5 элементов массива
m.ReadMas(); //ввод элементов массива с клавиатуры
…
}
Описание конструктора можно упростить, если компонентные данные принадлежат к базовым типам или являются объектными переменными, имеющими конструктор. При описании конструктора после заголовка функции можно поставить двоеточие и за ним список инициализаторов вида
имя_компонента (выражение)
Например, для класса array можно было определить конструктор так:
class array
( .....
public:
array ( int k) : n(k)
{mas=new int[n] ;
}
};
Еще одним специальным методом класса является деструктор. Деструктор вызывается перед освобождением памяти, занимаемой объектной переменной, и предназначен для выполнения дополнительных действий, связанных с уничтожением объектной переменной, например, для освобождения динамической памяти, закрытия, уничтожения файлов и т.п.
Объявление деструктора в классе выглядит следующим образом:
~имя_класса() {тело деструктора}
Деструктор всегда имеет то же имя, что и имя класса, но перед именем записывается знак ~ (тильда). Деструктор не имеет параметров и подобно конструктору не возвращает никакого значения. Таким образом, деструктор не может быть перегружен и должен существовать в классе в единственном экземпляре. Деструктор вызывается автоматически при уничтожении объекта. Таким образом, для статически определенных объектов деструктор вызывается, когда заканчивается блок программы, в котором определен объект (блок в данном случае – составной оператор или тело функции). Для объектов, память для которых выделена динамически, деструктор вызывается при уничтожении объекта операцией delete.
//Листинг 8. Вызов деструктора объекта
main()
{MyClass m; //создание объекта статически
MyClass *ptm=new MyClass; //создание объекта динамически
…
delete ptm; //вызов деструктора для динамического объекта
…
//вызов деструктора для статического объекта
}
Определим деструктор для класса array.
struct array
{ …
~array()
{ if(n>0)
delete []mas;
}
…
};
Деструктор в отличие от конструктора допускает явный вызов вида:
имя_обекта.~имя_класса()
адрес_объекта->~имя_класса()