
Конструкторы и деструкторы
Объект можно проинициализировать на этапе его объявления, как сделал бы программист на С:
struct Student
{
int semesterHours;
double gpa;
} ;
void fnf)
{
Student s = {0,0};
// ...продолжение функции...
}
Этот фрагмент кода не будет работать для настоящего класса C++, поскольку внешнее приложение не имеет доступа к защищенным членам класса. Приведенный ниже фрагмент некорректен.
class Student
{
public:
// ...открытые члены...
protected:
int semesterHours;
double gpa;
};
void fn()
{
Student s = {0,0); // неправильно, так как
// данные-члены недоступны
// ...продолжение функции...fn
}
В данном случае функция — не член fn() не имеет права обращаться к членам semesterHours и gpa, поскольку они являются защищенными (а функция не объявлена в качестве друга класса).
Можно использовать специально сконструированную функцию класса Student для определения начальных значений объектов, но это неудобно. Лучше использовать конструктор – это специальная функция-член, которая автоматически вызывается во время создания объекта. Конструктор должен иметь то же имя, что и класс. Таким образом компилятор определяет, что именно эта функция-член является конструктором. Еще одним свойством конструктора является то, что он не возвращает никакого значения, поскольку вызывается автоматически.
Класс с использованием конструктора продемонстрирован в следующем примере:
#include <iostream.h>
class Student
{
public:
Student()
{
сout << "Конструируем объект Student\n";
semesterHours = 0;
gpa = 0.;
}
// ...остальные открытые члены...
protected:
int semesterHours;
double gpa;
}
void fn()
{
Student s; // создаем и инициализируем объект
// ...продолжение функции...
}
В этом примере компилятор сам вызывает конструктор student::student() в том месте, где объявляется объект s.
Этот простой конструктор реализован в виде встроенной (inline) функции. Конструктор можно создать и как обычную функцию с телом, вынесенным из объявления класса:
#include <iostream.h>
class Student
{
public:
Student();
// ...остальные открытые члены...
protected:
int semesterHours;
double gpa;
};
Student::Student()
{
cout << "Конструируем Student\n";
semesterHours = 0;
gpa = 0.0;
}
void fn ()
{
Student s; // создаем и инициализируем объект
// ...продолжение функции...
}
int main(int arges, char *pArgs[])
{
fn();
return 0;
}
Конструктор может быть запущен только автоматически. Нельзя вызывать конструктор как обычную функцию-член, а значит, и повторно инициализировать объект класса Student:
void fn()
{
Student s; // создаем и инициализируем объект
// ...продолжение функции...
s.Student(); // пытаемся реинициализировать его,
// но компилятор сообщит об ошибке
}
Объявление конструктора не должно содержать тип возвращаемого значения, даже типа void. Если класс имеет данные-члены, которые являются объектами другого класса, конструкторы для этих объектов также будут вызваны автоматически.
Существует также понятие копирующего конструктора. Если такой конструктор не был определен пользователем, он создается компилятором автоматически. Посмотрим работу такого конструктора на примере:
#include <iostream.h>
#include <string.h>
class Student
{
public:
Student(char *pName = "no name")
{
cout << "Создаем нового студента "<< pName << "\n";
strncpy(name, pName, sizeof(name));
name[sizeof(name) - 1] = '\0';
}
Student(Student &s)
{
cout << "Создаем копию от " << s.name << "\n";
strcpy(name, "Копия ") ;
strcat(name, s.name);
}
~Student()
{
cout << "Ликвидируем " << name << "\n";
}
protected:
char name[40];
};
class Tutor
{
public:
Tutor(Student &s):student(s)
// вызываем копирующий конструктор
// для члена student
{
cout << "Создаем учителя\n";
}
protected:
Student student;
};
void fn(Tutor tutor)
{
cout << "Это сообщение из функции fn()\n";
}
int main(int arges, char *pArgs[])
{
Student randy("Randy");
Tutor tutor(randy);
cout << "Вызываем функцию fn()\n";
fn(tutor);
cout << "Вернулись из функции fn()\n";
return 0;
}
Запуск этой программы приведет к выводу таких сообщений:
Создаем нового студента Randy
Создаем копию от Randy
Создаем учителя
Вызываем функцию fn()
Создаем копию от Копия Randy
Это сообщение из функции fn()
Ликвидируем Копия Копия Randy
Вернулись из функции fn(}
Ликвидируем Копия Randy
Ликвидируем Randy
Конструирование объекта randy приводит к вызову конструктора student, который выводит первое сообщение. Объект tutor создается с использованием конструктора Tutor(Students). Этот конструктор инициализирует член Tutor::student, вызывая копирующий конструктор для student. Он и выводит вторую строку с сообщением. Вызов функции fn() требует создания копии tutor. Поскольку Tutor не обеспечен копирующим конструктором, каждый член объекта tutor копируется посредством конструктора, созданного C++ по умолчанию. При этом, как отмечалось ранее, для копирования члена tutor.student вызывается конструктор копирования класса Student.
Копирующий конструктор, создаваемый в С++ по умолчанию, просто копирует адрес исходного объекта в конечный объект.
Объекты класса уничтожаются так же, как и создаются. Если класс может иметь конструктор для выполнения начальных установок, то он может содержать и специальную функцию для уничтожения объекта. Такая функция-член называется деструктором. Класс может затребовать для своего объекта некоторые ресурсы с помощью конструктора; эти ресурсы должны быть освобождены при уничтожении объекта. Например, если конструктор открывает файл, перед окончанием работы с объектом класса или программы этот файл следует закрыть. Возможен и другой вариант: если конструктор берет память из кучи, то она должна быть освобождена перед тем, как объект перестанет существовать. Деструктор позволяет делать это автоматически, не полагаясь на вызов необходимых функций-членов в программе.
Деструктор имеет то же имя, что и класс, но только с предшествующим ему символом тильды (~). Как и конструктор, деструктор не имеет типа возвращаемого значения. С учетом сказанного деструктор класса Student будет выглядеть так:
class Student
{
public:
Student()
{
semesterHours = 0;
gpa = 0.0;
}
~Student()
{
//любые используемые ресурсы освобождаются здесь
}
// ...остальные открытые члены...
protected:
int semesterHours;
double gpa;
};
Деструктор вызывается автоматически, когда объект уничтожается или, если говорить языком C++, происходит его деструкция. Можно также сказать "когда объект выходит из области видимости". Локальный объект выходит из области видимости, когда функция, создавшая его, доходит до команды return. Глобальный или статический объект выходит из области видимости, когда прекращается работа программы. Если уничтожается более одного объекта, деструкторы вызываются в порядке, обратном вызову конструкторов. То же касается и случая, когда уничтожаемый объект содержит объекты-члены.
C++ позволяет программисту определить конструктор с аргументами, например:
#include <iostream.h>
#include <string.h>
class Student
{
public:
Student(char *pName)
{
сout << "Конструируем студента" << pName << "\n";
strncpy(name, pName, sizeof(name));
name[sizeof(name) - 1] = '\0';
}
// ...остальные открытые члены...
protected:
char name[40];
int semesterHours;
double gpa;
};
Важной причиной использования аргументов в конструкторе состоит в том, что иногда это единственный способ создать объект с необходимыми начальными значениями. Работа конструктора заключается в создании корректного (в смысле требований данного класса) объекта. Если какой-то созданный по умолчанию объект не отвечает требованиям программы, значит, конструктор не выполняет свою работу. Например, банковский счет без номера не является приемлемым. Можно создать объект BankAccount без номера, а затем потребовать от приложения вызвать некоторую функцию-член для инициализации номера счета перед использованием. Однако при таком подходе класс вынужден полагаться на то, что эти действия будут выполнены внешним приложением.
Идея использования аргументов проста. Как известно, функции-члены могут иметь аргументы, поэтому конструктор, будучи функцией-членом, тоже может иметь аргументы. При этом нельзя забывать, что конструктор вызывается не как нормальная функция и передать ему аргумент можно только в момент создания объекта. Так, приведенная ниже программа создает объект s класса student, вызывая конструктор Student (char *). Объект s уничтожается в момент возврата из функции main ().
#include <iostream.h>
#include <string.h>
class Student
{
public:
Student(char *pName)
{
cout << "Конструируем студента " << pName << "\n";
strncpy (name, pName, sizeof (name));
name[sizeof(name) - 1] = '\0';
semesterHours = 0;
gpa = 0.0;
}
~Student()
{
cout << "Ликвидируем " << name << "\n";
// неплохо бы стереть имя уничтожаемого студента,
name[0] = '\0';
}
// ...остальные открытые члены...
protected:
char name[40];
int semesterHours;
double gpa;
};
int main(int arges, char *pArgs[])
{
Students("Danny"); // Создаем студента Дэнни
return 0;
} //а теперь “избавимся” от него
Конструкторы можно перегружать. Это означает, что определено несколько функций с одинаковым именем, но разными типами аргументов. C++ выбирает вызываемый конструктор, исходя из аргументов, передаваемых при объявлении объекта. Например, для класса Student определим три конструктора:
#include <iostream.h>
#include <string.h>
class Student
{
public:
Student()
{
сout << "Создаем студента без имени\n";
semesterHours = 0 ;
gpa = 0.0;
name[0] = '\0';
}
Student (char *pName)
{
сout << "Создаем студента " <<" pName << "\n";
strncpy (name, pName, sizeof(name));
name [sizeof(name ) – 1] = ' \0 ' ;
semesterHours = 0 ;
gpa = 0.0;
}
Student (char *pName, int xfrHours, double xfrGPA)
{
cout << "Создаем студента " << pName << "\n";
strncpy (name, pName , sizeof(name));
name [sizeof(name ) - 1 ] ='\0';
semesterHours = xfrНоurs;
gpa = xfrGPA;
}
~Student()
{
cout << "Ликвидируем студента\n";
}
// ...остальные открытые члены...
protected:
char name[40];
int semesterHours;
double gpa;
};
// приведенный ниже фрагмент по очереди
// вызывает каждый из конструкторов
int main (int arges, char *pArqs[])
{
Student noName;
Student freshMan("Smell E. Fish");
Student xfer("Upp R. Classman", 80, 2.5);
return 0;
}
Можно заметить, что все три конструктора очень похожи. Добавив значения по умолчанию в последний конструктор, все три можно объединить в один, что и сделано в приведенном ниже коде:
#include <iostream.h>
#include <string.h>
class Student
{
public:
Student(char *pName ="no name",
int xfrHours = 0,
float xfrGPA = 0.0)
{
cout << "Создаем студента " << pName << "\n";
strncpy{name, pName, sizeof(name));
name[sizeof(name) - 1] = '\0';
semesterHours = xfrHours;
gpa = xfrGPA;
}
~Student()
{
cout << "Ликвидируем студента\n";
}
// ...остальные открытые члены...
protected:
char name[40];
int semesterHours ;
double gpa;
}
int main(int arges, char *pArgs[])
{
Student noName;
Student freshMan("Smell E. Fish");
Student xfer("Upp R. Classman", 80, 2.5);
return 0;
}
Теперь все три объекта строятся с помощью одного и того же конструктора, а значения по умолчанию используются для аргументов, отсутствующих в объектах freshMan и noName.
Для обеспечения совместимости с существующим кодом С, который ничего не знает о конструкторах, C++ автоматически создает конструктор по умолчанию, который инициализирует все данные-члены объекта нулями.